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

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

There is a newer version: 0.8.2-incubating
Show 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.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 { return (SpreadsheetDocument) Document.loadDocument(inputStream); } /** * 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 { return (SpreadsheetDocument) Document.loadDocument(documentPath); } /** * 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 { return (SpreadsheetDocument) Document.loadDocument(file); } /** * 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; } } }