All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.odftoolkit.simple.SpreadsheetDocument Maven / Gradle / Ivy

The newest version!
/**
 * **********************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Use is subject to license terms.
 *
 * 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. You can also
 * obtain a copy of the License at http://odftoolkit.org/docs/license.txt
 *
 * 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.
 *
 ***********************************************************************
 */
package org.odftoolkit.simple;

import java.awt.Rectangle;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.xpath.XPathConstants;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeSpreadsheetElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableElement;
import org.odftoolkit.odfdom.pkg.MediaType;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfPackage;
import org.odftoolkit.odfdom.type.CellRangeAddressList;
import org.odftoolkit.simple.chart.AbstractChartContainer;
import org.odftoolkit.simple.chart.Chart;
import org.odftoolkit.simple.chart.ChartContainer;
import org.odftoolkit.simple.chart.DataSet;
import org.odftoolkit.simple.table.Cell;
import org.odftoolkit.simple.table.RepeatedNumberUtils;
import org.odftoolkit.simple.table.Table;
import org.odftoolkit.simple.table.TableContainer;
import org.w3c.dom.Node;

/**
 * This class represents an empty ODF spreadsheet document.
 *
 */
public class SpreadsheetDocument extends Document implements ChartContainer {

    private static final String EMPTY_SPREADSHEET_DOCUMENT_PATH = "/OdfSpreadsheetDocument.ods";
    static final Resource EMPTY_SPREADSHEET_DOCUMENT_RESOURCE = new Resource(EMPTY_SPREADSHEET_DOCUMENT_PATH);
    private ChartContainerImpl chartContainerImpl;

    /**
     * This enum contains all possible media types of SpreadsheetDocument
     * documents.
     */
    public enum OdfMediaType implements MediaType {

        SPREADSHEET(Document.OdfMediaType.SPREADSHEET), SPREADSHEET_TEMPLATE(Document.OdfMediaType.SPREADSHEET_TEMPLATE);
        private final Document.OdfMediaType mMediaType;

        OdfMediaType(Document.OdfMediaType mediaType) {
            this.mMediaType = mediaType;
        }

        /**
         * @return the mediatype of this document
         */
        public String getMediaTypeString() {
            return mMediaType.getMediaTypeString();
        }

        /**
         * @return the ODF filesuffix of this document
         */
        public String getSuffix() {
            return mMediaType.getSuffix();
        }

        /**
         *
         * @param mediaType string defining an ODF document
         * @return the according OdfMediatype encapuslating the given string and
         * the suffix
         */
        public static Document.OdfMediaType getOdfMediaType(String mediaType) {
            return Document.OdfMediaType.getOdfMediaType(mediaType);
        }
    }

    /**
     * Creates an empty spreadsheet document.
     *
     * @return ODF spreadsheet document based on a default template
     *
     * @throws java.lang.Exception - if the document could not be created
     */
    public static SpreadsheetDocument newSpreadsheetDocument() throws Exception {
        return (SpreadsheetDocument) Document.loadTemplate(EMPTY_SPREADSHEET_DOCUMENT_RESOURCE,
            Document.OdfMediaType.SPREADSHEET);
    }

    /**
     * Creates an empty spreadsheet template.
     *
     * @return ODF spreadsheet template based on a default
     * @throws java.lang.Exception - if the template could not be created
     */
    public static SpreadsheetDocument newSpreadsheetTemplateDocument() throws Exception {
        SpreadsheetDocument doc = (SpreadsheetDocument) Document.loadTemplate(EMPTY_SPREADSHEET_DOCUMENT_RESOURCE,
            Document.OdfMediaType.SPREADSHEET_TEMPLATE);
        doc.changeMode(OdfMediaType.SPREADSHEET_TEMPLATE);
        return doc;
    }

    /**
     * To avoid data duplication a new document is only created, if not already
     * opened. A document is cached by this constructor using the internalpath
     * as key.
     */
    protected SpreadsheetDocument(OdfPackage pkg, String internalPath, SpreadsheetDocument.OdfMediaType odfMediaType) {
        super(pkg, internalPath, odfMediaType.mMediaType);
    }

