blob: 12b96b98fe182492953f3a1ce5a7c82263fc799b [file] [log] [blame]
/************************************************************************
*
* 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.
*
* <p>
* 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.
* </p>
*
* <p>
* If the resource stream is not a ODF spreadsheet document,
* ClassCastException might be thrown.
* </p>
*
* @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.
*
* <p>
* SpreadsheetDocument relies on the file being available for read access
* over the whole lifecycle of SpreadsheetDocument.
* </p>
*
* <p>
* If the resource stream is not a ODF spreadsheet document,
* ClassCastException might be thrown.
* </p>
*
* @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.
*
* <p>
* SpreadsheetDocument relies on the file being available for read access
* over the whole lifecycle of SpreadsheetDocument.
* </p>
*
* <p>
* If the resource stream is not a ODF spreadsheet document,
* ClassCastException might be thrown.
* </p>
*
* @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 <code>null</code>.
* @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 <code>name</code> 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.
* <p>
* 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 <code>null</code>.
* @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.
*
* <p>
* 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
* <code>null</code>.
* @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 <code>index</code>.
*
* @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<Chart> getChartByTitle(String title) {
return getChartContainerImpl().getChartByTitle(title);
}
public int getChartCount() {
return getChartContainerImpl().getChartCount();
}
private static String getUniqueSheetName(TableContainer container) {
List<Table> 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;
}
}
}