blob: b8425bfe178533b36f5d3c17f6ced265bcab7212 [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.odfdom.doc;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import org.odftoolkit.odfdom.doc.table.OdfTable;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.OdfMetaDom;
import org.odftoolkit.odfdom.dom.OdfSchemaConstraint;
import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
import org.odftoolkit.odfdom.dom.attribute.text.TextAnchorTypeAttribute;
import org.odftoolkit.odfdom.dom.element.draw.DrawPageElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeBodyElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableElement;
import org.odftoolkit.odfdom.dom.element.text.TextPElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.dom.style.props.OdfTextProperties;
import org.odftoolkit.odfdom.incubator.doc.draw.OdfDrawFrame;
import org.odftoolkit.odfdom.incubator.doc.draw.OdfDrawImage;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfDefaultStyle;
import org.odftoolkit.odfdom.incubator.meta.OdfOfficeMeta;
import org.odftoolkit.odfdom.pkg.MediaType;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.pkg.OdfPackage;
import org.odftoolkit.odfdom.pkg.OdfValidationException;
import org.odftoolkit.odfdom.type.Duration;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
/**
* This abstract class is representing one of the possible ODF documents.
*
*/
public abstract class OdfDocument extends OdfSchemaDocument {
// Static parts of file references
private static final String SLASH = "/";
private OdfMediaType mMediaType;
private OdfOfficeMeta mOfficeMeta;
private long documentOpeningTime;
private static final Pattern CONTROL_CHAR_PATTERN = Pattern.compile("\\p{Cntrl}");
private static final String EMPTY_STRING = "";
private Calendar mCreationDate;
// Using static factory instead of constructor
protected OdfDocument(OdfPackage pkg, String internalPath, OdfMediaType mediaType) throws SAXException {
super(pkg, internalPath, mediaType.getMediaTypeString());
mMediaType = mediaType;
//set document opening time.
documentOpeningTime = System.currentTimeMillis();
}
/**
* This enum contains all possible media types of OpenDocument documents.
*/
public enum OdfMediaType implements MediaType {
CHART("application/vnd.oasis.opendocument.chart", "odc"),
CHART_TEMPLATE("application/vnd.oasis.opendocument.chart-template", "otc"),
FORMULA("application/vnd.oasis.opendocument.formula", "odf"),
FORMULA_TEMPLATE("application/vnd.oasis.opendocument.formula-template", "otf"),
DATABASE_FRONT_END("application/vnd.oasis.opendocument.base", "odb"),
GRAPHICS("application/vnd.oasis.opendocument.graphics", "odg"),
GRAPHICS_TEMPLATE("application/vnd.oasis.opendocument.graphics-template", "otg"),
IMAGE("application/vnd.oasis.opendocument.image", "odi"),
IMAGE_TEMPLATE("application/vnd.oasis.opendocument.image-template", "oti"),
PRESENTATION("application/vnd.oasis.opendocument.presentation", "odp"),
PRESENTATION_TEMPLATE("application/vnd.oasis.opendocument.presentation-template", "otp"),
SPREADSHEET("application/vnd.oasis.opendocument.spreadsheet", "ods"),
SPREADSHEET_TEMPLATE("application/vnd.oasis.opendocument.spreadsheet-template", "ots"),
TEXT("application/vnd.oasis.opendocument.text", "odt"),
TEXT_MASTER("application/vnd.oasis.opendocument.text-master", "odm"),
TEXT_TEMPLATE("application/vnd.oasis.opendocument.text-template", "ott"),
TEXT_WEB("application/vnd.oasis.opendocument.text-web", "oth");
private final String mMediaType;
private final String mSuffix;
OdfMediaType(String mediaType, String suffix) {
this.mMediaType = mediaType;
this.mSuffix = suffix;
}
/**
* @return the mediatype String of this document
*/
public String getMediaTypeString() {
return mMediaType;
}
/**
* @return the ODF filesuffix of this document
*/
public String getSuffix() {
return mSuffix;
}
/**
*
* @param mediaType string defining an ODF document
* @return the according OdfMediatype encapuslating the given string and the suffix
*/
public static OdfMediaType getOdfMediaType(String mediaType) {
OdfMediaType odfMediaType = null;
if (mediaType != null) {
String mediaTypeShort = mediaType.substring(mediaType.lastIndexOf(".") + 1, mediaType.length());
mediaTypeShort = mediaTypeShort.replace('-', '_').toUpperCase();
try {
odfMediaType = OdfMediaType.valueOf(mediaTypeShort);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Given mediaType '" + mediaType + "' is not an ODF mediatype!");
}
}
return odfMediaType;
}
}
/**
* Loads the ODF root document from the given Resource.
*
* NOTE: Initial meta data (like the document creation time) will be added in this method.
*
* @param res a resource containing a package with a root document
* @param odfMediaType the media type of the root document
* @return the OpenDocument document
* or NULL if the media type is not supported by ODFDOM.
* @throws java.lang.Exception - if the document could not be created.
*/
protected static OdfDocument loadTemplate(Resource res, OdfMediaType odfMediaType) throws Exception {
InputStream in = res.createInputStream();
OdfPackage pkg = null;
try {
pkg = OdfPackage.loadPackage(in);
} finally {
in.close();
}
OdfDocument newDocument = newDocument(pkg, ROOT_DOCUMENT_PATH, odfMediaType);
//add creation time, the metadata have to be explicitly set
newDocument.mCreationDate = Calendar.getInstance();
return newDocument;
}
/**
* Loads the ODF root document from the ODF package provided by its path.
*
* <p>OdfDocument relies on the file being available for read access over
* the whole lifecycle of OdfDocument.</p>
*
* @param documentPath - the path from where the document can be loaded
* @return the OpenDocument from the given path
* or NULL if the media type is not supported by ODFDOM.
* @throws java.lang.Exception - if the document could not be created.
*/
public static OdfDocument loadDocument(String documentPath) throws Exception {
return loadDocument(OdfPackage.loadPackage(documentPath));
}
/**
* Loads the ODF root document from the ODF package provided by a Stream.
*
* <p>Since an InputStream does not provide the arbitrary (non sequentiell)
* read access needed by OdfDocument, 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>
*
* @param inStream - the InputStream of the ODF document.
* @return the document created from the given InputStream
* @throws java.lang.Exception - if the document could not be created.
*/
public static OdfDocument loadDocument(InputStream inStream) throws Exception {
return loadDocument(OdfPackage.loadPackage(inStream));
}
/**
* Loads the ODF root document from the ODF package provided as a File.
*
* @param file - a file representing the ODF document.
* @return the document created from the given File
* @throws java.lang.Exception - if the document could not be created.
*/
public static OdfDocument loadDocument(File file) throws Exception {
return loadDocument(OdfPackage.loadPackage(file));
}
/**
* Loads the ODF root document from the ODF package.
*
* @param odfPackage - the ODF package containing the ODF document.
* @return the root document of the given OdfPackage
* @throws java.lang.Exception - if the ODF document could not be created.
*/
public static OdfDocument loadDocument(OdfPackage odfPackage) throws Exception {
return loadDocument(odfPackage, ROOT_DOCUMENT_PATH);
}
/**
* Creates an OdfDocument from the OpenDocument provided by an ODF package.
* @param odfPackage - the ODF package containing the ODF document.
* @param internalPath - the path to the ODF document relative to the package root, or an empty String for the root document.
* @return the root document of the given OdfPackage
* @throws java.lang.Exception - if the ODF document could not be created.
*/
public static OdfDocument loadDocument(OdfPackage odfPackage, String internalPath) throws Exception {
String documentMediaType = odfPackage.getMediaTypeString(internalPath);
OdfMediaType odfMediaType = null;
try {
odfMediaType = OdfMediaType.getOdfMediaType(documentMediaType);
} catch (IllegalArgumentException e) {
// the returned NULL will be taking care of afterwards
}
if (odfMediaType == null) {
ErrorHandler errorHandler = odfPackage.getErrorHandler();
Matcher matcherCTRL = CONTROL_CHAR_PATTERN.matcher(documentMediaType);
if (matcherCTRL.find()) {
documentMediaType = matcherCTRL.replaceAll(EMPTY_STRING);
}
OdfValidationException ve = new OdfValidationException(OdfSchemaConstraint.DOCUMENT_WITHOUT_ODF_MIMETYPE, internalPath, documentMediaType);
if (errorHandler != null) {
errorHandler.fatalError(ve);
}
throw ve;
}
return newDocument(odfPackage, internalPath, odfMediaType);
}
//return null if the media type can not be recognized.
private static OdfDocument loadDocumentFromTemplate(OdfMediaType odfMediaType) throws Exception {
final Resource documentTemplate;
switch (odfMediaType) {
case TEXT:
case TEXT_TEMPLATE:
case TEXT_MASTER:
case TEXT_WEB:
documentTemplate = OdfTextDocument.EMPTY_TEXT_DOCUMENT_RESOURCE;
break;
case SPREADSHEET:
case SPREADSHEET_TEMPLATE:
documentTemplate = OdfSpreadsheetDocument.EMPTY_SPREADSHEET_DOCUMENT_RESOURCE;
break;
case PRESENTATION:
case PRESENTATION_TEMPLATE:
documentTemplate = OdfPresentationDocument.EMPTY_PRESENTATION_DOCUMENT_RESOURCE;
break;
case GRAPHICS:
case GRAPHICS_TEMPLATE:
documentTemplate = OdfGraphicsDocument.EMPTY_GRAPHICS_DOCUMENT_RESOURCE;
break;
case CHART:
case CHART_TEMPLATE:
documentTemplate = OdfChartDocument.EMPTY_CHART_DOCUMENT_RESOURCE;
break;
case IMAGE:
case IMAGE_TEMPLATE:
documentTemplate = OdfImageDocument.EMPTY_IMAGE_DOCUMENT_RESOURCE;
break;
default:
documentTemplate = null;
throw new IllegalArgumentException("Given mediaType '" + odfMediaType.mMediaType + "' is not yet supported!");
}
return loadTemplate(documentTemplate, odfMediaType);
}
/**
* Creates one of the ODF documents based a given mediatype.
*
* @param odfMediaType The ODF Mediatype of the ODF document to be created.
* @return The ODF document, which mediatype dependends on the parameter or
* NULL if media type were not supported.
*/
private static OdfDocument newDocument(OdfPackage pkg, String internalPath, OdfMediaType odfMediaType) throws SAXException {
OdfDocument newDoc = null;
switch (odfMediaType) {
case TEXT:
newDoc = new OdfTextDocument(pkg, internalPath, OdfTextDocument.OdfMediaType.TEXT);
break;
case TEXT_TEMPLATE:
newDoc = new OdfTextDocument(pkg, internalPath, OdfTextDocument.OdfMediaType.TEXT_TEMPLATE);
break;
case TEXT_MASTER:
newDoc = new OdfTextDocument(pkg, internalPath, OdfTextDocument.OdfMediaType.TEXT_MASTER);
break;
case TEXT_WEB:
newDoc = new OdfTextDocument(pkg, internalPath, OdfTextDocument.OdfMediaType.TEXT_WEB);
break;
case SPREADSHEET:
newDoc = new OdfSpreadsheetDocument(pkg, internalPath, OdfSpreadsheetDocument.OdfMediaType.SPREADSHEET);
break;
case SPREADSHEET_TEMPLATE:
newDoc = new OdfSpreadsheetDocument(pkg, internalPath, OdfSpreadsheetDocument.OdfMediaType.SPREADSHEET_TEMPLATE);
break;
case PRESENTATION:
newDoc = new OdfPresentationDocument(pkg, internalPath, OdfPresentationDocument.OdfMediaType.PRESENTATION);
break;
case PRESENTATION_TEMPLATE:
newDoc = new OdfPresentationDocument(pkg, internalPath, OdfPresentationDocument.OdfMediaType.PRESENTATION_TEMPLATE);
break;
case GRAPHICS:
newDoc = new OdfGraphicsDocument(pkg, internalPath, OdfGraphicsDocument.OdfMediaType.GRAPHICS);
break;
case GRAPHICS_TEMPLATE:
newDoc = new OdfGraphicsDocument(pkg, internalPath, OdfGraphicsDocument.OdfMediaType.GRAPHICS_TEMPLATE);
break;
case CHART:
newDoc = new OdfChartDocument(pkg, internalPath, OdfChartDocument.OdfMediaType.CHART);
break;
case CHART_TEMPLATE:
newDoc = new OdfChartDocument(pkg, internalPath, OdfChartDocument.OdfMediaType.CHART_TEMPLATE);
break;
case IMAGE:
newDoc = new OdfImageDocument(pkg, internalPath, OdfImageDocument.OdfMediaType.IMAGE);
break;
case IMAGE_TEMPLATE:
newDoc = new OdfImageDocument(pkg, internalPath, OdfImageDocument.OdfMediaType.IMAGE_TEMPLATE);
break;
default:
newDoc = null;
throw new IllegalArgumentException("Given mediaType '" + odfMediaType.mMediaType + "' is not yet supported!");
}
// returning null if MediaType is not supported
return newDoc;
}
/**
* Returns an embedded OdfPackageDocument from the given package path.
*
* @param documentPath to the ODF document within the package. The path is relative the current ODF document path.
* @return an embedded OdfPackageDocument
*/
@Override
public OdfDocument loadSubDocument(String documentPath) {
return (OdfDocument) super.loadSubDocument(documentPath);
}
/**
* Method returns all embedded OdfPackageDocuments, which match a valid OdfMediaType,
* of the current OdfPackageDocument. Note: The root document is not part of the returned collection.
* @return a map with all embedded documents and their paths of the current OdfPackageDocument
*/
public Map<String, OdfDocument> loadSubDocuments() {
return loadSubDocuments(null);
}
/**
* Method returns all embedded OdfPackageDocuments of sthe current OdfPackageDocument matching the
* according MediaType. This is done by matching the subfolder entries of the
* manifest file with the given OdfMediaType.
* @param desiredMediaType media type of the documents to be returned (used as a filter).
* @return embedded documents of the current OdfPackageDocument matching the given media type
*/
public Map<String, OdfDocument> loadSubDocuments(OdfMediaType desiredMediaType) {
String wantedMediaString = null;
if (desiredMediaType != null) {
wantedMediaString = desiredMediaType.getMediaTypeString();
}
Map<String, OdfDocument> embeddedObjectsMap = new HashMap<String, OdfDocument>();
// check manifest for current embedded OdfPackageDocuments
Set<String> manifestEntries = mPackage.getFilePaths();
for (String path : manifestEntries) {
// any directory that is not the root document "/"
if (path.length() > 1 && path.endsWith(SLASH)) {
String entryMediaType = mPackage.getFileEntry(path).getMediaTypeString();
// if the entry is a document (directory has mediaType)
if (entryMediaType != null) {
// if a specific ODF mediatype was requested
if (wantedMediaString != null) {
// test if the desired mediatype matches the current
if (entryMediaType.equals(wantedMediaString)) {
path = normalizeDocumentPath(path);
embeddedObjectsMap.put(path, (OdfDocument) mPackage.loadDocument(path));
}
} else {
// test if any ODF mediatype matches the current
for (OdfMediaType mediaType : OdfMediaType.values()) {
if (entryMediaType.equals(mediaType.getMediaTypeString())) {
embeddedObjectsMap.put(path, (OdfDocument) mPackage.loadDocument(path));
}
}
}
}
}
}
return embeddedObjectsMap;
}
/**
* Sets the media type of the OdfDocument
* @param odfMediaType media type to be set
*/
protected void setOdfMediaType(OdfMediaType odfMediaType) {
mMediaType = odfMediaType;
super.setMediaTypeString(odfMediaType.getMediaTypeString());
}
/**
* Gets the media type of the OdfDocument
*/
protected OdfMediaType getOdfMediaType() {
return mMediaType;
}
/**
* Get the meta data feature instance of the current document
*
* @return the meta data feature instance which represent
* <code>office:meta</code> in the meta.xml
*/
public OdfOfficeMeta getOfficeMetadata() {
if (mOfficeMeta == null) {
try {
OdfMetaDom metaDom = getMetaDom();
if (metaDom == null) {
metaDom = new OdfMetaDom(this, OdfSchemaDocument.OdfXMLFile.META.getFileName());
}
mOfficeMeta = new OdfOfficeMeta(metaDom);
} catch (Exception ex) {
Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE, null, ex);
}
}
return mOfficeMeta;
}
/**
* Save the document to an OutputStream. Delegate to the root document
* and save possible embedded OdfDocuments.
*
* <p>If the input file has been cached (this is the case when loading from an
* InputStream), the input file can be overwritten.</p>
*
* <p>If not, the OutputStream may not point to the input file! Otherwise
* this will result in unwanted behaviour and broken files.</p>
*
* <p>When save the embedded document to a stand alone document,
* all the file entries of the embedded document will be copied to a new document package.
* If the embedded document is outside of the current document directory,
* you have to embed it to the sub directory and refresh the link of the embedded document.
* you should reload it from the stream to get the saved embedded document.
*
* @param out - the OutputStream to write the file to
* @throws java.lang.Exception if the document could not be saved
*/
public void save(OutputStream out) throws Exception {
updateMetaData();
if (!isRootDocument()) {
OdfDocument newDoc = loadDocumentFromTemplate(getOdfMediaType());
newDoc.insertDocument(this, ROOT_DOCUMENT_PATH);
newDoc.updateMetaData();
newDoc.mPackage.save(out);
// ToDo: (Issue 219 - PackageRefactoring) - Return the document, when not closing!
// Should we close the sources now? User will never receive the open package!
} else {
mPackage.save(out);
}
}
/**
* Save the document to a given file.
*
* <p>If the input file has been cached (this is the case when loading from an
* InputStream), the input file can be overwritten.</p>
*
* <p>Otherwise it's allowed to overwrite the input file as long as
* the same path name is used that was used for loading (no symbolic link
* foo2.odt pointing to the loaded file foo1.odt, no network path X:\foo.odt
* pointing to the loaded file D:\foo.odt).</p>
*
* <p>When saving the embedded document to a stand alone document,
* all files of the embedded document will be copied to a new document package.
* If the embedded document is outside of the current document directory,
* you have to embed it to the sub directory and refresh the link of the embedded document.
* You should reload it from the given file to get the saved embedded document.
*
* @param file - the file to save the document
* @throws java.lang.Exception if the document could not be saved
*/
@Override
public void save(File file) throws Exception {
updateMetaData();
if (!isRootDocument()) {
OdfDocument newDoc = loadDocumentFromTemplate(getOdfMediaType());
newDoc.insertDocument(this, ROOT_DOCUMENT_PATH);
newDoc.updateMetaData();
newDoc.mPackage.save(file);
// ToDo: (Issue 219 - PackageRefactoring) - Return the document, when not closing!
// Should we close the sources now? User will never receive the open package!
} else {
this.mPackage.save(file);
}
}
/**
* Close the OdfPackage and release all temporary created data.
* Acter execution of this method, this class is no longer usable.
* Do this as the last action to free resources.
* Closing an already closed document has no effect.
*/
@Override
public void close() {
// set all member variables explicit to null
mMediaType = null;
mOfficeMeta = null;
super.close();
}
/**
* Get the content root of a document.
*
* You may prefer to use the getContentRoot methods of subclasses of
* OdfDocument. Their return parameters are already casted to
* respective subclasses of OdfElement.
*
* @param the type of the content root, depend on the document type
* @return the child element of office:body, e.g. office:text for text docs
* @throws Exception if the file DOM could not be created.
*/
@SuppressWarnings("unchecked")
<T extends OdfElement> T getContentRoot(Class<T> clazz) throws Exception {
OdfElement contentRoot = getContentDom().getRootElement();
OfficeBodyElement contentBody = OdfElement.findFirstChildNode(
OfficeBodyElement.class, contentRoot);
NodeList childs = contentBody.getChildNodes();
for (int i = 0;
i < childs.getLength();
i++) {
Node cur = childs.item(i);
if ((cur != null) && clazz.isInstance(cur)) {
return (T) cur;
}
}
return null;
}
/**
* Get the content root of a document.
*
* You may prefer to use the getContentRoot methods of subclasses of
* OdfDocument.
*
* @return the child element of office:body, e.g. office:text for text docs
* @throws Exception if the file DOM could not be created.
*/
public OdfElement getContentRoot() throws Exception {
return getContentRoot(OdfElement.class);
}
@Override
public String toString() {
return "\n" + getMediaTypeString() + " - ID: " + this.hashCode() + " " + getPackage().getBaseURI();
}
/**
* Insert an Image from the specified uri to the end of the OdfDocument.
* @param imageUri The URI of the image that will be added to the document,
* add image stream to the package,
* in the 'Pictures/' graphic directory with the same image file name as in the URI.
* If the imageURI is relative first the user.dir is taken to make it absolute.
* @return Returns the internal package path of the image, which was created based on the given URI.
* */
public String newImage(URI imageUri) {
try {
OdfContentDom contentDom = this.getContentDom();
OdfDrawFrame drawFrame = contentDom.newOdfElement(OdfDrawFrame.class);
XPath xpath = contentDom.getXPath();
if (this instanceof OdfSpreadsheetDocument) {
TableTableCellElement lastCell = (TableTableCellElement) xpath.evaluate("//table:table-cell[last()]", contentDom, XPathConstants.NODE);
lastCell.appendChild(drawFrame);
drawFrame.removeAttribute("text:anchor-type");
} else if (this instanceof OdfTextDocument) {
TextPElement lastPara = (TextPElement) xpath.evaluate("//text:p[last()]", contentDom, XPathConstants.NODE);
if (lastPara == null) {
lastPara = ((OdfTextDocument) this).newParagraph();
}
lastPara.appendChild(drawFrame);
drawFrame.setTextAnchorTypeAttribute(TextAnchorTypeAttribute.Value.PARAGRAPH.toString());
} else if (this instanceof OdfPresentationDocument) {
DrawPageElement lastPage = (DrawPageElement) xpath.evaluate("//draw:page[last()]", contentDom, XPathConstants.NODE);
lastPage.appendChild(drawFrame);
}
OdfDrawImage image = (OdfDrawImage) drawFrame.newDrawImageElement();
String imagePath = image.newImage(imageUri);
return imagePath;
} catch (Exception ex) {
Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
/**
* Return an instance of table feature with the specific table name.
* @param name of the table beeing searched for.
* @return an instance of table feature with the specific table name.
*/
public OdfTable getTableByName(String name) {
try {
OdfElement root = getContentDom().getRootElement();
OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root);
OdfElement typedContent = OdfElement.findFirstChildNode(OdfElement.class, officeBody);
NodeList childList = typedContent.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
if (childList.item(i) instanceof TableTableElement) {
TableTableElement table = (TableTableElement) childList.item(i);
if (table.getOdfAttributeValue(OdfName.newName(OdfDocumentNamespace.TABLE, "name")).equals(name)) {
return OdfTable.getInstance(table);
}
}
}
} catch (Exception e) {
Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE, null, e);
}
return null;
}
/**
* Return a list of table features in this document.
* @return a list of table features in this document.
*/
public List<OdfTable> getTableList() {
List<OdfTable> tableList = null;
try {
List<TableTableElement> tableElementList = getTables();
tableList = new ArrayList<OdfTable>(tableElementList.size());
for (int i = 0;
i < tableElementList.size();
i++) {
tableList.add(OdfTable.getInstance(tableElementList.get(i)));
}
} catch (Exception e) {
Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE, null, e);
}
return tableList;
}
/**
* Update document meta data in the ODF document. Following metadata data is
* being updated:
* <ul>
* <li>The name of the person who last modified this document will be the Java user.name System property</li>
* <li>The date and time when the document was last modified using current data</li>
* <li>The number of times this document has been edited is incremented by 1</li>
* <li>The total time spent editing this document</li>
* </ul>
*
* TODO:This method will be moved to OdfMetadata class.
* see http://odftoolkit.org/bugzilla/show_bug.cgi?id=204
* @throws Exception
* @throws IllegalArgumentException
*/
private void updateMetaData() throws IllegalArgumentException, Exception {
if (getOfficeMetadata().hasAutomaticUpdate()) {
OdfOfficeMeta metaData = getOfficeMetadata();
// set creation date´
if (mCreationDate != null) {
getOfficeMetadata().setCreationDate(mCreationDate);
}
// update late modfied date
Calendar calendar = Calendar.getInstance();
metaData.setDate(calendar);
// update editing-cycles
Integer cycle = metaData.getEditingCycles();
if (cycle != null) {
metaData.setEditingCycles(++cycle);
} else {
metaData.setEditingCycles(1);
}
// update editing-duration
long editingDuration = calendar.getTimeInMillis() - documentOpeningTime;
editingDuration = (editingDuration < 1) ? 1 : editingDuration;
try {
DatatypeFactory aFactory = DatatypeFactory.newInstance();
metaData.setEditingDuration(new Duration(aFactory.newDurationDayTime(editingDuration)));
} catch (DatatypeConfigurationException e) {
Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE,
"editing duration update fail as DatatypeFactory can not be instanced", e);
}
}
}
// /////////////////
// Following is the implementation of locale settings
// ////////////////
/**
* <p>
* Unicode characters are in general divided by office applications into
* three different groups.
*
* <p>
* 1) There is CJK: the Chinese, Japanese and Korean script (also old
* Vietnamese belong to this group). See
* http://en.wikipedia.org/wiki/CJK_characters
*
* <p>
* 2) There is CTL: Complex Text Layout, which uses BIDI algorithms and/or
* glyph modules for instance Arabic, Hebrew, Indic and Thai. See http://en.wikipedia.org/wiki/Complex_Text_Layout
*
* <p>
* 3) And there is all the rest, which was once called by MS Western.
*/
public enum UnicodeGroup {
/**
* Western language
*/
WESTERN,
/**
* Chinese, Japanese and Korean
*/
CJK,
/**
* Complex Text Layout language
*/
CTL;
}
private final static HashSet<String> CJKLanguage = new HashSet<String>();
private final static HashSet<String> CTLLanguage = new HashSet<String>();
{
CJKLanguage.add("zh"); // LANGUAGE_CHINES
CJKLanguage.add("ja"); // LANGUAGE_JAPANESE
CJKLanguage.add("ko"); // LANGUAGE_KOREANE
CTLLanguage.add("am"); // LANGUAGE_AMHARIC_ETHIOPIA
CTLLanguage.add("ar"); // LANGUAGE_ARABIC_SAUDI_ARABIA
CTLLanguage.add("as"); // LANGUAGE_ASSAMESE
CTLLanguage.add("bn"); // LANGUAGE_BENGALI
CTLLanguage.add("bo"); // LANGUAGE_TIBETAN
CTLLanguage.add("brx");// LANGUAGE_USER_BODO_INDIA
CTLLanguage.add("dgo");// LANGUAGE_USER_DOGRI_INDIA
CTLLanguage.add("dv"); // LANGUAGE_DHIVEHI
CTLLanguage.add("dz"); // LANGUAGE_DZONGKHA
CTLLanguage.add("fa"); // LANGUAGE_FARSI
CTLLanguage.add("gu"); // LANGUAGE_GUJARATI
CTLLanguage.add("he"); // LANGUAGE_HEBREW
CTLLanguage.add("hi"); // LANGUAGE_HINDI
CTLLanguage.add("km"); // LANGUAGE_KHMER
CTLLanguage.add("kn"); // LANGUAGE_KANNADA
CTLLanguage.add("ks"); // LANGUAGE_KASHMIRI
CTLLanguage.add("ku"); // LANGUAGE_USER_KURDISH_IRAQ
CTLLanguage.add("lo"); // LANGUAGE_LAO
CTLLanguage.add("mai");// LANGUAGE_USER_MAITHILI_INDIA
CTLLanguage.add("ml"); // LANGUAGE_MALAYALAM
CTLLanguage.add("mn"); // LANGUAGE_MONGOLIAN_MONGOLIAN
CTLLanguage.add("mni");// LANGUAGE_MANIPURI
CTLLanguage.add("mr"); // LANGUAGE_MARATHI
CTLLanguage.add("my"); // LANGUAGE_BURMESE
CTLLanguage.add("ne"); // LANGUAGE_NEPALI
CTLLanguage.add("or"); // LANGUAGE_ORIYA
CTLLanguage.add("pa"); // LANGUAGE_PUNJABI
CTLLanguage.add("sa"); // LANGUAGE_SANSKRIT
CTLLanguage.add("sd"); // LANGUAGE_SINDHI
CTLLanguage.add("si"); // LANGUAGE_SINHALESE_SRI_LANKA
CTLLanguage.add("syr");// LANGUAGE_SYRIAC
CTLLanguage.add("ta"); // LANGUAGE_TAMIL
CTLLanguage.add("te"); // LANGUAGE_TELUGU
CTLLanguage.add("th"); // LANGUAGE_THAI
CTLLanguage.add("ug"); // LANGUAGE_UIGHUR_CHINA
CTLLanguage.add("ur"); // LANGUAGE_URDU
CTLLanguage.add("yi"); // LANGUAGE_YIDDISH
}
/**
* <p>
* Set a locale information.
* <p>
* The locale information will affect the language and country setting of
* the document. Thus the font settings, the spell checkings and etc will be
* affected.
*
* @param locale
* - an instance of Locale
*/
public void setLocale(Locale locale) {
setLocale(locale, getUnicodeGroup(locale));
}
/**
* This method will return Locale, which presents the default
* language and country information settings in this document.
*
* @return an instance of Locale that the default language and country is
* set to.
*/
/**
* Similar to OpenOffice.org, ODFDOM assumes that every Locale is related
* to one of the three Unicodes Groups, either CJK, CTL or Western.
* @param locale the UnicodeGroup is requested for
* @return the related UnicodeGroup
*/
public static UnicodeGroup getUnicodeGroup(Locale locale) {
String language = locale.getLanguage();
if (CJKLanguage.contains(language)) {
return UnicodeGroup.CJK;
}
if (CTLLanguage.contains(language)) {
return UnicodeGroup.CTL;
}
return UnicodeGroup.WESTERN;
}
/**
* <p>
* Set a locale of a specific script type.
* <p>
* If the locale does not belong to the script type, it will not be set.
*
* @param locale
* - Locale information
* @param unicodeGroup
* - The script type
*/
private void setLocale(Locale locale, UnicodeGroup unicodeGroup) {
try {
switch (unicodeGroup) {
case WESTERN:
setDefaultWesternLanguage(locale);
break;
case CJK:
setDefaultAsianLanguage(locale);
break;
case CTL:
setDefaultComplexLanguage(locale);
break;
}
} catch (Exception e) {
Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE,
"Failed to set locale", e);
}
}
/**
* This method will return Locale, which presents the default
* language and country information settings in this document
* <p>
* ODF allows to set a Locale for each of the three UnicodeGroups.
* Therefore there might be three different Locale for the document.
*
* @param unicodeGroup
* - One of the three (CJK, CTL or Western).
* @return the Locale for the given UnicodeGroup
*/
public Locale getLocale(UnicodeGroup unicodeGroup) {
try {
switch (unicodeGroup) {
case WESTERN:
return getDefaultLanguageByProperty(OdfTextProperties.Country,
OdfTextProperties.Language);
case CJK:
return getDefaultLanguageByProperty(
OdfTextProperties.CountryAsian,
OdfTextProperties.LanguageAsian);
case CTL:
return getDefaultLanguageByProperty(
OdfTextProperties.CountryComplex,
OdfTextProperties.LanguageComplex);
}
} catch (Exception e) {
Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE,
"Failed to get locale", e);
}
return null;
}
/** Returns the current Locale for the OdfStyleProperty of the corresponding UnicodeGroup */
private Locale getDefaultLanguageByProperty(OdfStyleProperty countryProp,
OdfStyleProperty languageProp) throws Exception {
String lang = null, ctry = null;
OdfOfficeStyles styles = getStylesDom().getOfficeStyles();
// get language and country setting from default style setting for
// paragraph
OdfDefaultStyle defaultStyle = styles.getDefaultStyle(OdfStyleFamily.Paragraph);
if (defaultStyle != null) {
if (defaultStyle.hasProperty(countryProp) && defaultStyle.hasProperty(languageProp)) {
ctry = defaultStyle.getProperty(countryProp);
lang = defaultStyle.getProperty(languageProp);
return new Locale(lang, ctry);
}
}
// if no default style setting for paragraph
// get language and country setting from other default style settings
Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles();
Iterator<OdfDefaultStyle> itera = defaultStyles.iterator();
while (itera.hasNext()) {
OdfDefaultStyle style = itera.next();
if (style.hasProperty(countryProp) && style.hasProperty(languageProp)) {
ctry = style.getProperty(countryProp);
lang = style.getProperty(languageProp);
return new Locale(lang, ctry);
}
}
return null;
}
/**
* This method will set the default language and country information of the
* document, based on the parameter of the Locale information.
*
* @param locale
* - an instance of Locale that the default language and country
* will be set to.
* @throws Exception
*/
private void setDefaultWesternLanguage(Locale locale) throws Exception {
OdfOfficeStyles styles = getStylesDom().getOfficeStyles();
Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles();
if (defaultStyles != null) {
Iterator<OdfDefaultStyle> itera = defaultStyles.iterator();
while (itera.hasNext()) {
OdfDefaultStyle style = itera.next();
if (style.getFamily().getProperties().contains(
OdfTextProperties.Language)) {
style.setProperty(OdfTextProperties.Language, locale.getLanguage());
style.setProperty(OdfTextProperties.Country, locale.getCountry());
}
}
}
}
/**
* This method will set the default Asian language and country information
* of the document, based on the parameter of the Locale information. If the
* Locale instance is not set a Asian language (Chinese, Traditional
* Chinese, Japanese and Korean, nothing will take effect.
*
* @param locale
* - an instance of Locale that the default Asian language and
* country will be set to.
* @throws Exception
*/
private void setDefaultAsianLanguage(Locale locale) throws Exception {
OdfOfficeStyles styles = getStylesDom().getOfficeStyles();
Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles();
if (defaultStyles != null) {
Iterator<OdfDefaultStyle> itera = defaultStyles.iterator();
while (itera.hasNext()) {
OdfDefaultStyle style = itera.next();
if (style.getFamily().getProperties().contains(
OdfTextProperties.LanguageAsian)) {
style.setProperty(OdfTextProperties.LanguageAsian, locale.getLanguage());
style.setProperty(OdfTextProperties.CountryAsian, locale.getCountry());
}
}
}
}
/**
* This method will set the default complex language and country information
* of the document, based on the parameter of the Locale information.
*
* @param locale
* - an instance of Locale that the default complex language and
* country will be set to.
* @throws Exception
*/
private void setDefaultComplexLanguage(Locale locale) throws Exception {
OdfOfficeStyles styles = getStylesDom().getOfficeStyles();
Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles();
if (defaultStyles != null) {
Iterator<OdfDefaultStyle> itera = defaultStyles.iterator();
while (itera.hasNext()) {
OdfDefaultStyle style = itera.next();
if (style.getFamily().getProperties().contains(
OdfTextProperties.LanguageComplex)) {
style.setProperty(OdfTextProperties.LanguageComplex, locale.getLanguage());
style.setProperty(OdfTextProperties.CountryComplex, locale.getCountry());
}
}
}
}
}