    /**
     * Creates an SpreadsheetDocument from the OpenDocument provided by a
     * resource Stream.
     *
     * 

* Since an InputStream does not provide the arbitrary (non sequentiell) * read access needed by SpreadsheetDocument, the InputStream is cached. * This usually takes more time compared to the other createInternalDocument * methods. An advantage of caching is that there are no problems * overwriting an input file. *

* *

* If the resource stream is not a ODF spreadsheet document, * ClassCastException might be thrown. *

* * @param inputStream - the InputStream of the ODF spreadsheet document. * @return the spreadsheet document created from the given InputStream * @throws java.lang.Exception - if the document could not be created. */ public static SpreadsheetDocument loadDocument(InputStream inputStream) throws Exception { SpreadsheetDocument document = (SpreadsheetDocument) Document.loadDocument(inputStream); removeDummyCellsFromDocument(document); return document; } /** * Loads an SpreadsheetDocument from the provided path. * *

* SpreadsheetDocument relies on the file being available for read access * over the whole lifecycle of SpreadsheetDocument. *

* *

* If the resource stream is not a ODF spreadsheet document, * ClassCastException might be thrown. *

* * @param documentPath - the path from where the document can be loaded * @return the spreadsheet document from the given path or NULL if the media * type is not supported by SIMPLE. * @throws java.lang.Exception - if the document could not be created. */ public static SpreadsheetDocument loadDocument(String documentPath) throws Exception { SpreadsheetDocument document = (SpreadsheetDocument) Document.loadDocument(documentPath); removeDummyCellsFromDocument(document); return document; } /** * Creates an SpreadsheetDocument from the OpenDocument provided by a File. * *

* SpreadsheetDocument relies on the file being available for read access * over the whole lifecycle of SpreadsheetDocument. *

* *

* If the resource stream is not a ODF spreadsheet document, * ClassCastException might be thrown. *

* * @param file - a file representing the ODF spreadsheet document. * @return the spreadsheet document created from the given File * @throws java.lang.Exception - if the document could not be created. */ public static SpreadsheetDocument loadDocument(File file) throws Exception { SpreadsheetDocument document = (SpreadsheetDocument) Document.loadDocument(file); removeDummyCellsFromDocument(document); return document; } /** * Get the content root of a spreadsheet document. * * @return content root, representing the office:spreadsheet tag * @throws Exception if the file DOM could not be created. */ @Override public OfficeSpreadsheetElement getContentRoot() throws Exception { return super.getContentRoot(OfficeSpreadsheetElement.class); } /** * Changes the document to the given mediatype. This method can only be used * to convert a document to a related mediatype, e.g. template. * * @param mediaType the related ODF mimetype */ public void changeMode(OdfMediaType mediaType) { setOdfMediaType(mediaType.mMediaType); } /** * Retrieves sheet by index. * * @param index the index of the retrieved sheet, which starts from 0. If * the index value is out of range (index >= sheet count or index < 0), this * method would return null. @since 0.6 */ public Table getSheetByIndex(int index) { if (index < 0) { return null; } int count = 0; try { OfficeSpreadsheetElement spreadsheetElement = getContentRoot(); Node child = spreadsheetElement.getFirstChild(); while ((child != null) && (count <= index)) { if (child instanceof TableTableElement) { if (count == index) { return getTableBuilder().getTableInstance((TableTableElement) child); } else { count++; } } child = child.getNextSibling(); } } catch (Exception e) { Logger.getLogger(SpreadsheetDocument.class.getName()).log(Level.SEVERE, null, e); } return null; } /** * Retrieves sheet by name. * * @param name the name of the retrieved sheet. * @since 0.6 */ public Table getSheetByName(String name) { return getTableByName(name); } /** * Adds a new blank sheet with the specified name to this * document. * * @param name the name of the new sheet. * @return added sheet. * @since 0.6 */ public Table appendSheet(String name) { Table newTable = addTable(); newTable.setTableName(name); return newTable; } /** * Adds a new sheet with data from existing table. *

* NOTE: This method copies data from existing table, including linked * resources and styles, if the source table is not in the target document. * If these data has dependencies to other data of the source document, the * data dependencies will not be copied. For example, document A has two * sheets, "Sheet1" and "Sheet2". In "Sheet2", there is a cell with formula, * "=sum(Sheet1.A1:Sheet1.A10)". After copy the data of "Sheet2" to the new * sheet in document B, the result of this formula would be different or * even invalid in document B. * * @param refTable the reference table, which is the data source of the new * sheet. * @param name the name of the new sheet. * @return added sheet. * @since 0.6 */ public Table appendSheet(Table refTable, String name) { TableTableElement refTableElement = refTable.getOdfElement(); try { OdfContentDom contentDom = getContentDom(); TableTableElement newTableEle = (TableTableElement) (refTableElement.cloneNode(true)); // not in a same document if (refTableElement.getOwnerDocument() != contentDom) { Document ownerDocument = refTable.getOwnerDocument(); copyLinkedRefInBatch(newTableEle, ownerDocument); copyForeignStyleRef(newTableEle, ownerDocument); newTableEle = (TableTableElement) cloneForeignElement(newTableEle, contentDom, true); } updateNames(newTableEle); updateXMLIds(newTableEle); getTableContainerElement().appendChild(newTableEle); Table tableInstance = getTableBuilder().getTableInstance(newTableEle); tableInstance.setTableName(name); return tableInstance; } catch (Exception e) { Logger.getLogger(SpreadsheetDocument.class.getName()).log(Level.SEVERE, null, e); } return null; } /** * Inserts a new blank sheet before the reference index. * * @param before the reference index, which starts from 0. If the index * value is out of range (index >= sheet count or index < 0), this method * would return null. @return inserted sheet. @since 0.6 */ public Table insertSheet(int before) { if (before < 0) { return null; } int count = 0; try { OfficeSpreadsheetElement spreadsheetElement = getContentRoot(); Node child = spreadsheetElement.getFirstChild(); while ((child != null) && (count <= before)) { if (child instanceof TableTableElement) { if (count == before) { Table table = getTableBuilder().newTable(); getContentRoot().insertBefore(table.getOdfElement(), child); return table; } else { count++; } } child = child.getNextSibling(); } } catch (Exception e) { Logger.getLogger(SpreadsheetDocument.class.getName()).log(Level.SEVERE, null, e); } return null; } /** * Inserts a new sheet with data from existing table. * *

* NOTE: This method copies data from existing table, including linked * resources and styles, if the source table is not in the target document. * If these data has dependencies to other data of the source document, the * data dependencies will not be copied. For example, document A has two * sheets, "Sheet1" and "Sheet2". In "Sheet2", there is a cell with formula, * "=sum(Sheet1.A1:Sheet1.A10)". After copy the data of "Sheet2" to the new * sheet in document B, the result of this formula would be different or * even invalid in document B. * * @param refTable the reference table, which is the data source of the new * sheet. * @param before the reference index, which starts from 0 and new sheet * would be inserted before it. If the index value is out of range (index >= * sheet count or index < 0), this method would return null. * @return inserted sheet. * @since 0.6 */ public Table insertSheet(Table refTable, int before) { if (before < 0) { return null; } int count = 0; try { OfficeSpreadsheetElement spreadsheetElement = getContentRoot(); Node child = spreadsheetElement.getFirstChild(); while ((child != null) && (count <= before)) { if (child instanceof TableTableElement) { if (count == before) { TableTableElement refTableElement = refTable.getOdfElement(); try { OdfContentDom contentDom = getContentDom(); TableTableElement newTableEle = (TableTableElement) (refTableElement.cloneNode(true)); //foreign node not in a same document if (refTableElement.getOwnerDocument() != contentDom) { Document ownerDocument = refTable.getOwnerDocument(); copyLinkedRefInBatch(newTableEle, ownerDocument); copyForeignStyleRef(newTableEle, ownerDocument); newTableEle = (TableTableElement) cloneForeignElement(newTableEle, contentDom, true); } updateNames(newTableEle); updateXMLIds(newTableEle); newTableEle.setTableNameAttribute(getUniqueSheetName(this)); getContentRoot().insertBefore(newTableEle, child); return getTableBuilder().getTableInstance(newTableEle); } catch (Exception e) { Logger.getLogger(SpreadsheetDocument.class.getName()).log(Level.SEVERE, null, e); } } else { count++; } } child = child.getNextSibling(); } } catch (Exception e) { Logger.getLogger(SpreadsheetDocument.class.getName()).log(Level.SEVERE, null, e); } return null; } /** * Removes the sheet in the specified index. * * @param index the index of the removed sheet, which starts from 0. If the * index value is out of range (index >= sheet count or index < 0), this * method would do nothing. @since 0.6 */ public void removeSheet(int index) { if (index < 0) { return; } int count = 0; try { OfficeSpreadsheetElement spreadsheetElement = getContentRoot(); Node child = spreadsheetElement.getFirstChild(); while ((child != null) && (count <= index)) { if (child instanceof TableTableElement) { if (count == index) { spreadsheetElement.removeChild(child); return; } else { count++; } } child = child.getNextSibling(); } } catch (Exception e) { Logger.getLogger(SpreadsheetDocument.class.getName()).log(Level.SEVERE, null, e); } } /** * Returns the sheet count of this document. * * @return the sheet count of this document. * @since 0.6 */ public int getSheetCount() { int count = 0; try { OfficeSpreadsheetElement spreadsheetElement = getContentRoot(); Node child = spreadsheetElement.getFirstChild(); while (child != null) { if (child instanceof TableTableElement) { count++; } child = child.getNextSibling(); } } catch (Exception e) { Logger.getLogger(SpreadsheetDocument.class.getName()).log(Level.SEVERE, null, e); } return count; } public OdfElement getTableContainerElement() { return getTableContainerImpl().getTableContainerElement(); } /** * Creates a new Chart for this spreadsheet document. * * @param title chart title. * @param dataset chart data set. * @param rect chart rectangle. * @return the created chart. * * @since 0.6 */ public Chart createChart(String title, DataSet dataset, Rectangle rect) { return getChartContainerImpl().createChart(title, dataset, rect); } /** * Creates a new Chart for this spreadsheet document. * * @param title chart title. * @param document the data source spreadsheet document. * @param cellRangeAddr the cell range address list which is used as chart * data set. * @param firstRowAsLabel whether uses first row as label. * @param firstColumnAsLabel whether uses first column as label. * @param rowAsDataSeries whether uses data as series. * @param rect chart rectangle. * @return the created chart. * * @since 0.6 */ public Chart createChart(String title, SpreadsheetDocument document, CellRangeAddressList cellRangeAddr, boolean firstRowAsLabel, boolean firstColumnAsLabel, boolean rowAsDataSeries, Rectangle rect) { return getChartContainerImpl().createChart(title, document, cellRangeAddr, firstRowAsLabel, firstColumnAsLabel, rowAsDataSeries, rect); } /** * Creates a new Chart for this spreadsheet document. * * @param title chart rectangle. * @param labels label strings * @param legends legend strings * @param data chart data set. * @param rect chart rectangle. * @return the created chart. * * @since 0.6 */ public Chart createChart(String title, String[] labels, String[] legends, double[][] data, Rectangle rect) { return getChartContainerImpl().createChart(title, labels, legends, data, rect); } /** * Creates a new Chart for this spreadsheet document. * * @param title chart rectangle. * @param document the data source spreadsheet document. * @param cellRangeAddr the cell range list to be used as chart data. * @param firstRowAsLabel whether use first row as label. * @param firstColumnAsLabel whether use first column as label. * @param rowAsDataSeries whether use row as data series. * @param rect chart rectangle. * @param cell the position cell where the new chart is inserted. * @return the created chart. * * @since 0.6 */ public Chart createChart(String title, SpreadsheetDocument document, CellRangeAddressList cellRangeAddr, boolean firstRowAsLabel, boolean firstColumnAsLabel, boolean rowAsDataSeries, Rectangle rect, Cell cell) { return getChartContainerImpl().createChart(title, document, cellRangeAddr, firstRowAsLabel, firstColumnAsLabel, rowAsDataSeries, rect, cell); } public void deleteChartById(String chartId) { getChartContainerImpl().deleteChartById(chartId); } public void deleteChartByTitle(String title) { getChartContainerImpl().deleteChartByTitle(title); } public Chart getChartById(String chartId) { return getChartContainerImpl().getChartById(chartId); } public List getChartByTitle(String title) { return getChartContainerImpl().getChartByTitle(title); } public int getChartCount() { return getChartContainerImpl().getChartCount(); } private static String getUniqueSheetName(TableContainer container) { List

tableList = container.getTableList(); boolean notUnique = true; String tablename = "Sheet" + (tableList.size() + 1); while (notUnique) { notUnique = false; for (int i = 0; i < tableList.size(); i++) { if (tableList.get(i).getTableName() != null) { if (tableList.get(i).getTableName().equalsIgnoreCase(tablename)) { notUnique = true; break; } } } if (notUnique) { tablename = tablename + Math.round(Math.random() * 10); } } return tablename; } private ChartContainerImpl getChartContainerImpl() { if (chartContainerImpl == null) { chartContainerImpl = new ChartContainerImpl(this); } return chartContainerImpl; } private class ChartContainerImpl extends AbstractChartContainer { SpreadsheetDocument sdoc; DrawFrameElement drawFrame; protected ChartContainerImpl(Document doc) { super(doc); sdoc = (SpreadsheetDocument) doc; } protected DrawFrameElement getChartFrame() throws Exception { OdfContentDom contentDom2 = sdoc.getContentDom(); DrawFrameElement drawFrame = contentDom2.newOdfElement(DrawFrameElement.class); TableTableCellElement lastCell = (TableTableCellElement) contentDom2.getXPath().evaluate( "//table:table-cell[last()]", contentDom2, XPathConstants.NODE); lastCell.appendChild(drawFrame); drawFrame.removeAttribute("text:anchor-type"); this.drawFrame = drawFrame; return drawFrame; } private Chart createChart(String title, SpreadsheetDocument document, CellRangeAddressList cellRangeAddr, boolean firstRowAsLabel, boolean firstColumnAsLabel, boolean rowAsDataSeries, Rectangle rect, Cell cell) { Chart chart = getChartContainerImpl().createChart(title, document, cellRangeAddr, firstRowAsLabel, firstColumnAsLabel, rowAsDataSeries, rect); cell.getOdfElement().appendChild(this.drawFrame); return chart; } } /** * @see RepeatedNumberUtils#removeDummyCellsFromTable(Table) */ private static void removeDummyCellsFromDocument(SpreadsheetDocument document) { int sheetCount = document.getSheetCount(); for (int n = 0; n < sheetCount; n++) { RepeatedNumberUtils.removeDummyCellsFromTable(document.getSheetByIndex(n)); } } }