Patch by Nimarukan - PERFORMANCE: simple Document.getCopyStyleList improvements to reduce String allocation, hashing, etc.
git-svn-id: https://svn.apache.org/repos/asf/incubator/odf/trunk@1738440 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/simple/src/main/java/org/odftoolkit/simple/Document.java b/simple/src/main/java/org/odftoolkit/simple/Document.java
index 49e3534..6a0e74f 100644
--- a/simple/src/main/java/org/odftoolkit/simple/Document.java
+++ b/simple/src/main/java/org/odftoolkit/simple/Document.java
@@ -1,2674 +1,2669 @@
-/************************************************************************
- *
- * 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.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-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.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import javax.xml.datatype.DatatypeConfigurationException;
-import javax.xml.datatype.DatatypeFactory;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import org.odftoolkit.odfdom.dom.OdfContentDom;
-import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
-import org.odftoolkit.odfdom.dom.OdfSchemaConstraint;
-import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
-import org.odftoolkit.odfdom.dom.OdfStylesDom;
-import org.odftoolkit.odfdom.dom.attribute.text.TextAnchorTypeAttribute;
-import org.odftoolkit.odfdom.dom.element.OdfStyleBase;
-import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement;
-import org.odftoolkit.odfdom.dom.element.draw.DrawPageElement;
-import org.odftoolkit.odfdom.dom.element.office.OfficeBodyElement;
-import org.odftoolkit.odfdom.dom.element.office.OfficeMasterStylesElement;
-import org.odftoolkit.odfdom.dom.element.style.StyleFontFaceElement;
-import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
-import org.odftoolkit.odfdom.dom.element.table.TableTableCellElementBase;
-import org.odftoolkit.odfdom.dom.element.table.TableTableTemplateElement;
-import org.odftoolkit.odfdom.dom.element.text.TextPElement;
-import org.odftoolkit.odfdom.dom.element.text.TextSectionElement;
-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.OdfOfficeAutomaticStyles;
-import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
-import org.odftoolkit.odfdom.incubator.doc.style.OdfDefaultStyle;
-import org.odftoolkit.odfdom.pkg.MediaType;
-import org.odftoolkit.odfdom.pkg.OdfElement;
-import org.odftoolkit.odfdom.pkg.OdfFileDom;
-import org.odftoolkit.odfdom.pkg.OdfName;
-import org.odftoolkit.odfdom.pkg.OdfNamespace;
-import org.odftoolkit.odfdom.pkg.OdfPackage;
-import org.odftoolkit.odfdom.pkg.OdfPackageDocument;
-import org.odftoolkit.odfdom.pkg.OdfValidationException;
-import org.odftoolkit.odfdom.pkg.manifest.OdfFileEntry;
-import org.odftoolkit.odfdom.type.Duration;
-import org.odftoolkit.simple.meta.Meta;
-import org.odftoolkit.simple.table.AbstractTableContainer;
-import org.odftoolkit.simple.table.Cell;
-import org.odftoolkit.simple.table.Table;
-import org.odftoolkit.simple.table.Table.TableBuilder;
-import org.odftoolkit.simple.table.TableContainer;
-import org.odftoolkit.simple.table.TableTemplate;
-import org.odftoolkit.simple.text.Paragraph;
-import org.odftoolkit.simple.text.Section;
-import org.w3c.dom.Attr;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.ErrorHandler;
-
-/**
- * This abstract class is representing one of the possible ODF documents
- */
-public abstract class Document extends OdfSchemaDocument implements TableContainer {
- // Static parts of file references
- private static final String SLASH = "/";
- private OdfMediaType mMediaType;
- private Meta mOfficeMeta;
- private long documentOpeningTime;
- private TableContainerImpl tableContainerImpl;
- private static final Pattern CONTROL_CHAR_PATTERN = Pattern.compile("\\p{Cntrl}");
- private static final String EMPTY_STRING = "";
-
- private IdentityHashMap<OdfElement, Component> mComponentRepository = new IdentityHashMap<OdfElement, Component>();
-
- // FIXME: This field is only used in method copyResourcesFrom to improve
- // copy performance, should not be used in any other way.
- // methods loadDocument(String documentPath) and loadDocument(File file)
- // will initialize it.
- // This field and its methods should be removed after ODFDOM supplies batch
- // copy.
- private File mFile = null;
-
- // if the copy foreign slide for several times,
- // the same style might be copied for several times with the different name
- // so use styleRenameMap to keep track the renamed style so we can reuse the
- // style,
- // rather than new several styles which only have the different style names.
- // while if the style elements really have the same style name but with
- // different content
- // such as that these style elements are from different document
- // so the value for each key should be a list
- private HashMap<String, List<String>> styleRenameMap = new HashMap<String, List<String>>();
- // the map is used to record if the renamed style name is appended to the
- // current dom
- private HashMap<String, Boolean> styleAppendMap = new HashMap<String, Boolean>();
-
- // the object rename map for image.
- // can not easily recognize if the embedded document are the same.
- // private HashMap<String, String> objectRenameMap = new HashMap<String,
- // String>();
-
- // Using static factory instead of constructor
- protected Document(OdfPackage pkg, String internalPath, OdfMediaType mediaType) {
- super(pkg, internalPath, mediaType.getMediaTypeString());
- mMediaType = mediaType;
- // set document opening time.
- documentOpeningTime = System.currentTimeMillis();
- }
-
- /**
- * This enum contains all possible media types of Document 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 either not yet supported or not an ODF mediatype!");
- }
- }
- return odfMediaType;
- }
- }
-
- /**
- * Loads an Document from the given resource. NOTE: Initial meta data 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 Document document or NULL if the media type is not supported
- * by SIMPLE.
- * @throws java.lang.Exception
- * - if the document could not be created.
- */
- protected static Document loadTemplate(Resource res, OdfMediaType odfMediaType) throws Exception {
- InputStream in = res.createInputStream();
- OdfPackage pkg = null;
- try {
- pkg = OdfPackage.loadPackage(in);
- } finally {
- in.close();
- }
- Document newDocument = newDocument(pkg, ROOT_DOCUMENT_PATH, odfMediaType);
- // add initial meta data to new document.
- initializeMetaData(newDocument);
- return newDocument;
- }
-
- /**
- * Loads a Document from the provided path.
- *
- * <p>
- * Document relies on the file being available for read access over the
- * whole life cycle of Document.
- * </p>
- *
- * @param documentPath
- * - the path from where the document can be loaded
- * @param password
- * - file password.
- * @return the 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.
- * @since 0.8
- */
- public static Document loadDocument(String documentPath, String password) throws Exception {
- File file = new File(documentPath);
- return loadDocument(file, password);
- }
-
- /**
- * Loads a Document from the provided path.
- *
- * <p>
- * Document relies on the file being available for read access over the
- * whole life cycle of Document.
- * </p>
- *
- * @param documentPath
- * - the path from where the document can be loaded
- * @return the 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 Document loadDocument(String documentPath) throws Exception {
- File file = new File(documentPath);
- return loadDocument(file);
- }
-
- /**
- * Creates a Document from the Document provided by a resource Stream.
- *
- * <p>
- * Since an InputStream does not provide the arbitrary (non sequentiell)
- * read access needed by Document, 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 Document loadDocument(InputStream inStream) throws Exception {
- return loadDocument(OdfPackage.loadPackage(inStream));
- }
-
- /**
- * Creates a Document from the Document provided by a File.
- *
- * <p>
- * Document relies on the file being available for read access over the
- * whole lifecycle of Document.
- * </p>
- *
- * @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 Document loadDocument(File file) throws Exception {
- Document doc = loadDocument(OdfPackage.loadPackage(file));
- doc.setFile(file);
- return doc;
- }
-
- /**
- * Creates a Document from the Document provided by a File.
- *
- * <p>
- * Document relies on the file being available for read access over the
- * whole lifecycle of Document.
- * </p>
- *
- * @param file
- * - a file representing the ODF document.
- * @param password
- * - file password.
- * @return the document created from the given File
- * @throws java.lang.Exception
- * - if the document could not be created.
- * @since 0.7
- */
- public static Document loadDocument(File file, String password) throws Exception {
- Document doc = loadDocument(OdfPackage.loadPackage(file, password, null));
- doc.setFile(file);
- return doc;
- }
-
- /**
- * Creates a Document from the Document provided by an 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 Document loadDocument(OdfPackage odfPackage) throws Exception {
- return loadDocument(odfPackage, ROOT_DOCUMENT_PATH);
- }
-
- /**
- * Creates a Document from the Document 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.
- * @return the root document of the given OdfPackage
- * @throws java.lang.Exception
- * - if the ODF document could not be created.
- */
- public static Document loadDocument(OdfPackage odfPackage, String internalPath) throws Exception {
- String documentMediaType = odfPackage.getMediaTypeString(internalPath);
- if (documentMediaType == null) {
- throw new IllegalArgumentException("Given internalPath '" + internalPath + "' is an illegal or inappropriate argument.");
- }
- OdfMediaType odfMediaType = OdfMediaType.getOdfMediaType(documentMediaType);
- 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);
- }
-
- /**
- * Sets password of this document.
- *
- * @param password
- * the password of this document.
- * @since 0.8
- */
- public void setPassword(String password) {
- getPackage().setPassword(password);
- }
-
- // return null if the media type can not be recognized.
- private static Document loadDocumentFromTemplate(OdfMediaType odfMediaType) throws Exception {
-
- switch (odfMediaType) {
- case TEXT:
- case TEXT_TEMPLATE:
- case TEXT_MASTER:
- case TEXT_WEB:
- // documentTemplate = TextDocument.EMPTY_TEXT_DOCUMENT_RESOURCE;
- TextDocument document = TextDocument.newTextDocument();
- document.changeMode(TextDocument.OdfMediaType.TEXT_WEB);
- return document;
-
- case SPREADSHEET:
- SpreadsheetDocument spreadsheet = SpreadsheetDocument.newSpreadsheetDocument();
- spreadsheet.changeMode(SpreadsheetDocument.OdfMediaType.SPREADSHEET);
- return spreadsheet;
-
- case SPREADSHEET_TEMPLATE:
- SpreadsheetDocument spreadsheettemplate = SpreadsheetDocument.newSpreadsheetDocument();
- spreadsheettemplate.changeMode(SpreadsheetDocument.OdfMediaType.SPREADSHEET_TEMPLATE);
- return spreadsheettemplate;
-
- case PRESENTATION:
- PresentationDocument presentation = PresentationDocument.newPresentationDocument();
- presentation.changeMode(PresentationDocument.OdfMediaType.PRESENTATION);
- return presentation;
-
- case PRESENTATION_TEMPLATE:
- PresentationDocument presentationtemplate = PresentationDocument.newPresentationDocument();
- presentationtemplate.changeMode(PresentationDocument.OdfMediaType.PRESENTATION_TEMPLATE);
- return presentationtemplate;
-
- case GRAPHICS:
- GraphicsDocument graphics = GraphicsDocument.newGraphicsDocument();
- graphics.changeMode(GraphicsDocument.OdfMediaType.GRAPHICS);
- return graphics;
-
- case GRAPHICS_TEMPLATE:
- GraphicsDocument graphicstemplate = GraphicsDocument.newGraphicsDocument();
- graphicstemplate.changeMode(GraphicsDocument.OdfMediaType.GRAPHICS_TEMPLATE);
- return graphicstemplate;
-
- case CHART:
- ChartDocument chart = ChartDocument.newChartDocument();
- chart.changeMode(ChartDocument.OdfMediaType.CHART);
- return chart;
-
- case CHART_TEMPLATE:
- ChartDocument charttemplate = ChartDocument.newChartDocument();
- charttemplate.changeMode(ChartDocument.OdfMediaType.CHART_TEMPLATE);
- return charttemplate;
-
- // case IMAGE:
- // case IMAGE_TEMPLATE:
-
- default:
- throw new IllegalArgumentException("Given mediaType '" + odfMediaType.toString() + "' is either not yet supported or not an ODF mediatype!");
- }
- }
-
- /**
- * 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 Document newDocument(OdfPackage pkg, String internalPath, OdfMediaType odfMediaType) {
- Document newDoc = null;
- switch (odfMediaType) {
- case TEXT:
- newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT);
- break;
-
- case TEXT_TEMPLATE:
- newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_TEMPLATE);
- break;
-
- case TEXT_MASTER:
- newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_MASTER);
- break;
-
- case TEXT_WEB:
- newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_WEB);
- break;
-
- case SPREADSHEET:
- newDoc = new SpreadsheetDocument(pkg, internalPath, SpreadsheetDocument.OdfMediaType.SPREADSHEET);
- break;
-
- case SPREADSHEET_TEMPLATE:
- newDoc = new SpreadsheetDocument(pkg, internalPath, SpreadsheetDocument.OdfMediaType.SPREADSHEET_TEMPLATE);
- break;
-
- case PRESENTATION:
- newDoc = new PresentationDocument(pkg, internalPath, PresentationDocument.OdfMediaType.PRESENTATION);
- break;
-
- case PRESENTATION_TEMPLATE:
- newDoc = new PresentationDocument(pkg, internalPath, PresentationDocument.OdfMediaType.PRESENTATION_TEMPLATE);
- break;
-
- case GRAPHICS:
- newDoc = new GraphicsDocument(pkg, internalPath, GraphicsDocument.OdfMediaType.GRAPHICS);
- break;
-
- case GRAPHICS_TEMPLATE:
- newDoc = new GraphicsDocument(pkg, internalPath, GraphicsDocument.OdfMediaType.GRAPHICS_TEMPLATE);
- break;
-
- case CHART:
- newDoc = new ChartDocument(pkg, internalPath, ChartDocument.OdfMediaType.CHART);
- break;
-
- case CHART_TEMPLATE:
- newDoc = new ChartDocument(pkg, internalPath, ChartDocument.OdfMediaType.CHART_TEMPLATE);
- break;
- // case IMAGE:
- // case IMAGE_TEMPLATE:
-
- 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
- * path to the ODF document within the package. The path is
- * relative to the current document.
- * @return an embedded Document
- */
- public Document getEmbeddedDocument(String documentPath) {
- String internalPath = getDocumentPath() + documentPath;
- internalPath = normalizeDocumentPath(internalPath);
- Document embeddedDocument = (Document) mPackage.getCachedDocument(internalPath);
- // if the document was not already loaded, fine mimetype and create a
- // new instance
- if (embeddedDocument == null) {
- String mediaTypeString = getMediaTypeString();
- OdfMediaType odfMediaType = OdfMediaType.getOdfMediaType(mediaTypeString);
- if (odfMediaType == null) {
- embeddedDocument = newDocument(mPackage, internalPath, odfMediaType);
- } else {
- try {
- String documentMediaType = mPackage.getMediaTypeString(internalPath);
- odfMediaType = OdfMediaType.getOdfMediaType(documentMediaType);
- if (odfMediaType == null) {
- return null;
- }
- embeddedDocument = Document.loadDocument(mPackage, internalPath);
- } catch (Exception ex) {
- Logger.getLogger(OdfPackageDocument.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
- }
- return embeddedDocument;
- }
-
- /**
- * Method returns all embedded OdfPackageDocuments, which match a valid
- * OdfMediaType, of the root OdfPackageDocument.
- *
- * @return a list with all embedded documents of the root OdfPackageDocument
- */
- // ToDo: (Issue 219 - PackageRefactoring) - Better return Path of
- // Documents??
- public List<Document> getEmbeddedDocuments() {
- List<Document> embeddedObjects = new ArrayList<Document>();
- // ToDo: (Issue 219 - PackageRefactoring) - Algorithm enhancement:
- // Instead going through all the files for each mimetype, better
- // Check all files, which have a mimetype if it is one of the desired,
- // perhaps start with ODF prefix
- for (OdfMediaType mediaType : OdfMediaType.values()) {
- embeddedObjects.addAll(getEmbeddedDocuments(mediaType));
- }
- return embeddedObjects;
- }
-
- /**
- * Method returns all embedded OdfPackageDocuments of the root
- * OdfPackageDocument matching the according MediaType. This is done by
- * matching the subfolder entries of the manifest file with the given
- * OdfMediaType.
- *
- * @param mediaType
- * media type which is used as a filter
- * @return embedded documents of the root OdfPackageDocument matching the
- * given media type
- */
- public List<Document> getEmbeddedDocuments(OdfMediaType mediaType) {
- String wantedMediaString = null;
- if (mediaType != null) {
- wantedMediaString = mediaType.getMediaTypeString();
- }
- List<Document> embeddedObjects = new ArrayList<Document>();
- // 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)) {
- normalizeDocumentPath(path);
- embeddedObjects.add(getEmbeddedDocument(path));
- }
- } else {
- // test if any ODF mediatype matches the current
- for (OdfMediaType type : OdfMediaType.values()) {
- if (entryMediaType.equals(type.getMediaTypeString())) {
- embeddedObjects.add(getEmbeddedDocument(path));
- }
- }
- }
- }
- }
- }
- return embeddedObjects;
- }
-
- /**
- * Embed an OdfPackageDocument to the current OdfPackageDocument. All the
- * file entries of child document will be embedded as well to the current
- * document package.
- *
- * @param documentPath
- * to the directory the ODF document should be inserted (relative
- * to the current document).
- * @param sourceDocument
- * the OdfPackageDocument to be embedded.
- */
- public void insertDocument(OdfPackageDocument sourceDocument, String documentPath) {
- super.insertDocument(sourceDocument, documentPath);
- }
-
- /**
- * Sets the media type of the Document
- *
- * @param odfMediaType
- * media type to be set
- */
- protected void setOdfMediaType(OdfMediaType odfMediaType) {
- mMediaType = odfMediaType;
- super.setMediaTypeString(odfMediaType.getMediaTypeString());
- }
-
- /**
- * Gets the media type of the Document
- */
- 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 Meta getOfficeMetadata() {
- if (mOfficeMeta == null) {
- try {
- mOfficeMeta = new Meta(getMetaDom());
- } catch (Exception ex) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
- return mOfficeMeta;
- }
-
- /**
- * Save the document to an OutputStream. Delegate to the root document and
- * save possible embedded Documents.
- *
- * <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 {
- // 2DO FLUSH AND SAVE IN PACKAGE
- flushDoms();
- updateMetaData();
- if (!isRootDocument()) {
- Document 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 {
- // 2DO MOVE CACHE TO PACKAGE
- // // the root document only have to flush the DOM of all open child
- // documents
- // flushAllDOMs();
- 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
- */
- public void save(File file) throws Exception {
- // 2DO FLUSH AND SAVE IN PACKAGE
- flushDoms();
- updateMetaData();
- if (!isRootDocument()) {
- Document 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);
- }
- }
-
- /**
- * Save the document to a given file with given password.
- *
- * <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.
- * @param file
- * the password of this document.
- *
- * @throws java.lang.Exception
- * if the document could not be saved
- * @since 0.8
- */
- public void save(File file, String password) throws Exception {
- // 2DO FLUSH AND SAVE IN PACKAGE
- flushDoms();
- updateMetaData();
- if (!isRootDocument()) {
- Document newDoc = loadDocumentFromTemplate(getOdfMediaType());
- newDoc.insertDocument(this, ROOT_DOCUMENT_PATH);
- newDoc.updateMetaData();
- newDoc.mPackage.setPassword(password);
- 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 {
- mPackage.setPassword(password);
- 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. Note that this will not close any cached documents.
- */
- @Override
- public void close() {
- // set all member variables explicit to null
- mMediaType = null;
- mOfficeMeta = null;
- mComponentRepository.clear();
- super.close();
- }
-
- /**
- * Get the content root of a document.
- *
- * You may prefer to use the getContentRoot methods of subclasses of
- * Document. Their return parameters are already casted to respective
- * subclasses of OdfElement.
- *
- * @param clazz
- * 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")
- protected <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
- * Document.
- *
- * @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 Document.
- *
- * @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 SpreadsheetDocument) {
- TableTableCellElement lastCell = (TableTableCellElement) xpath.evaluate("//table:table-cell[last()]", contentDom, XPathConstants.NODE);
- lastCell.appendChild(drawFrame);
- drawFrame.removeAttribute("text:anchor-type");
-
- } else if (this instanceof TextDocument) {
- TextPElement lastPara = (TextPElement) xpath.evaluate("//text:p[last()]", contentDom, XPathConstants.NODE);
- if (lastPara == null) {
- lastPara = ((TextDocument) this).newParagraph();
- }
- lastPara.appendChild(drawFrame);
- drawFrame.setTextAnchorTypeAttribute(TextAnchorTypeAttribute.Value.PARAGRAPH.toString());
- } else if (this instanceof PresentationDocument) {
- 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(Document.class.getName()).log(Level.SEVERE, null, ex);
- }
- return null;
- }
-
- /**
- * Meta data about the document will be initialized. Following metadata data
- * is being added:
- * <ul>
- * <li>The initial creator name will be the Java user.name System property.</li>
- * <li>The date and time when this document was created using the current
- * data.</li>
- * <li>The number of times this document has been edited.</li>
- * <li>The default language will be the Java user.language System property.</li>
- * </ul>
- *
- * @param newDoc
- * the Document object which need to initialize meta data.
- *
- * TODO:This method will be moved to OdfMetadata class. see
- * http://odftoolkit.org/bugzilla/show_bug.cgi?id=204
- */
- private static void initializeMetaData(Document newDoc) {
- Meta metaData = newDoc.getOfficeMetadata();
- // add initial-creator info.
- String creator = System.getProperty("user.name");
- metaData.setInitialCreator(creator);
- // add creation-date info.
- Calendar calendar = Calendar.getInstance();
- metaData.setCreationDate(calendar);
- // add editing-cycles info.
- metaData.setEditingCycles(0);
- // add language info.
- String language = System.getProperty("user.language");
- if (language != null) {
- metaData.setLanguage(language);
- }
- }
-
- /**
- * 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
- */
- private void updateMetaData() throws Exception {
- if (mMetaDom != null) {
- Meta metaData = getOfficeMetadata();
- String creator = System.getProperty("user.name");
- // update creator info.
- metaData.setCreator(creator);
- // update date info.
- Calendar calendar = Calendar.getInstance();
- metaData.setDcdate(calendar);
- // update editing-cycles info.
- Integer cycle = metaData.getEditingCycles();
- if (cycle != null) {
- metaData.setEditingCycles(++cycle);
- } else {
- metaData.setEditingCycles(1);
- }
- // update editing-duration info.
- 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(Document.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 types:
- *
- * <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. 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 ScriptType {
- /**
- * 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, getScriptType(locale));
- }
-
- public static ScriptType getScriptType(Locale locale) {
- String language = locale.getLanguage();
- if (CJKLanguage.contains(language))
- return ScriptType.CJK;
- if (CTLLanguage.contains(language))
- return ScriptType.CTL;
- return ScriptType.WESTERN;
-
- }
-
- /**
- * <p>
- * Set a locale of a specific script type.
- * <p>
- * If the locale is not belone to the script type, nothing will happen.
- *
- * @param locale
- * - Locale information
- * @param scriptType
- * - The script type
- */
- public void setLocale(Locale locale, ScriptType scriptType) {
- try {
- switch (scriptType) {
- case WESTERN:
- setWesternLanguage(locale);
- break;
- case CJK:
- setDefaultAsianLanguage(locale);
- break;
- case CTL:
- setDefaultComplexLanguage(locale);
- break;
- }
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed to set locale", e);
- }
- }
-
- /**
- * <p>
- * Get a locale information of a specific script type.
- *
- * @param scriptType
- * - The script type
- * @return the Locale information
- */
- public Locale getLocale(ScriptType scriptType) {
- try {
- switch (scriptType) {
- 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(Document.class.getName()).log(Level.SEVERE, "Failed to get locale", e);
- }
- 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 setWesternLanguage(Locale locale) throws Exception {
- if (getScriptType(locale) != ScriptType.WESTERN)
- return;
-
- 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());
- }
- }
- }
- }
-
- 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 return an instance of 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.
- */
-
- /**
- * 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 {
- if (getScriptType(locale) != ScriptType.CJK)
- return;
- String user_language = locale.getLanguage();
- if (!user_language.equals(Locale.CHINESE.getLanguage()) && !user_language.equals(Locale.TRADITIONAL_CHINESE.getLanguage())
- && !user_language.equals(Locale.JAPANESE.getLanguage()) && !user_language.equals(Locale.KOREAN.getLanguage()))
- return;
-
- 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 {
- if (getScriptType(locale) != ScriptType.CTL)
- return;
- 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());
- }
- }
- }
- }
-
- /**
- * This method will search both the document content and header/footer,
- * return an iterator of section objects.
- * <p>
- * The sections defined in embed document won't be covered.
- *
- * @return an iterator of Section objects
- */
- public Iterator<Section> getSectionIterator() {
- TextSectionElement element;
- ArrayList<Section> list = new ArrayList<Section>();
- try {
- // search in content.xml
- OdfElement root = getContentDom().getRootElement();
- OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root);
- NodeList sectionList = officeBody.getElementsByTagNameNS(OdfDocumentNamespace.TEXT.getUri(), "section");
- for (int i = 0; i < sectionList.getLength(); i++) {
- element = (TextSectionElement) sectionList.item(i);
- list.add(Section.getInstance(element));
- }
-
- // Search in style.xml
- root = getStylesDom().getRootElement();
- OfficeMasterStylesElement masterStyle = OdfElement.findFirstChildNode(OfficeMasterStylesElement.class, root);
- sectionList = masterStyle.getElementsByTagNameNS(OdfDocumentNamespace.TEXT.getUri(), "section");
- for (int i = 0; i < sectionList.getLength(); i++) {
- element = (TextSectionElement) sectionList.item(i);
- list.add(Section.getInstance(element));
- }
-
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in sectionIterator", e);
- }
- return list.iterator();
- }
-
- /**
- * This method will search both the document content and header/footer,
- * return a section with a specific name.
- * <p>
- * This method won't search in the embed document.
- * <p>
- * Null will be returned if there is no section found.
- *
- * @param name
- * - the name of a section
- * @return a section object with a specific name
- */
- public Section getSectionByName(String name) {
- TextSectionElement element;
- try {
- OdfElement root = getContentDom().getRootElement();
- OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root);
- XPath xpath = getContentDom().getXPath();
- String xpathValue = ".//text:section[@text:name=\"" + name + "\"]";
- element = (TextSectionElement) xpath.evaluate(xpathValue, officeBody, XPathConstants.NODE);
- if (element != null) {
- return Section.getInstance(element);
- }
-
- root = getStylesDom().getRootElement();
- OfficeMasterStylesElement masterStyle = OdfElement.findFirstChildNode(OfficeMasterStylesElement.class, root);
- xpath = getStylesDom().getXPath();
- element = (TextSectionElement) xpath.evaluate(".//text:section[@text:name=\"" + name + "\"]", masterStyle, XPathConstants.NODE);
- if (element != null) {
- return Section.getInstance(element);
- }
-
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in getSectionByName", e);
- }
-
- return null;
-
- }
-
- /**
- * Remove an ODF element from the document. All the resources that are only
- * related with this element will be removed at the same time.
- *
- * @param odfElement
- * - the odf element that would be moved.
- */
- public boolean removeElementLinkedResource(OdfElement odfElement) {
- boolean success = deleteLinkedRef(odfElement);
- success &= deleteStyleRef(odfElement);
- return success;
- }
-
- /**
- * Return a unique string with a character "a" followed by randomly
- * generating 6 hex numbers
- *
- * @return a unique string
- */
- String makeUniqueName() {
- return String.format("a%06x", (int) (Math.random() * 0xffffff));
- }
-
- private String getNewUniqueString(String oldStr) {
- int lastIndex = oldStr.lastIndexOf("-");
- if (lastIndex == -1) {
- return oldStr + "-" + makeUniqueName();
- }
- String suffix = oldStr.substring(lastIndex + 1);
- if (suffix.matches("a[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]")) {
- return oldStr.substring(0, lastIndex + 1) + makeUniqueName();
- } else
- return oldStr + "-" + makeUniqueName();
- }
-
- private void updateAttribute(Attr attr) {
- String oldID = attr.getValue();
- String newID = getNewUniqueString(oldID);
- attr.setValue(newID);
- }
-
- /**
- * Make a content copy of the specified element, and the returned element
- * should have the specified ownerDocument.
- *
- * @param element
- * The element that need to be copied
- * @param dom
- * The specified DOM tree that the returned element belong to
- * @param deep
- * If true, recursively clone the subtree under the element,
- * false, only clone the element itself
- * @return Returns a duplicated element which is not in the DOM tree with
- * the specified element
- */
- Node cloneForeignElement(Node element, OdfFileDom dom, boolean deep) {
- if (element instanceof OdfElement) {
- OdfElement cloneElement = dom.createElementNS(((OdfElement) element).getOdfName());
-
- NamedNodeMap attributes = element.getAttributes();
- if (attributes != null) {
- for (int i = 0; i < attributes.getLength(); i++) {
- Node item = attributes.item(i);
- String qname = null;
- String prefix = item.getPrefix();
- if (prefix == null) {
- qname = item.getLocalName();
- cloneElement.setAttribute(qname, item.getNodeValue());
- } else {
- qname = prefix + ":" + item.getLocalName();
- cloneElement.setAttributeNS(item.getNamespaceURI(), qname, item.getNodeValue());
- }
- }
- }
-
- if (deep) {
- Node childNode = element.getFirstChild();
- while (childNode != null) {
- cloneElement.appendChild(cloneForeignElement(childNode, dom, true));
- childNode = childNode.getNextSibling();
- }
- }
-
- return cloneElement;
- } else {
- return dom.createTextNode(element.getNodeValue());
- }
-
- }
-
- /**
- * This method will update all the attribute "xml:id" to make it unique
- * within the whole document content
- * <p>
- * This method is usually be invoked before inserting a copied ODF element
- * to document content.
- *
- * @param element
- * - the element that need to be inserted.
- */
- void updateXMLIds(OdfElement element) {
- try {
- XPath xpath = getContentDom().getXPath();
- String xpathValue = "//*[@xml:id]";
- NodeList childList = (NodeList) xpath.evaluate(xpathValue, element, XPathConstants.NODESET);
- if (childList == null)
- return;
-
- for (int i = 0; i < childList.getLength(); i++) {
- OdfElement ele = (OdfElement) childList.item(i);
- Attr attri = ele.getAttributeNodeNS(OdfDocumentNamespace.XML.getUri(), "id");
- updateAttribute(attri);
- }
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in updateXMLIds", e);
- }
- }
-
- /**
- * This method will update all the attribute
- * "text:name","table:name","draw:name","chart:name", to make it unique
- * within the whole document content.
- * <p>
- * This method is usually be invoked before inserting a copied ODF element
- * to document content.
- *
- * @param element
- * - the element that need to be inserted.
- */
- // anim:name, chart:name, config:name, office:name, presentation:name,
- // svg:name,
- void updateNames(OdfElement element) {
- try {
- XPath xpath = getContentDom().getXPath();
- String xpathValue = "descendant-or-self::node()[@text:name|@table:name|@draw:name|@chart:name]";
- NodeList childList = (NodeList) xpath.evaluate(xpathValue, element, XPathConstants.NODESET);
- if (childList == null)
- return;
- for (int i = 0; i < childList.getLength(); i++) {
- OdfElement ele = (OdfElement) childList.item(i);
- Attr attri = ele.getAttributeNodeNS(OdfDocumentNamespace.TEXT.getUri(), "name");
- if (attri != null)
- updateAttribute(attri);
- attri = ele.getAttributeNodeNS(OdfDocumentNamespace.TABLE.getUri(), "name");
- if (attri != null)
- updateAttribute(attri);
- if (ele instanceof DrawFrameElement)// only update draw:frame
- {
- attri = ele.getAttributeNodeNS(OdfDocumentNamespace.DRAW.getUri(), "name");
- if (attri != null)
- updateAttribute(attri);
- }
- }
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in updateXMLIds", e);
- }
- }
-
- /**
- * This method will copy the linked resource of the element which need to be
- * copied, from the source package to the target package.
- * <p>
- * If the target package contains a resource with the same path and name,
- * the name of the resource will be renamed.
- * <p>
- * This method will copy resources all in one batch.
- *
- * @param sourceCloneEle
- * - the element that need to be copied
- * @param srcDocument
- * - the source document
- */
- void copyLinkedRefInBatch(OdfElement sourceCloneEle, Document srcDocument) {
- try {
- OdfFileDom fileDom = (OdfFileDom) sourceCloneEle.getOwnerDocument();
- XPath xpath;
- if (fileDom instanceof OdfContentDom) {
- xpath = ((OdfContentDom) fileDom).getXPath();
- } else {
- xpath = ((OdfStylesDom) fileDom).getXPath();
- }
- // OdfPackageDocument srcDoc = fileDom.getDocument();
- // new a map to put the original name and the rename string, in case
- // that the same name might be referred by the slide several times.
- HashMap<String, String> objectRenameMap = new HashMap<String, String>();
- NodeList linkNodes = (NodeList) xpath.evaluate(".//*[@xlink:href]", sourceCloneEle, XPathConstants.NODESET);
- for (int i = 0; i <= linkNodes.getLength(); i++) {
- OdfElement object = null;
- if (linkNodes.getLength() == i) {
- if (sourceCloneEle.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) {
- object = sourceCloneEle;
- } else {
- break;
- }
- } else {
- object = (OdfElement) linkNodes.item(i);
- }
- String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
- if (refObjPath != null && refObjPath.length() > 0) {
- // the path of the object is start with "./"
- boolean hasPrefix = false;
- String prefix = "./";
- String newObjPath;
- if (refObjPath.startsWith(prefix)) {
- refObjPath = refObjPath.substring(2);
- hasPrefix = true;
- }
- // check if this linked resource has been copied
- if (objectRenameMap.containsKey(refObjPath)) {
- // this object has been copied already
- newObjPath = objectRenameMap.get(refObjPath);
- object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath);
- continue;
- }
- // check if the current document contains the same path
- OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath);
- // note: if refObjPath is a directory, it must end with '/'
- if (fileEntry == null) {
- fileEntry = getPackage().getFileEntry(refObjPath + "/");
- }
- if (fileEntry != null) {
- // rename the object path
- newObjPath = objectRenameMap.get(refObjPath);
- if (newObjPath == null) {
- // if refObjPath still contains ".", it means that
- // it has the suffix
- // then change the name before the suffix string
- int dotIndex = refObjPath.indexOf(".");
- if (dotIndex != -1) {
- newObjPath = refObjPath.substring(0, dotIndex) + "-" + makeUniqueName() + refObjPath.substring(dotIndex);
- } else {
- newObjPath = refObjPath + "-" + makeUniqueName();
- }
- objectRenameMap.put(refObjPath, newObjPath);
- }
- object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath);
- } else
- objectRenameMap.put(refObjPath, refObjPath);
- }
- }
- // copy resource in batch
- copyResourcesFrom(srcDocument, objectRenameMap);
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- }
- }
-
- /*****************************/
- /*
- * These codes are moved from OdfPackage, and should be removed till
- * OdfPackage can provide a mechanism to copy resources in batch.
- */
- /*****************************/
- private InputStream readAsInputStream(ZipInputStream inputStream) throws IOException {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- if (outputStream != null) {
- byte[] buf = new byte[4096];
- int r = 0;
- while ((r = inputStream.read(buf, 0, 4096)) > -1) {
- outputStream.write(buf, 0, r);
- }
- }
- return new ByteArrayInputStream(outputStream.toByteArray());
- }
-
- private String normalizeFilePath(String internalPath) {
- if (internalPath.equals(EMPTY_STRING)) {
- String errMsg = "The internalPath given by parameter is an empty string!";
- Logger.getLogger(OdfPackage.class.getName()).severe(errMsg);
- throw new IllegalArgumentException(errMsg);
- } else {
- return normalizePath(internalPath);
- }
- }
-
- private static final String DOUBLE_DOT = "..";
- private static final String DOT = ".";
- private static final String COLON = ":";
- private static final Pattern BACK_SLASH_PATTERN = Pattern.compile("\\\\");
- private static final Pattern DOUBLE_SLASH_PATTERN = Pattern.compile("//");
-
- private String normalizePath(String path) {
- if (path == null) {
- String errMsg = "The internalPath given by parameter is NULL!";
- Logger.getLogger(OdfPackage.class.getName()).severe(errMsg);
- throw new IllegalArgumentException(errMsg);
- } else if (!mightBeExternalReference(path)) {
- if (path.equals(EMPTY_STRING)) {
- path = SLASH;
- } else {
- // exchange all backslash "\" with a slash "/"
- if (path.indexOf('\\') != -1) {
- path = BACK_SLASH_PATTERN.matcher(path).replaceAll(SLASH);
- }
- // exchange all double slash "//" with a slash "/"
- while (path.indexOf("//") != -1) {
- path = DOUBLE_SLASH_PATTERN.matcher(path).replaceAll(SLASH);
- }
- // if directory replacements (e.g. ..) exist, resolve and remove
- // them
- if (path.indexOf("/.") != -1 || path.indexOf("./") != -1) {
- path = removeChangeDirectories(path);
- }
- }
- }
- return path;
- }
-
- private boolean mightBeExternalReference(String internalPath) {
- boolean isExternalReference = false;
- // if the fileReference is a external relative documentURL..
- if (internalPath.startsWith(DOUBLE_DOT) || // or absolute documentURL
- // AND not root document
- internalPath.startsWith(SLASH) && !internalPath.equals(SLASH) || // or
- // absolute
- // IRI
- internalPath.contains(COLON)) {
- isExternalReference = true;
- }
- return isExternalReference;
- }
-
- private String removeChangeDirectories(String path) {
- boolean isDirectory = path.endsWith(SLASH);
- StringTokenizer tokenizer = new StringTokenizer(path, SLASH);
- int tokenCount = tokenizer.countTokens();
- List<String> tokenList = new ArrayList<String>(tokenCount);
- // add all paths to a list
- while (tokenizer.hasMoreTokens()) {
- String token = tokenizer.nextToken();
- tokenList.add(token);
- }
- if (!isDirectory) {
- String lastPath = tokenList.get(tokenCount - 1);
- if (lastPath.equals(DOT) || lastPath.equals(DOUBLE_DOT)) {
- isDirectory = true;
- }
- }
- String currentToken;
- int removeDirLevel = 0;
- StringBuilder out = new StringBuilder();
- // work on the list from back to front
- for (int i = tokenCount - 1; i >= 0; i--) {
- currentToken = tokenList.get(i);
- // every ".." will remove an upcoming path
- if (currentToken.equals(DOUBLE_DOT)) {
- removeDirLevel++;
- } else if (currentToken.equals(DOT)) {
- } else {
- // if a path have to be remove, neglect current path
- if (removeDirLevel > 0) {
- removeDirLevel--;
- } else {
- // add the path segment
- out.insert(0, SLASH);
- out.insert(0, currentToken);
- }
- }
- }
- if (removeDirLevel > 0) {
- return EMPTY_STRING;
- } else {
- if (!isDirectory) {
- // remove trailing slash /
- out.deleteCharAt(out.length() - 1);
- }
- return out.toString();
- }
- }
-
- /*****************************/
- // FIXME: These two methods are only used in method copyResourcesFrom to
- // improve copy performance, should not be used in any other way.
- // methods loadDocument(String documentPath) and loadDocument(File file)
- // will initialize mFile.
- // This field and these two methods should be removed after ODFDOM supplies
- // batch copy.
- private void setFile(File thisFile) {
- mFile = thisFile;
- }
-
- private File getFile() {
- return mFile;
- }
-
- /**
- * This method will copy resources from source document to this document.
- * The second parameter contains a map between all the name of resources in
- * the source document and the rename string. If the source document is
- * loaded from a file, a good performance method will be used. If the source
- * document is loaded from a input stream, package layer methods will be
- * invoked to copy these resources, with bad performance.
- *
- * In future, the code of this method will move to ODFDOM package layer.
- * Till then, good performance will be gotten whether the source document is
- * loaded from file or from input stream.
- *
- */
- void copyResourcesFrom(Document srcDoc, HashMap<String, String> objectRenameMap) throws Exception {
- if (srcDoc.getFile() != null) {
- ArrayList<String> copiedFolder = new ArrayList<String>();
- Set<String> refObjPathSet = objectRenameMap.keySet();
- FileInputStream tempFileStream = new FileInputStream(srcDoc.getFile());
- ZipInputStream zipStream = new ZipInputStream(tempFileStream);
- ZipEntry zipEntry = zipStream.getNextEntry();
- while (zipEntry != null) {
- String refObjPath = zipEntry.getName();
- for (String path : refObjPathSet) {
- if (refObjPath.equals(path)) {
- String newObjPath = objectRenameMap.get(refObjPath);
- refObjPath = normalizeFilePath(refObjPath);
- String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString();
- InputStream is = readAsInputStream(zipStream);
- getPackage().insert(is, newObjPath, mediaType);
- break;
- } else if (refObjPath.startsWith(path + "/")) {
- String suffix = refObjPath.substring(path.length());
- String newObjPath = objectRenameMap.get(path) + suffix;
- refObjPath = normalizeFilePath(refObjPath);
- String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString();
- InputStream is = readAsInputStream(zipStream);
- getPackage().insert(is, newObjPath, mediaType);
- if (!copiedFolder.contains(path)) {
- mediaType = srcDoc.getPackage().getFileEntry(path + "/").getMediaTypeString();
- getPackage().insert((InputStream) null, objectRenameMap.get(path) + "/", mediaType);
- copiedFolder.add(path);
- }
- break;
- }
- }
- zipEntry = zipStream.getNextEntry();
- }
- zipStream.close();
- tempFileStream.close();
- } else {
- Set<String> refObjPathSet = objectRenameMap.keySet();
- for (String refObjPath : refObjPathSet) {
- String newObjPath = objectRenameMap.get(refObjPath);
- InputStream is = srcDoc.getPackage().getInputStream(refObjPath);
- if (is != null) {
- String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString();
- getPackage().insert(is, newObjPath, mediaType);
- } else {
- Document embedDoc = ((Document) srcDoc).getEmbeddedDocument(refObjPath);
- if (embedDoc != null) {
- insertDocument(embedDoc, newObjPath);
- }
- }
- }
- }
- }
-
- /**
- * This method will copy the linked resource of the element which need to be
- * copied, from the source package to the target package.
- * <p>
- * If the target package contains a resource with the same path and name,
- * the name of the resource will be renamed.
- *
- * @param sourceCloneEle
- * - the element that need to be copied
- */
- // clone the source clone element's referred object path to the current
- // package
- // if the current package contains the same name with the referred object
- // path,
- // rename the object path and path reference of this slide element
- // notes: the source clone element is the copied one to avoid changing the
- // content of the source document.
- void copyLinkedRef(OdfElement sourceCloneEle) {
- try {
- OdfFileDom fileDom = (OdfFileDom) sourceCloneEle.getOwnerDocument();
- XPath xpath;
- if (fileDom instanceof OdfContentDom) {
- xpath = ((OdfContentDom) fileDom).getXPath();
- } else {
- xpath = ((OdfStylesDom) fileDom).getXPath();
- }
- OdfPackageDocument srcDoc = fileDom.getDocument();
- // new a map to put the original name and the rename string, in case
- // that the same name might be referred by the slide several times.
- HashMap<String, String> objectRenameMap = new HashMap<String, String>();
- NodeList linkNodes = (NodeList) xpath.evaluate(".//*[@xlink:href]", sourceCloneEle, XPathConstants.NODESET);
- for (int i = 0; i <= linkNodes.getLength(); i++) {
- OdfElement object = null;
- if (linkNodes.getLength() == i) {
- if (sourceCloneEle.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) {
- object = sourceCloneEle;
- } else {
- break;
- }
- } else {
- object = (OdfElement) linkNodes.item(i);
- }
- String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
- if (refObjPath != null && refObjPath.length() > 0) {
- // the path of the object is start with "./"
- boolean hasPrefix = false;
- String prefix = "./";
- if (refObjPath.startsWith(prefix)) {
- refObjPath = refObjPath.substring(2);
- hasPrefix = true;
- }
- // check if the current document contains the same path
- OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath);
- // note: if refObjPath is a directory, it must end with '/'
- if (fileEntry == null) {
- fileEntry = getPackage().getFileEntry(refObjPath + "/");
- }
- String newObjPath = refObjPath;
- if (fileEntry != null) {
- // rename the object path
- newObjPath = objectRenameMap.get(refObjPath);
- if (newObjPath == null) {
- // if refObjPath still contains ".", it means that
- // it has the suffix
- // then change the name before the suffix string
- int dotIndex = refObjPath.indexOf(".");
- if (dotIndex != -1) {
- newObjPath = refObjPath.substring(0, dotIndex) + "-" + makeUniqueName() + refObjPath.substring(dotIndex);
- } else {
- newObjPath = refObjPath + "-" + makeUniqueName();
- }
- objectRenameMap.put(refObjPath, newObjPath);
- }
- object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath);
- }
- InputStream is = srcDoc.getPackage().getInputStream(refObjPath);
- if (is != null) {
- String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString();
- getPackage().insert(is, newObjPath, mediaType);
- } else {
- Document embedDoc = ((Document) srcDoc).getEmbeddedDocument(refObjPath);
- if (embedDoc != null) {
- insertDocument(embedDoc, newObjPath);
- }
- }
- }
- }
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- }
- }
-
- /**
- * When a element needs to be copied to a different document, all the style
- * definitions that are related with this element need to be copied.
- *
- * @param sourceCloneEle
- * - the element that need to be copied
- * @param srcDoc
- * - the source document
- */
- void copyForeignStyleRef(OdfElement sourceCloneEle, Document srcDoc) {
- try {
- ArrayList<String> tempList = new ArrayList<String>();
- OdfFileDom srcContentDom = srcDoc.getContentDom();
- XPath xpath = srcContentDom.getXPath();
- // 1. collect all the referred style element which has "style:name"
- // attribute
- // 1.1. style:name of content.xml
- String styleQName = "style:name";
- NodeList srcStyleDefNodeList = (NodeList) xpath.evaluate("*/office:automatic-styles/*[@" + styleQName + "]", srcContentDom, XPathConstants.NODESET);
- IdentityHashMap<OdfElement, List<OdfElement>> srcContentStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>();
- IdentityHashMap<OdfElement, OdfElement> appendContentStyleList = new IdentityHashMap<OdfElement, OdfElement>();
- getCopyStyleList(null, sourceCloneEle, styleQName, srcStyleDefNodeList, srcContentStyleCloneEleList, appendContentStyleList, tempList, true);
- // 1.2. style:name of styles.xml
- srcStyleDefNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", srcDoc.getStylesDom(), XPathConstants.NODESET);
- IdentityHashMap<OdfElement, List<OdfElement>> srcStylesStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>();
- IdentityHashMap<OdfElement, OdfElement> appendStylesStyleList = new IdentityHashMap<OdfElement, OdfElement>();
- tempList.clear();
- getCopyStyleList(null, sourceCloneEle, styleQName, srcStyleDefNodeList, srcStylesStyleCloneEleList, appendStylesStyleList, tempList, true);
- // 1.3 rename, copy the referred style element to the corresponding
- // position in the dom tree
- insertCollectedStyle(styleQName, srcContentStyleCloneEleList, getContentDom(), appendContentStyleList);
- insertCollectedStyle(styleQName, srcStylesStyleCloneEleList, getStylesDom(), appendStylesStyleList);
-
- // 2. collect all the referred style element which has "draw:name"
- // attribute
- // 2.1 draw:name of styles.xml
- // the value of draw:name is string or StyleName,
- // only when the value is StyleName type, the style definition
- // should be cloned to the destination document
- // in ODF spec, such attribute type is only exist in <office:styles>
- // element, so only search it in styles.xml dom
- tempList.clear();
- styleQName = "draw:name";
- srcStyleDefNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", srcDoc.getStylesDom(), XPathConstants.NODESET);
- IdentityHashMap<OdfElement, List<OdfElement>> srcDrawStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>();
- IdentityHashMap<OdfElement, OdfElement> appendDrawStyleList = new IdentityHashMap<OdfElement, OdfElement>();
- Iterator<OdfElement> iter = appendContentStyleList.keySet().iterator();
- while (iter.hasNext()) {
- OdfElement styleElement = iter.next();
- OdfElement cloneStyleElement = appendContentStyleList.get(styleElement);
- getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, tempList,
- false);
- }
- iter = appendStylesStyleList.keySet().iterator();
- while (iter.hasNext()) {
- OdfElement styleElement = iter.next();
- OdfElement cloneStyleElement = appendStylesStyleList.get(styleElement);
- getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, tempList,
- false);
- }
- // 2.2 rename, copy the referred style element to the corresponding
- // position in the dom tree
- // note: "draw:name" style element only exist in styles.dom
- insertCollectedStyle(styleQName, srcDrawStyleCloneEleList, getStylesDom(), appendDrawStyleList);
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- }
-
- }
-
- // 1. modified the style name of the style definition element which has the
- // same name with the source document
- // 2. As to the style definition which match 1) condition, modified the
- // referred style name of the element which reference this style
- // 3. All the style which also contains other style reference, should be
- // copied to the source document.
- private void insertCollectedStyle(String styleQName, IdentityHashMap<OdfElement, List<OdfElement>> srcStyleCloneEleList, OdfFileDom dom,
- IdentityHashMap<OdfElement, OdfElement> appendStyleList) {
- try {
- String stylePrefix = OdfNamespace.getPrefixPart(styleQName);
- String styleLocalName = OdfNamespace.getLocalPart(styleQName);
- String styleURI = OdfDocumentNamespace.STYLE.getUri();
- if (stylePrefix.equals("draw"))
- styleURI = OdfDocumentNamespace.DRAW.getUri();
- // is the DOM always the styles.xml
- XPath xpath = dom.getXPath();
- NodeList destStyleNodeList;
- if (dom instanceof OdfContentDom)
- destStyleNodeList = (NodeList) xpath.evaluate("*/office:automatic-styles/*[@" + styleQName + "]", dom, XPathConstants.NODESET);
- else
- destStyleNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", dom, XPathConstants.NODESET);
- Iterator<OdfElement> iter = srcStyleCloneEleList.keySet().iterator();
- while (iter.hasNext()) {
- OdfElement styleElement = iter.next();
- OdfElement cloneStyleElement = appendStyleList.get(styleElement);
- if (cloneStyleElement == null) {
- cloneStyleElement = (OdfElement) styleElement.cloneNode(true);
- appendStyleList.put(styleElement, cloneStyleElement);
- }
- String styleName = styleElement.getAttributeNS(styleURI, styleLocalName);
- List<String> newStyleNameList = styleRenameMap.get(styleName);
- // if the newStyleNameList != null, means that styleName exists
- // in dest document
- // and it has already been renamed
- if ((newStyleNameList != null) || (isStyleNameExist(destStyleNodeList, styleName) != null)) {
- String newStyleName = null;
- if (newStyleNameList == null) {
- newStyleNameList = new ArrayList<String>();
- newStyleName = styleName + "-" + makeUniqueName();
- newStyleNameList.add(newStyleName);
- styleRenameMap.put(styleName, newStyleNameList);
- } else {
- for (int i = 0; i < newStyleNameList.size(); i++) {
- String styleNameIter = newStyleNameList.get(i);
- OdfElement destStyleElementWithNewName = isStyleNameExist(destStyleNodeList, styleNameIter);
- // check if the two style elements have the same
- // content
- // if not, the cloneStyleElement should rename,
- // rather than reuse the new style name
- cloneStyleElement.setAttributeNS(styleURI, styleQName, styleNameIter);
- if ((destStyleElementWithNewName != null) && destStyleElementWithNewName.equals(cloneStyleElement)) {
- newStyleName = styleNameIter;
- break;
- }
- }
- if (newStyleName == null) {
- newStyleName = styleName + "-" + makeUniqueName();
- newStyleNameList.add(newStyleName);
- }
- }
- // System.out.println("renaming:"+styleName+"-"+newStyleName);
- // if newStyleName has been set in the element as the new
- // name
- // which means that the newStyleName is conform to the odf
- // spec
- // then change element style reference name
- if (changeStyleRefName(srcStyleCloneEleList.get(styleElement), styleName, newStyleName)) {
- cloneStyleElement.setAttributeNS(styleURI, styleQName, newStyleName);
- // if display name should also be renamed
- String displayName = cloneStyleElement.getAttributeNS(styleURI, "display-name");
- if ((displayName != null) && (displayName.length() > 0)) {
- cloneStyleElement.setAttributeNS(styleURI, stylePrefix + ":display-name",
- displayName + newStyleName.substring(newStyleName.length() - 8));
- }
- }
- }
- }
-
- iter = appendStyleList.keySet().iterator();
- while (iter.hasNext()) {
- OdfElement styleElement = iter.next();
- OdfElement cloneStyleElement = appendStyleList.get(styleElement);
- String newStyleName = cloneStyleElement.getAttributeNS(styleURI, styleLocalName);
- Boolean isAppended = styleAppendMap.get(newStyleName);
- // if styleAppendMap contain the newStyleName,
- // means that cloneStyleElement has already been appended
- if ((isAppended != null) && isAppended.booleanValue() == true) {
- continue;
- } else {
- styleAppendMap.put(newStyleName, true);
- }
- OdfElement cloneForeignStyleElement = (OdfElement) cloneForeignElement(cloneStyleElement, dom, true);
- String styleElePath = getElementPath(styleElement);
- appendForeignStyleElement(cloneForeignStyleElement, dom, styleElePath);
- copyLinkedRef(cloneStyleElement);
- }
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- }
-
- }
-
- // get all the copy of referred style element which is directly referred or
- // indirectly referred by cloneEle
- // styleQName is style:name
- // all the style are defined in srcStyleNodeList
- // and these style are all have the styleName defined in styleQName
- // attribute
- // the key of copyStyleEleList is the style definition element
- // the value of the corresponding key is the clone of the element which
- // refer to the key,
- // the cloned element can be the content of slide or the style element.
- // the key of appendStyleList is the style definition element which has the
- // other style reference
- // the value of the corresponding key is the the style definition clone
- // element
- // loop means if recursive call this function
- // if loop == true, get the style definition element reference other style
- // definition element
- private void getCopyStyleList(OdfElement ele, OdfElement cloneEle, String styleQName, NodeList srcStyleNodeList,
- IdentityHashMap<OdfElement, List<OdfElement>> copyStyleEleList, IdentityHashMap<OdfElement, OdfElement> appendStyleList, List<String> attrStrList,
- boolean loop) {
- try {
- String styleLocalName = OdfNamespace.getLocalPart(styleQName);
- String stylePrefix = OdfNamespace.getPrefixPart(styleQName);
- String styleURI = OdfDocumentNamespace.STYLE.getUri();
- if (stylePrefix.equals("draw"))
- styleURI = OdfDocumentNamespace.DRAW.getUri();
- // OdfElement override the "toString" method
- String cloneEleStr = cloneEle.toString();
- for (int i = 0; i < srcStyleNodeList.getLength(); i++) {
- OdfElement styleElement = (OdfElement) srcStyleNodeList.item(i);
- String styleName = styleElement.getAttributeNS(styleURI, styleLocalName);
- if (styleName != null) {
- int index = 0;
- index = cloneEleStr.indexOf("=\"" + styleName + "\"", index);
- while (index >= 0) {
- String subStr = cloneEleStr.substring(0, index);
- int lastSpaceIndex = subStr.lastIndexOf(' ');
- String attrStr = subStr.substring(lastSpaceIndex + 1, index);
- if (attrStr.equals(styleQName) || attrStrList.contains(attrStr + "=" + "\"" + styleName + "\"")) {
- index = cloneEleStr.indexOf("=\"" + styleName + "\"", index + styleName.length());
- continue;
- }
- attrStrList.add(attrStr + "=" + "\"" + styleName + "\"");
- XPath xpath = ((OdfFileDom) cloneEle.getOwnerDocument()).getXPath();
- NodeList styleRefNodes = (NodeList) xpath.evaluate(".//*[@" + attrStr + "='" + styleName + "']", cloneEle, XPathConstants.NODESET);
- boolean isExist = false;
- for (int j = 0; j <= styleRefNodes.getLength(); j++) {
- OdfElement styleRefElement = null;
- if (j == styleRefNodes.getLength()) {
- isExist = isStyleNameRefExist(cloneEle, styleName, false);
- if (isExist) {
- styleRefElement = cloneEle;
- } else {
- continue;
- }
- } else {
- OdfElement tmpElement = (OdfElement) styleRefNodes.item(j);
- if (isStyleNameRefExist(tmpElement, styleName, false)) {
- styleRefElement = tmpElement;
- } else {
- continue;
- }
- }
- boolean hasLoopStyleDef = true;
- if (!(styleElement instanceof StyleFontFaceElement)) {
- if (copyStyleEleList.get(styleElement) == null) {
- List<OdfElement> styleRefEleList = new ArrayList<OdfElement>();
- copyStyleEleList.put(styleElement, styleRefEleList);
- hasLoopStyleDef = false;
- }
- copyStyleEleList.get(styleElement).add(styleRefElement);
- }
-
- OdfElement cloneStyleElement = appendStyleList.get(styleElement);
- if (cloneStyleElement == null) {
- cloneStyleElement = (OdfElement) styleElement.cloneNode(true);
- appendStyleList.put(styleElement, cloneStyleElement);
- }
- if (loop && !hasLoopStyleDef) {
- getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleNodeList, copyStyleEleList, appendStyleList, attrStrList,
- loop);
- }
- }
- index = cloneEleStr.indexOf("=\"" + styleName + "\"", index + styleName.length());
- }
- }
- }
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- }
- }
-
- // append the cloneStyleElement to the contentDom which position is defined
- // by styleElePath
- private void appendForeignStyleElement(OdfElement cloneStyleEle, OdfFileDom dom, String styleElePath) {
- StringTokenizer token = new StringTokenizer(styleElePath, "/");
- boolean isExist = true;
- boolean found = false;
- Node iterNode = dom.getFirstChild();
- Node parentNode = dom;
- while (token.hasMoreTokens()) {
- String onePath = token.nextToken();
- found = false;
-
- while ((iterNode != null) && isExist) {
- String path = iterNode.getNamespaceURI();
- String prefix = iterNode.getPrefix();
- if (prefix == null) {
- path += "@" + iterNode.getLocalName();
- } else {
- path += "@" + prefix + ":" + iterNode.getLocalName();
- }
- if (!path.equals(onePath)) {
- // not found, then get the next sibling to find such path
- // node
- iterNode = iterNode.getNextSibling();
- } else {
- // found, then get the child nodes to find the next path
- // node
- parentNode = iterNode;
- found = true;
- iterNode = iterNode.getFirstChild();
- break;
- }
- }
-
- if (!found) {
- // should new the element since the current path node
- if (isExist) {
- isExist = false;
- }
- StringTokenizer token2 = new StringTokenizer(onePath, "@");
- OdfElement newElement = dom.createElementNS(OdfName.newName(token2.nextToken(), token2.nextToken()));
- parentNode.appendChild(newElement);
- parentNode = newElement;
- }
- }
- parentNode.appendChild(cloneStyleEle);
- }
-
- // The returned string is a path from the top of the dom tree to the
- // specified element
- // and the path is split by "/" between each node
- private String getElementPath(OdfElement styleEle) {
- String path = "";
- Node parentNode = styleEle.getParentNode();
- while (!(parentNode instanceof OdfFileDom)) {
- String qname = null;
- String prefix = parentNode.getPrefix();
- if (prefix == null) {
- qname = parentNode.getLocalName();
- } else {
- qname = prefix + ":" + parentNode.getLocalName();
- }
- path = parentNode.getNamespaceURI() + "@" + qname + "/" + path;
- parentNode = parentNode.getParentNode();
- }
- return path;
- }
-
- // change the element referred oldStyleName to the new name
- // if true then set newStyleName attribute value successfully
- // if false means that the newStyleName value is not conform to the ODF
- // spec, so do not modify the oldStyleName
- private boolean changeStyleRefName(List<OdfElement> list, String oldStyleName, String newStyleName) {
- boolean rtn = false;
- for (int index = 0; index < list.size(); index++) {
- OdfElement element = list.get(index);
- NamedNodeMap attributes = element.getAttributes();
-
- if (attributes != null) {
- for (int i = 0; i < attributes.getLength(); i++) {
- Node item = attributes.item(i);
- String value = item.getNodeValue();
- if (oldStyleName.equals(value)) {
- try {
- item.setNodeValue(newStyleName);
- rtn = true;
- break;
- } catch (IllegalArgumentException e) {
- return false;
- }
- }
- }
- }
- }
- return rtn;
- }
-
- // check if the element contains the referred styleName
- private boolean isStyleNameRefExist(Node element, String styleName, boolean deep) {
- NamedNodeMap attributes = element.getAttributes();
- if (attributes != null) {
- for (int i = 0; i < attributes.getLength(); i++) {
- Node item = attributes.item(i);
- if (item.getNodeValue().equals(styleName) && !item.getNodeName().equals("style:name")) {
- // this is style definition, not reference.
- return true;
- }
- }
- }
- if (deep) {
- Node childNode = element.getFirstChild();
- while (childNode != null) {
- if (!isStyleNameRefExist(childNode, styleName, true)) {
- childNode = childNode.getNextSibling();
- } else {
- return true;
- }
- }
- }
- return false;
- }
-
- // check if nodeList contains the node that "style:name" attribute has the
- // same value with styleName
- // Note: nodeList here is all the style definition list
- private OdfElement isStyleNameExist(NodeList nodeList, String styleName) {
- for (int i = 0; i < nodeList.getLength(); i++) {
- OdfElement element = (OdfElement) nodeList.item(i);
- String name = element.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name");
- if (name.equals(styleName)) {
- // return true;
- return element;
- }
- }
- // return false;
- return null;
- }
-
- /**
- * This method will delete all the linked resources that are only related
- * with this element.
- *
- * @param odfEle
- * - the element to be deleted.
- * @return true if successfully delete, or else, false will be returned
- */
- // delete all the xlink:href object which is contained in slideElement and
- // does not referred by other slides
- boolean deleteLinkedRef(OdfElement odfEle) {
- boolean success = true;
- try {
- OdfFileDom contentDom = getContentDom();
- XPath xpath = contentDom.getXPath();
- NodeList linkNodes = (NodeList) xpath.evaluate("//*[@xlink:href]", contentDom, XPathConstants.NODESET);
- for (int i = 0; i < linkNodes.getLength(); i++) {
- OdfElement object = (OdfElement) linkNodes.item(i);
- String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
- int relation = odfEle.compareDocumentPosition(object);
- // if slide element contains the returned element which has the
- // xlink:href reference
- if ((relation & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0 && refObjPath != null && refObjPath.length() > 0) {
- // the path of the object is start with "./"
- NodeList pathNodes = (NodeList) xpath.evaluate("//*[@xlink:href='" + refObjPath + "']", getContentDom(), XPathConstants.NODESET);
- int refCount = pathNodes.getLength();
- if (refCount == 1) {
- // delete "./"
- if (refObjPath.startsWith("./")) {
- refObjPath = refObjPath.substring(2);
- }
- // check if the current document contains the same path
- OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath);
- if (fileEntry != null) {
- // it is a stream, such as image, binary file
- getPackage().remove(refObjPath);
- } else {
- // note: if refObjPath is a directory, it must end
- // with '/'
- fileEntry = getPackage().getFileEntry(refObjPath + "/");
- removeDocument(refObjPath);
- }
- }
- }
- }
- } catch (XPathExpressionException e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- success = false;
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- success = false;
- }
- return success;
- }
-
- /**
- * This method will delete all the style definitions that are only related
- * with this element.
- *
- * @param odfEle
- * - the element to be deleted.
- * @return true if successfully delete, or else, false will be returned
- */
- boolean deleteStyleRef(OdfElement odfEle) {
- boolean success = true;
- try {
- // method 1:
- // 1.1. iterate child element of the content element
- // 1.2. if the child element is an OdfStylableElement, get the
- // style-name ref count
- // //////////////
- // method 2:
- // 2.1. get the list of the style definition
- ArrayList<OdfElement> removeStyles = new ArrayList<OdfElement>();
- OdfOfficeAutomaticStyles autoStyles = getContentDom().getAutomaticStyles();
-
- NodeList stylesList = autoStyles.getChildNodes();
- OdfFileDom contentDom = getContentDom();
- XPath xpath = contentDom.getXPath();
-
- // 2.2. get the reference of each style which occurred in the
- // current page
- for (int i = 0; i < stylesList.getLength(); i++) {
- Node item = stylesList.item(i);
- if (item instanceof OdfElement) {
- OdfElement node = (OdfElement) item;
- String styleName = node.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name");
- if (styleName != null) {
- // search the styleName contained at the current page
- // element
- NodeList styleNodes = (NodeList) xpath.evaluate("//*[@*='" + styleName + "']", contentDom, XPathConstants.NODESET);
- int styleCnt = styleNodes.getLength();
- if (styleCnt > 1) {
- // the first styleName is occurred in the style
- // definition
- // so check if the second styleName and last
- // styleName is occurred in the current page element
- // if yes, then remove it
- OdfElement elementFirst = (OdfElement) styleNodes.item(1);
- OdfElement elementLast = (OdfElement) styleNodes.item(styleCnt - 1);
- boolean isSamePage = false;
- if (elementFirst instanceof DrawPageElement) {
- DrawPageElement tempPage = (DrawPageElement) elementFirst;
- if (tempPage.equals(odfEle)) {
- isSamePage = true;
- }
- }
- int relationFirst = odfEle.compareDocumentPosition(elementFirst);
- int relationLast = odfEle.compareDocumentPosition(elementLast);
- // if slide element contains the child element which
- // has the styleName reference
- if (((relationFirst & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0 && (relationLast & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0)
- || (isSamePage && (styleCnt == 1))) {
- if (node instanceof OdfStyleBase) {
- removeStyles.add(node);
- }
- }
- } else {
- continue;
- }
- }
- }
- }
- for (int i = 0; i < removeStyles.size(); i++) {
- autoStyles.removeChild(removeStyles.get(i));
- }
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- success = false;
- }
- return success;
- }
-
- public Table addTable() {
- return getTableContainerImpl().addTable();
- }
-
- public Table addTable(int numRows, int numCols) {
- return getTableContainerImpl().addTable(numRows, numCols);
- }
-
- public Table getTableByName(String name) {
- return getTableContainerImpl().getTableByName(name);
- }
-
- public java.util.List<Table> getTableList() {
- return getTableContainerImpl().getTableList();
- }
-
- public TableBuilder getTableBuilder() {
- return getTableContainerImpl().getTableBuilder();
- }
-
- protected TableContainer getTableContainerImpl() {
- if (tableContainerImpl == null) {
- tableContainerImpl = new TableContainerImpl();
- }
- return tableContainerImpl;
- }
-
- private class TableContainerImpl extends AbstractTableContainer {
-
- public OdfElement getTableContainerElement() {
- OdfElement containerElement = null;
- try {
- containerElement = getContentRoot();
- } catch (Exception e) {
- Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
- }
- return containerElement;
- }
- }
-
- /**
- * Return the component repository of this document.
- *
- * @return the component repository of this document.
- */
- protected IdentityHashMap<OdfElement, Component> getComponentMap() {
- return mComponentRepository;
- }
-
- /**
- * Construct a
- *
- *
- * <code>TableTemplate<code> feature by extracting style template from an pre-defined table in a foreign document. The styles loaded by the template will be copied into the document as well and can be referenced by table directly.
- * <p>
- * The imported table need to be at least a 5*5 table (e.g. A1E5). Each type of style in the template will be set according to the style reference in a specific table cell, as following:
- * <br>first column - A2
- * <br>last column - E2
- * <br>first row - A2
- * <br>last row - E2
- * <br>even rows - B3
- * <br>odd rows - B2
- * <br>even columns - C2
- * <br>odd columns - B2
- * <br>body - B2
- * <br>first-row-start-column -A1
- * <br>first-row-end-column -E1
- * <br>last-row-start-column -A5
- * <br>last-row-end-column -E5
- *
- * @param templateFileInputStream
- * - the InputStream of the ODF document.
- * @param tableName
- * - the table name which will be used to load styles as template
- * @throws Exception
- * - if content DOM could not be initialized
- */
- public TableTemplate LoadTableTemplateFromForeignTable(
- InputStream templateFileInputStream, String tableName) throws Exception {
-
- Document doc = Document.loadDocument(templateFileInputStream);
-
- if (doc == null)
- throw new IllegalStateException(
- "Cannot load specified template file.");
-
- Table table = doc.getTableByName(tableName);
- if (table == null)
- throw new IllegalStateException(
- "Cannot load table template from specified file.");
-
- if (table.getRowCount() < 5 || table.getColumnCount() < 5)
- throw new IllegalStateException(
- "The template cannot be loaded. It should be at least a 5*5 table.");
-
- TableTemplate template = new TableTemplate(getStylesDom()
- .newOdfElement(TableTableTemplateElement.class));
-
- // first-row-start-column
- Cell cell = table.getCellByPosition(0, 0);
- cell.getParagraphIterator().hasNext();
- cell.getParagraphIterator().next().getStyleName();
- Paragraph para = cell.getParagraphByIndex(0, false);
- String paraStyle = (para != null ? para.getStyleName() : null);
- template.setExtendedStyleByType(
- TableTemplate.ExtendedStyleType.FIRSTROWSTARTCOLUM, cell
- .getStyleName(), paraStyle);
- TableTableCellElementBase oldCellEle = cell.getOdfElement();
- TableTableCellElementBase newCellEle = (TableTableCellElementBase) oldCellEle
- .cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // first-row-end-column
- cell = table.getCellByPosition(4, 0);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setExtendedStyleByType(
- TableTemplate.ExtendedStyleType.FIRSTROWENDCOLUMN, cell
- .getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // last-row-start-column
- cell = table.getCellByPosition(0, 4);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setExtendedStyleByType(
- TableTemplate.ExtendedStyleType.LASTROWSTARTCOLUMN, cell
- .getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // last-row-end-column
- cell = table.getCellByPosition(4, 4);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setExtendedStyleByType(
- TableTemplate.ExtendedStyleType.LASTROWENDCOLUMN, cell
- .getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // first column
- cell = table.getCellByPosition(0, 1);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setTableFirstColumnStyle(cell.getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // last column
- cell = table.getCellByPosition(4, 2);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setTableLastColumnStyle(cell.getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // first row
- cell = table.getCellByPosition(1, 0);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setTableFirstRowStyle(cell.getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // last row
- cell = table.getCellByPosition(1, 4);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setTableLastRowStyle(cell.getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // body (=odd row/column)
- cell = table.getCellByPosition(1, 1);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setTableBodyStyle(cell.getStyleName(), paraStyle);
- template.setTableOddRowsStyle(cell.getStyleName(), paraStyle);
- template.setTableOddColumnsStyle(cell.getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // even row
- cell = table.getCellByPosition(1, 2);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setTableEvenRowsStyle(cell.getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- // even row
- cell = table.getCellByPosition(2, 1);
- para = cell.getParagraphByIndex(0, false);
- paraStyle = (para != null ? para.getStyleName() : null);
- template.setTableEvenColumnsStyle(cell.getStyleName(), paraStyle);
- oldCellEle = cell.getOdfElement();
- newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
- copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
-
- return template;
- }
-
-}
\ No newline at end of file
+/************************************************************************
+ *
+ * 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.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+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.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import org.odftoolkit.odfdom.dom.OdfContentDom;
+import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
+import org.odftoolkit.odfdom.dom.OdfSchemaConstraint;
+import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
+import org.odftoolkit.odfdom.dom.OdfStylesDom;
+import org.odftoolkit.odfdom.dom.attribute.text.TextAnchorTypeAttribute;
+import org.odftoolkit.odfdom.dom.element.OdfStyleBase;
+import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement;
+import org.odftoolkit.odfdom.dom.element.draw.DrawPageElement;
+import org.odftoolkit.odfdom.dom.element.office.OfficeBodyElement;
+import org.odftoolkit.odfdom.dom.element.office.OfficeMasterStylesElement;
+import org.odftoolkit.odfdom.dom.element.style.StyleFontFaceElement;
+import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
+import org.odftoolkit.odfdom.dom.element.table.TableTableCellElementBase;
+import org.odftoolkit.odfdom.dom.element.table.TableTableTemplateElement;
+import org.odftoolkit.odfdom.dom.element.text.TextPElement;
+import org.odftoolkit.odfdom.dom.element.text.TextSectionElement;
+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.OdfOfficeAutomaticStyles;
+import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
+import org.odftoolkit.odfdom.incubator.doc.style.OdfDefaultStyle;
+import org.odftoolkit.odfdom.pkg.MediaType;
+import org.odftoolkit.odfdom.pkg.OdfElement;
+import org.odftoolkit.odfdom.pkg.OdfFileDom;
+import org.odftoolkit.odfdom.pkg.OdfName;
+import org.odftoolkit.odfdom.pkg.OdfNamespace;
+import org.odftoolkit.odfdom.pkg.OdfPackage;
+import org.odftoolkit.odfdom.pkg.OdfPackageDocument;
+import org.odftoolkit.odfdom.pkg.OdfValidationException;
+import org.odftoolkit.odfdom.pkg.manifest.OdfFileEntry;
+import org.odftoolkit.odfdom.type.Duration;
+import org.odftoolkit.simple.meta.Meta;
+import org.odftoolkit.simple.table.AbstractTableContainer;
+import org.odftoolkit.simple.table.Cell;
+import org.odftoolkit.simple.table.Table;
+import org.odftoolkit.simple.table.Table.TableBuilder;
+import org.odftoolkit.simple.table.TableContainer;
+import org.odftoolkit.simple.table.TableTemplate;
+import org.odftoolkit.simple.text.Paragraph;
+import org.odftoolkit.simple.text.Section;
+import org.w3c.dom.Attr;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.ErrorHandler;
+
+/**
+ * This abstract class is representing one of the possible ODF documents
+ */
+public abstract class Document extends OdfSchemaDocument implements TableContainer {
+ // Static parts of file references
+ private static final String SLASH = "/";
+ private OdfMediaType mMediaType;
+ private Meta mOfficeMeta;
+ private long documentOpeningTime;
+ private TableContainerImpl tableContainerImpl;
+ private static final Pattern CONTROL_CHAR_PATTERN = Pattern.compile("\\p{Cntrl}");
+ private static final String EMPTY_STRING = "";
+
+ private IdentityHashMap<OdfElement, Component> mComponentRepository = new IdentityHashMap<OdfElement, Component>();
+
+ // FIXME: This field is only used in method copyResourcesFrom to improve
+ // copy performance, should not be used in any other way.
+ // methods loadDocument(String documentPath) and loadDocument(File file)
+ // will initialize it.
+ // This field and its methods should be removed after ODFDOM supplies batch
+ // copy.
+ private File mFile = null;
+
+ // if the copy foreign slide for several times,
+ // the same style might be copied for several times with the different name
+ // so use styleRenameMap to keep track the renamed style so we can reuse the
+ // style,
+ // rather than new several styles which only have the different style names.
+ // while if the style elements really have the same style name but with
+ // different content
+ // such as that these style elements are from different document
+ // so the value for each key should be a list
+ private HashMap<String, List<String>> styleRenameMap = new HashMap<String, List<String>>();
+ // the map is used to record if the renamed style name is appended to the
+ // current dom
+ private HashMap<String, Boolean> styleAppendMap = new HashMap<String, Boolean>();
+
+ // the object rename map for image.
+ // can not easily recognize if the embedded document are the same.
+ // private HashMap<String, String> objectRenameMap = new HashMap<String,
+ // String>();
+
+ // Using static factory instead of constructor
+ protected Document(OdfPackage pkg, String internalPath, OdfMediaType mediaType) {
+ super(pkg, internalPath, mediaType.getMediaTypeString());
+ mMediaType = mediaType;
+ // set document opening time.
+ documentOpeningTime = System.currentTimeMillis();
+ }
+
+ /**
+ * This enum contains all possible media types of Document 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 either not yet supported or not an ODF mediatype!");
+ }
+ }
+ return odfMediaType;
+ }
+ }
+
+ /**
+ * Loads an Document from the given resource. NOTE: Initial meta data 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 Document document or NULL if the media type is not supported
+ * by SIMPLE.
+ * @throws java.lang.Exception
+ * - if the document could not be created.
+ */
+ protected static Document loadTemplate(Resource res, OdfMediaType odfMediaType) throws Exception {
+ InputStream in = res.createInputStream();
+ OdfPackage pkg = null;
+ try {
+ pkg = OdfPackage.loadPackage(in);
+ } finally {
+ in.close();
+ }
+ Document newDocument = newDocument(pkg, ROOT_DOCUMENT_PATH, odfMediaType);
+ // add initial meta data to new document.
+ initializeMetaData(newDocument);
+ return newDocument;
+ }
+
+ /**
+ * Loads a Document from the provided path.
+ *
+ * <p>
+ * Document relies on the file being available for read access over the
+ * whole life cycle of Document.
+ * </p>
+ *
+ * @param documentPath
+ * - the path from where the document can be loaded
+ * @param password
+ * - file password.
+ * @return the 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.
+ * @since 0.8
+ */
+ public static Document loadDocument(String documentPath, String password) throws Exception {
+ File file = new File(documentPath);
+ return loadDocument(file, password);
+ }
+
+ /**
+ * Loads a Document from the provided path.
+ *
+ * <p>
+ * Document relies on the file being available for read access over the
+ * whole life cycle of Document.
+ * </p>
+ *
+ * @param documentPath
+ * - the path from where the document can be loaded
+ * @return the 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 Document loadDocument(String documentPath) throws Exception {
+ File file = new File(documentPath);
+ return loadDocument(file);
+ }
+
+ /**
+ * Creates a Document from the Document provided by a resource Stream.
+ *
+ * <p>
+ * Since an InputStream does not provide the arbitrary (non sequentiell)
+ * read access needed by Document, 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 Document loadDocument(InputStream inStream) throws Exception {
+ return loadDocument(OdfPackage.loadPackage(inStream));
+ }
+
+ /**
+ * Creates a Document from the Document provided by a File.
+ *
+ * <p>
+ * Document relies on the file being available for read access over the
+ * whole lifecycle of Document.
+ * </p>
+ *
+ * @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 Document loadDocument(File file) throws Exception {
+ Document doc = loadDocument(OdfPackage.loadPackage(file));
+ doc.setFile(file);
+ return doc;
+ }
+
+ /**
+ * Creates a Document from the Document provided by a File.
+ *
+ * <p>
+ * Document relies on the file being available for read access over the
+ * whole lifecycle of Document.
+ * </p>
+ *
+ * @param file
+ * - a file representing the ODF document.
+ * @param password
+ * - file password.
+ * @return the document created from the given File
+ * @throws java.lang.Exception
+ * - if the document could not be created.
+ * @since 0.7
+ */
+ public static Document loadDocument(File file, String password) throws Exception {
+ Document doc = loadDocument(OdfPackage.loadPackage(file, password, null));
+ doc.setFile(file);
+ return doc;
+ }
+
+ /**
+ * Creates a Document from the Document provided by an 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 Document loadDocument(OdfPackage odfPackage) throws Exception {
+ return loadDocument(odfPackage, ROOT_DOCUMENT_PATH);
+ }
+
+ /**
+ * Creates a Document from the Document 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.
+ * @return the root document of the given OdfPackage
+ * @throws java.lang.Exception
+ * - if the ODF document could not be created.
+ */
+ public static Document loadDocument(OdfPackage odfPackage, String internalPath) throws Exception {
+ String documentMediaType = odfPackage.getMediaTypeString(internalPath);
+ if (documentMediaType == null) {
+ throw new IllegalArgumentException("Given internalPath '" + internalPath + "' is an illegal or inappropriate argument.");
+ }
+ OdfMediaType odfMediaType = OdfMediaType.getOdfMediaType(documentMediaType);
+ 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);
+ }
+
+ /**
+ * Sets password of this document.
+ *
+ * @param password
+ * the password of this document.
+ * @since 0.8
+ */
+ public void setPassword(String password) {
+ getPackage().setPassword(password);
+ }
+
+ // return null if the media type can not be recognized.
+ private static Document loadDocumentFromTemplate(OdfMediaType odfMediaType) throws Exception {
+
+ switch (odfMediaType) {
+ case TEXT:
+ case TEXT_TEMPLATE:
+ case TEXT_MASTER:
+ case TEXT_WEB:
+ // documentTemplate = TextDocument.EMPTY_TEXT_DOCUMENT_RESOURCE;
+ TextDocument document = TextDocument.newTextDocument();
+ document.changeMode(TextDocument.OdfMediaType.TEXT_WEB);
+ return document;
+
+ case SPREADSHEET:
+ SpreadsheetDocument spreadsheet = SpreadsheetDocument.newSpreadsheetDocument();
+ spreadsheet.changeMode(SpreadsheetDocument.OdfMediaType.SPREADSHEET);
+ return spreadsheet;
+
+ case SPREADSHEET_TEMPLATE:
+ SpreadsheetDocument spreadsheettemplate = SpreadsheetDocument.newSpreadsheetDocument();
+ spreadsheettemplate.changeMode(SpreadsheetDocument.OdfMediaType.SPREADSHEET_TEMPLATE);
+ return spreadsheettemplate;
+
+ case PRESENTATION:
+ PresentationDocument presentation = PresentationDocument.newPresentationDocument();
+ presentation.changeMode(PresentationDocument.OdfMediaType.PRESENTATION);
+ return presentation;
+
+ case PRESENTATION_TEMPLATE:
+ PresentationDocument presentationtemplate = PresentationDocument.newPresentationDocument();
+ presentationtemplate.changeMode(PresentationDocument.OdfMediaType.PRESENTATION_TEMPLATE);
+ return presentationtemplate;
+
+ case GRAPHICS:
+ GraphicsDocument graphics = GraphicsDocument.newGraphicsDocument();
+ graphics.changeMode(GraphicsDocument.OdfMediaType.GRAPHICS);
+ return graphics;
+
+ case GRAPHICS_TEMPLATE:
+ GraphicsDocument graphicstemplate = GraphicsDocument.newGraphicsDocument();
+ graphicstemplate.changeMode(GraphicsDocument.OdfMediaType.GRAPHICS_TEMPLATE);
+ return graphicstemplate;
+
+ case CHART:
+ ChartDocument chart = ChartDocument.newChartDocument();
+ chart.changeMode(ChartDocument.OdfMediaType.CHART);
+ return chart;
+
+ case CHART_TEMPLATE:
+ ChartDocument charttemplate = ChartDocument.newChartDocument();
+ charttemplate.changeMode(ChartDocument.OdfMediaType.CHART_TEMPLATE);
+ return charttemplate;
+
+ // case IMAGE:
+ // case IMAGE_TEMPLATE:
+
+ default:
+ throw new IllegalArgumentException("Given mediaType '" + odfMediaType.toString() + "' is either not yet supported or not an ODF mediatype!");
+ }
+ }
+
+ /**
+ * 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 Document newDocument(OdfPackage pkg, String internalPath, OdfMediaType odfMediaType) {
+ Document newDoc = null;
+ switch (odfMediaType) {
+ case TEXT:
+ newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT);
+ break;
+
+ case TEXT_TEMPLATE:
+ newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_TEMPLATE);
+ break;
+
+ case TEXT_MASTER:
+ newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_MASTER);
+ break;
+
+ case TEXT_WEB:
+ newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_WEB);
+ break;
+
+ case SPREADSHEET:
+ newDoc = new SpreadsheetDocument(pkg, internalPath, SpreadsheetDocument.OdfMediaType.SPREADSHEET);
+ break;
+
+ case SPREADSHEET_TEMPLATE:
+ newDoc = new SpreadsheetDocument(pkg, internalPath, SpreadsheetDocument.OdfMediaType.SPREADSHEET_TEMPLATE);
+ break;
+
+ case PRESENTATION:
+ newDoc = new PresentationDocument(pkg, internalPath, PresentationDocument.OdfMediaType.PRESENTATION);
+ break;
+
+ case PRESENTATION_TEMPLATE:
+ newDoc = new PresentationDocument(pkg, internalPath, PresentationDocument.OdfMediaType.PRESENTATION_TEMPLATE);
+ break;
+
+ case GRAPHICS:
+ newDoc = new GraphicsDocument(pkg, internalPath, GraphicsDocument.OdfMediaType.GRAPHICS);
+ break;
+
+ case GRAPHICS_TEMPLATE:
+ newDoc = new GraphicsDocument(pkg, internalPath, GraphicsDocument.OdfMediaType.GRAPHICS_TEMPLATE);
+ break;
+
+ case CHART:
+ newDoc = new ChartDocument(pkg, internalPath, ChartDocument.OdfMediaType.CHART);
+ break;
+
+ case CHART_TEMPLATE:
+ newDoc = new ChartDocument(pkg, internalPath, ChartDocument.OdfMediaType.CHART_TEMPLATE);
+ break;
+ // case IMAGE:
+ // case IMAGE_TEMPLATE:
+
+ 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
+ * path to the ODF document within the package. The path is
+ * relative to the current document.
+ * @return an embedded Document
+ */
+ public Document getEmbeddedDocument(String documentPath) {
+ String internalPath = getDocumentPath() + documentPath;
+ internalPath = normalizeDocumentPath(internalPath);
+ Document embeddedDocument = (Document) mPackage.getCachedDocument(internalPath);
+ // if the document was not already loaded, fine mimetype and create a
+ // new instance
+ if (embeddedDocument == null) {
+ String mediaTypeString = getMediaTypeString();
+ OdfMediaType odfMediaType = OdfMediaType.getOdfMediaType(mediaTypeString);
+ if (odfMediaType == null) {
+ embeddedDocument = newDocument(mPackage, internalPath, odfMediaType);
+ } else {
+ try {
+ String documentMediaType = mPackage.getMediaTypeString(internalPath);
+ odfMediaType = OdfMediaType.getOdfMediaType(documentMediaType);
+ if (odfMediaType == null) {
+ return null;
+ }
+ embeddedDocument = Document.loadDocument(mPackage, internalPath);
+ } catch (Exception ex) {
+ Logger.getLogger(OdfPackageDocument.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ }
+ return embeddedDocument;
+ }
+
+ /**
+ * Method returns all embedded OdfPackageDocuments, which match a valid
+ * OdfMediaType, of the root OdfPackageDocument.
+ *
+ * @return a list with all embedded documents of the root OdfPackageDocument
+ */
+ // ToDo: (Issue 219 - PackageRefactoring) - Better return Path of
+ // Documents??
+ public List<Document> getEmbeddedDocuments() {
+ List<Document> embeddedObjects = new ArrayList<Document>();
+ // ToDo: (Issue 219 - PackageRefactoring) - Algorithm enhancement:
+ // Instead going through all the files for each mimetype, better
+ // Check all files, which have a mimetype if it is one of the desired,
+ // perhaps start with ODF prefix
+ for (OdfMediaType mediaType : OdfMediaType.values()) {
+ embeddedObjects.addAll(getEmbeddedDocuments(mediaType));
+ }
+ return embeddedObjects;
+ }
+
+ /**
+ * Method returns all embedded OdfPackageDocuments of the root
+ * OdfPackageDocument matching the according MediaType. This is done by
+ * matching the subfolder entries of the manifest file with the given
+ * OdfMediaType.
+ *
+ * @param mediaType
+ * media type which is used as a filter
+ * @return embedded documents of the root OdfPackageDocument matching the
+ * given media type
+ */
+ public List<Document> getEmbeddedDocuments(OdfMediaType mediaType) {
+ String wantedMediaString = null;
+ if (mediaType != null) {
+ wantedMediaString = mediaType.getMediaTypeString();
+ }
+ List<Document> embeddedObjects = new ArrayList<Document>();
+ // 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)) {
+ normalizeDocumentPath(path);
+ embeddedObjects.add(getEmbeddedDocument(path));
+ }
+ } else {
+ // test if any ODF mediatype matches the current
+ for (OdfMediaType type : OdfMediaType.values()) {
+ if (entryMediaType.equals(type.getMediaTypeString())) {
+ embeddedObjects.add(getEmbeddedDocument(path));
+ }
+ }
+ }
+ }
+ }
+ }
+ return embeddedObjects;
+ }
+
+ /**
+ * Embed an OdfPackageDocument to the current OdfPackageDocument. All the
+ * file entries of child document will be embedded as well to the current
+ * document package.
+ *
+ * @param documentPath
+ * to the directory the ODF document should be inserted (relative
+ * to the current document).
+ * @param sourceDocument
+ * the OdfPackageDocument to be embedded.
+ */
+ public void insertDocument(OdfPackageDocument sourceDocument, String documentPath) {
+ super.insertDocument(sourceDocument, documentPath);
+ }
+
+ /**
+ * Sets the media type of the Document
+ *
+ * @param odfMediaType
+ * media type to be set
+ */
+ protected void setOdfMediaType(OdfMediaType odfMediaType) {
+ mMediaType = odfMediaType;
+ super.setMediaTypeString(odfMediaType.getMediaTypeString());
+ }
+
+ /**
+ * Gets the media type of the Document
+ */
+ 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 Meta getOfficeMetadata() {
+ if (mOfficeMeta == null) {
+ try {
+ mOfficeMeta = new Meta(getMetaDom());
+ } catch (Exception ex) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+ return mOfficeMeta;
+ }
+
+ /**
+ * Save the document to an OutputStream. Delegate to the root document and
+ * save possible embedded Documents.
+ *
+ * <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 {
+ // 2DO FLUSH AND SAVE IN PACKAGE
+ flushDoms();
+ updateMetaData();
+ if (!isRootDocument()) {
+ Document 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 {
+ // 2DO MOVE CACHE TO PACKAGE
+ // // the root document only have to flush the DOM of all open child
+ // documents
+ // flushAllDOMs();
+ 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
+ */
+ public void save(File file) throws Exception {
+ // 2DO FLUSH AND SAVE IN PACKAGE
+ flushDoms();
+ updateMetaData();
+ if (!isRootDocument()) {
+ Document 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);
+ }
+ }
+
+ /**
+ * Save the document to a given file with given password.
+ *
+ * <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.
+ * @param file
+ * the password of this document.
+ *
+ * @throws java.lang.Exception
+ * if the document could not be saved
+ * @since 0.8
+ */
+ public void save(File file, String password) throws Exception {
+ // 2DO FLUSH AND SAVE IN PACKAGE
+ flushDoms();
+ updateMetaData();
+ if (!isRootDocument()) {
+ Document newDoc = loadDocumentFromTemplate(getOdfMediaType());
+ newDoc.insertDocument(this, ROOT_DOCUMENT_PATH);
+ newDoc.updateMetaData();
+ newDoc.mPackage.setPassword(password);
+ 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 {
+ mPackage.setPassword(password);
+ 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. Note that this will not close any cached documents.
+ */
+ @Override
+ public void close() {
+ // set all member variables explicit to null
+ mMediaType = null;
+ mOfficeMeta = null;
+ mComponentRepository.clear();
+ super.close();
+ }
+
+ /**
+ * Get the content root of a document.
+ *
+ * You may prefer to use the getContentRoot methods of subclasses of
+ * Document. Their return parameters are already casted to respective
+ * subclasses of OdfElement.
+ *
+ * @param clazz
+ * 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")
+ protected <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
+ * Document.
+ *
+ * @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 Document.
+ *
+ * @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 SpreadsheetDocument) {
+ TableTableCellElement lastCell = (TableTableCellElement) xpath.evaluate("//table:table-cell[last()]", contentDom, XPathConstants.NODE);
+ lastCell.appendChild(drawFrame);
+ drawFrame.removeAttribute("text:anchor-type");
+
+ } else if (this instanceof TextDocument) {
+ TextPElement lastPara = (TextPElement) xpath.evaluate("//text:p[last()]", contentDom, XPathConstants.NODE);
+ if (lastPara == null) {
+ lastPara = ((TextDocument) this).newParagraph();
+ }
+ lastPara.appendChild(drawFrame);
+ drawFrame.setTextAnchorTypeAttribute(TextAnchorTypeAttribute.Value.PARAGRAPH.toString());
+ } else if (this instanceof PresentationDocument) {
+ 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(Document.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return null;
+ }
+
+ /**
+ * Meta data about the document will be initialized. Following metadata data
+ * is being added:
+ * <ul>
+ * <li>The initial creator name will be the Java user.name System property.</li>
+ * <li>The date and time when this document was created using the current
+ * data.</li>
+ * <li>The number of times this document has been edited.</li>
+ * <li>The default language will be the Java user.language System property.</li>
+ * </ul>
+ *
+ * @param newDoc
+ * the Document object which need to initialize meta data.
+ *
+ * TODO:This method will be moved to OdfMetadata class. see
+ * http://odftoolkit.org/bugzilla/show_bug.cgi?id=204
+ */
+ private static void initializeMetaData(Document newDoc) {
+ Meta metaData = newDoc.getOfficeMetadata();
+ // add initial-creator info.
+ String creator = System.getProperty("user.name");
+ metaData.setInitialCreator(creator);
+ // add creation-date info.
+ Calendar calendar = Calendar.getInstance();
+ metaData.setCreationDate(calendar);
+ // add editing-cycles info.
+ metaData.setEditingCycles(0);
+ // add language info.
+ String language = System.getProperty("user.language");
+ if (language != null) {
+ metaData.setLanguage(language);
+ }
+ }
+
+ /**
+ * 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
+ */
+ private void updateMetaData() throws Exception {
+ if (mMetaDom != null) {
+ Meta metaData = getOfficeMetadata();
+ String creator = System.getProperty("user.name");
+ // update creator info.
+ metaData.setCreator(creator);
+ // update date info.
+ Calendar calendar = Calendar.getInstance();
+ metaData.setDcdate(calendar);
+ // update editing-cycles info.
+ Integer cycle = metaData.getEditingCycles();
+ if (cycle != null) {
+ metaData.setEditingCycles(++cycle);
+ } else {
+ metaData.setEditingCycles(1);
+ }
+ // update editing-duration info.
+ 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(Document.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 types:
+ *
+ * <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. 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 ScriptType {
+ /**
+ * 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, getScriptType(locale));
+ }
+
+ public static ScriptType getScriptType(Locale locale) {
+ String language = locale.getLanguage();
+ if (CJKLanguage.contains(language))
+ return ScriptType.CJK;
+ if (CTLLanguage.contains(language))
+ return ScriptType.CTL;
+ return ScriptType.WESTERN;
+
+ }
+
+ /**
+ * <p>
+ * Set a locale of a specific script type.
+ * <p>
+ * If the locale is not belone to the script type, nothing will happen.
+ *
+ * @param locale
+ * - Locale information
+ * @param scriptType
+ * - The script type
+ */
+ public void setLocale(Locale locale, ScriptType scriptType) {
+ try {
+ switch (scriptType) {
+ case WESTERN:
+ setWesternLanguage(locale);
+ break;
+ case CJK:
+ setDefaultAsianLanguage(locale);
+ break;
+ case CTL:
+ setDefaultComplexLanguage(locale);
+ break;
+ }
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed to set locale", e);
+ }
+ }
+
+ /**
+ * <p>
+ * Get a locale information of a specific script type.
+ *
+ * @param scriptType
+ * - The script type
+ * @return the Locale information
+ */
+ public Locale getLocale(ScriptType scriptType) {
+ try {
+ switch (scriptType) {
+ 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(Document.class.getName()).log(Level.SEVERE, "Failed to get locale", e);
+ }
+ 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 setWesternLanguage(Locale locale) throws Exception {
+ if (getScriptType(locale) != ScriptType.WESTERN)
+ return;
+
+ 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());
+ }
+ }
+ }
+ }
+
+ 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 return an instance of 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.
+ */
+
+ /**
+ * 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 {
+ if (getScriptType(locale) != ScriptType.CJK)
+ return;
+ String user_language = locale.getLanguage();
+ if (!user_language.equals(Locale.CHINESE.getLanguage()) && !user_language.equals(Locale.TRADITIONAL_CHINESE.getLanguage())
+ && !user_language.equals(Locale.JAPANESE.getLanguage()) && !user_language.equals(Locale.KOREAN.getLanguage()))
+ return;
+
+ 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 {
+ if (getScriptType(locale) != ScriptType.CTL)
+ return;
+ 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());
+ }
+ }
+ }
+ }
+
+ /**
+ * This method will search both the document content and header/footer,
+ * return an iterator of section objects.
+ * <p>
+ * The sections defined in embed document won't be covered.
+ *
+ * @return an iterator of Section objects
+ */
+ public Iterator<Section> getSectionIterator() {
+ TextSectionElement element;
+ ArrayList<Section> list = new ArrayList<Section>();
+ try {
+ // search in content.xml
+ OdfElement root = getContentDom().getRootElement();
+ OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root);
+ NodeList sectionList = officeBody.getElementsByTagNameNS(OdfDocumentNamespace.TEXT.getUri(), "section");
+ for (int i = 0; i < sectionList.getLength(); i++) {
+ element = (TextSectionElement) sectionList.item(i);
+ list.add(Section.getInstance(element));
+ }
+
+ // Search in style.xml
+ root = getStylesDom().getRootElement();
+ OfficeMasterStylesElement masterStyle = OdfElement.findFirstChildNode(OfficeMasterStylesElement.class, root);
+ sectionList = masterStyle.getElementsByTagNameNS(OdfDocumentNamespace.TEXT.getUri(), "section");
+ for (int i = 0; i < sectionList.getLength(); i++) {
+ element = (TextSectionElement) sectionList.item(i);
+ list.add(Section.getInstance(element));
+ }
+
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in sectionIterator", e);
+ }
+ return list.iterator();
+ }
+
+ /**
+ * This method will search both the document content and header/footer,
+ * return a section with a specific name.
+ * <p>
+ * This method won't search in the embed document.
+ * <p>
+ * Null will be returned if there is no section found.
+ *
+ * @param name
+ * - the name of a section
+ * @return a section object with a specific name
+ */
+ public Section getSectionByName(String name) {
+ TextSectionElement element;
+ try {
+ OdfElement root = getContentDom().getRootElement();
+ OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root);
+ XPath xpath = getContentDom().getXPath();
+ String xpathValue = ".//text:section[@text:name=\"" + name + "\"]";
+ element = (TextSectionElement) xpath.evaluate(xpathValue, officeBody, XPathConstants.NODE);
+ if (element != null) {
+ return Section.getInstance(element);
+ }
+
+ root = getStylesDom().getRootElement();
+ OfficeMasterStylesElement masterStyle = OdfElement.findFirstChildNode(OfficeMasterStylesElement.class, root);
+ xpath = getStylesDom().getXPath();
+ element = (TextSectionElement) xpath.evaluate(".//text:section[@text:name=\"" + name + "\"]", masterStyle, XPathConstants.NODE);
+ if (element != null) {
+ return Section.getInstance(element);
+ }
+
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in getSectionByName", e);
+ }
+
+ return null;
+
+ }
+
+ /**
+ * Remove an ODF element from the document. All the resources that are only
+ * related with this element will be removed at the same time.
+ *
+ * @param odfElement
+ * - the odf element that would be moved.
+ */
+ public boolean removeElementLinkedResource(OdfElement odfElement) {
+ boolean success = deleteLinkedRef(odfElement);
+ success &= deleteStyleRef(odfElement);
+ return success;
+ }
+
+ /**
+ * Return a unique string with a character "a" followed by randomly
+ * generating 6 hex numbers
+ *
+ * @return a unique string
+ */
+ String makeUniqueName() {
+ return String.format("a%06x", (int) (Math.random() * 0xffffff));
+ }
+
+ private String getNewUniqueString(String oldStr) {
+ int lastIndex = oldStr.lastIndexOf("-");
+ if (lastIndex == -1) {
+ return oldStr + "-" + makeUniqueName();
+ }
+ String suffix = oldStr.substring(lastIndex + 1);
+ if (suffix.matches("a[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]")) {
+ return oldStr.substring(0, lastIndex + 1) + makeUniqueName();
+ } else
+ return oldStr + "-" + makeUniqueName();
+ }
+
+ private void updateAttribute(Attr attr) {
+ String oldID = attr.getValue();
+ String newID = getNewUniqueString(oldID);
+ attr.setValue(newID);
+ }
+
+ /**
+ * Make a content copy of the specified element, and the returned element
+ * should have the specified ownerDocument.
+ *
+ * @param element
+ * The element that need to be copied
+ * @param dom
+ * The specified DOM tree that the returned element belong to
+ * @param deep
+ * If true, recursively clone the subtree under the element,
+ * false, only clone the element itself
+ * @return Returns a duplicated element which is not in the DOM tree with
+ * the specified element
+ */
+ Node cloneForeignElement(Node element, OdfFileDom dom, boolean deep) {
+ if (element instanceof OdfElement) {
+ OdfElement cloneElement = dom.createElementNS(((OdfElement) element).getOdfName());
+
+ NamedNodeMap attributes = element.getAttributes();
+ if (attributes != null) {
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Node item = attributes.item(i);
+ String qname = null;
+ String prefix = item.getPrefix();
+ if (prefix == null) {
+ qname = item.getLocalName();
+ cloneElement.setAttribute(qname, item.getNodeValue());
+ } else {
+ qname = prefix + ":" + item.getLocalName();
+ cloneElement.setAttributeNS(item.getNamespaceURI(), qname, item.getNodeValue());
+ }
+ }
+ }
+
+ if (deep) {
+ Node childNode = element.getFirstChild();
+ while (childNode != null) {
+ cloneElement.appendChild(cloneForeignElement(childNode, dom, true));
+ childNode = childNode.getNextSibling();
+ }
+ }
+
+ return cloneElement;
+ } else {
+ return dom.createTextNode(element.getNodeValue());
+ }
+
+ }
+
+ /**
+ * This method will update all the attribute "xml:id" to make it unique
+ * within the whole document content
+ * <p>
+ * This method is usually be invoked before inserting a copied ODF element
+ * to document content.
+ *
+ * @param element
+ * - the element that need to be inserted.
+ */
+ void updateXMLIds(OdfElement element) {
+ try {
+ XPath xpath = getContentDom().getXPath();
+ String xpathValue = "//*[@xml:id]";
+ NodeList childList = (NodeList) xpath.evaluate(xpathValue, element, XPathConstants.NODESET);
+ if (childList == null)
+ return;
+
+ for (int i = 0; i < childList.getLength(); i++) {
+ OdfElement ele = (OdfElement) childList.item(i);
+ Attr attri = ele.getAttributeNodeNS(OdfDocumentNamespace.XML.getUri(), "id");
+ updateAttribute(attri);
+ }
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in updateXMLIds", e);
+ }
+ }
+
+ /**
+ * This method will update all the attribute
+ * "text:name","table:name","draw:name","chart:name", to make it unique
+ * within the whole document content.
+ * <p>
+ * This method is usually be invoked before inserting a copied ODF element
+ * to document content.
+ *
+ * @param element
+ * - the element that need to be inserted.
+ */
+ // anim:name, chart:name, config:name, office:name, presentation:name,
+ // svg:name,
+ void updateNames(OdfElement element) {
+ try {
+ XPath xpath = getContentDom().getXPath();
+ String xpathValue = "descendant-or-self::node()[@text:name|@table:name|@draw:name|@chart:name]";
+ NodeList childList = (NodeList) xpath.evaluate(xpathValue, element, XPathConstants.NODESET);
+ if (childList == null)
+ return;
+ for (int i = 0; i < childList.getLength(); i++) {
+ OdfElement ele = (OdfElement) childList.item(i);
+ Attr attri = ele.getAttributeNodeNS(OdfDocumentNamespace.TEXT.getUri(), "name");
+ if (attri != null)
+ updateAttribute(attri);
+ attri = ele.getAttributeNodeNS(OdfDocumentNamespace.TABLE.getUri(), "name");
+ if (attri != null)
+ updateAttribute(attri);
+ if (ele instanceof DrawFrameElement)// only update draw:frame
+ {
+ attri = ele.getAttributeNodeNS(OdfDocumentNamespace.DRAW.getUri(), "name");
+ if (attri != null)
+ updateAttribute(attri);
+ }
+ }
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in updateXMLIds", e);
+ }
+ }
+
+ /**
+ * This method will copy the linked resource of the element which need to be
+ * copied, from the source package to the target package.
+ * <p>
+ * If the target package contains a resource with the same path and name,
+ * the name of the resource will be renamed.
+ * <p>
+ * This method will copy resources all in one batch.
+ *
+ * @param sourceCloneEle
+ * - the element that need to be copied
+ * @param srcDocument
+ * - the source document
+ */
+ void copyLinkedRefInBatch(OdfElement sourceCloneEle, Document srcDocument) {
+ try {
+ OdfFileDom fileDom = (OdfFileDom) sourceCloneEle.getOwnerDocument();
+ XPath xpath;
+ if (fileDom instanceof OdfContentDom) {
+ xpath = ((OdfContentDom) fileDom).getXPath();
+ } else {
+ xpath = ((OdfStylesDom) fileDom).getXPath();
+ }
+ // OdfPackageDocument srcDoc = fileDom.getDocument();
+ // new a map to put the original name and the rename string, in case
+ // that the same name might be referred by the slide several times.
+ HashMap<String, String> objectRenameMap = new HashMap<String, String>();
+ NodeList linkNodes = (NodeList) xpath.evaluate(".//*[@xlink:href]", sourceCloneEle, XPathConstants.NODESET);
+ for (int i = 0; i <= linkNodes.getLength(); i++) {
+ OdfElement object = null;
+ if (linkNodes.getLength() == i) {
+ if (sourceCloneEle.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) {
+ object = sourceCloneEle;
+ } else {
+ break;
+ }
+ } else {
+ object = (OdfElement) linkNodes.item(i);
+ }
+ String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
+ if (refObjPath != null && refObjPath.length() > 0) {
+ // the path of the object is start with "./"
+ boolean hasPrefix = false;
+ String prefix = "./";
+ String newObjPath;
+ if (refObjPath.startsWith(prefix)) {
+ refObjPath = refObjPath.substring(2);
+ hasPrefix = true;
+ }
+ // check if this linked resource has been copied
+ if (objectRenameMap.containsKey(refObjPath)) {
+ // this object has been copied already
+ newObjPath = objectRenameMap.get(refObjPath);
+ object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath);
+ continue;
+ }
+ // check if the current document contains the same path
+ OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath);
+ // note: if refObjPath is a directory, it must end with '/'
+ if (fileEntry == null) {
+ fileEntry = getPackage().getFileEntry(refObjPath + "/");
+ }
+ if (fileEntry != null) {
+ // rename the object path
+ newObjPath = objectRenameMap.get(refObjPath);
+ if (newObjPath == null) {
+ // if refObjPath still contains ".", it means that
+ // it has the suffix
+ // then change the name before the suffix string
+ int dotIndex = refObjPath.indexOf(".");
+ if (dotIndex != -1) {
+ newObjPath = refObjPath.substring(0, dotIndex) + "-" + makeUniqueName() + refObjPath.substring(dotIndex);
+ } else {
+ newObjPath = refObjPath + "-" + makeUniqueName();
+ }
+ objectRenameMap.put(refObjPath, newObjPath);
+ }
+ object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath);
+ } else
+ objectRenameMap.put(refObjPath, refObjPath);
+ }
+ }
+ // copy resource in batch
+ copyResourcesFrom(srcDocument, objectRenameMap);
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ }
+ }
+
+ /*****************************/
+ /*
+ * These codes are moved from OdfPackage, and should be removed till
+ * OdfPackage can provide a mechanism to copy resources in batch.
+ */
+ /*****************************/
+ private InputStream readAsInputStream(ZipInputStream inputStream) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ if (outputStream != null) {
+ byte[] buf = new byte[4096];
+ int r = 0;
+ while ((r = inputStream.read(buf, 0, 4096)) > -1) {
+ outputStream.write(buf, 0, r);
+ }
+ }
+ return new ByteArrayInputStream(outputStream.toByteArray());
+ }
+
+ private String normalizeFilePath(String internalPath) {
+ if (internalPath.equals(EMPTY_STRING)) {
+ String errMsg = "The internalPath given by parameter is an empty string!";
+ Logger.getLogger(OdfPackage.class.getName()).severe(errMsg);
+ throw new IllegalArgumentException(errMsg);
+ } else {
+ return normalizePath(internalPath);
+ }
+ }
+
+ private static final String DOUBLE_DOT = "..";
+ private static final String DOT = ".";
+ private static final String COLON = ":";
+ private static final Pattern BACK_SLASH_PATTERN = Pattern.compile("\\\\");
+ private static final Pattern DOUBLE_SLASH_PATTERN = Pattern.compile("//");
+
+ private String normalizePath(String path) {
+ if (path == null) {
+ String errMsg = "The internalPath given by parameter is NULL!";
+ Logger.getLogger(OdfPackage.class.getName()).severe(errMsg);
+ throw new IllegalArgumentException(errMsg);
+ } else if (!mightBeExternalReference(path)) {
+ if (path.equals(EMPTY_STRING)) {
+ path = SLASH;
+ } else {
+ // exchange all backslash "\" with a slash "/"
+ if (path.indexOf('\\') != -1) {
+ path = BACK_SLASH_PATTERN.matcher(path).replaceAll(SLASH);
+ }
+ // exchange all double slash "//" with a slash "/"
+ while (path.indexOf("//") != -1) {
+ path = DOUBLE_SLASH_PATTERN.matcher(path).replaceAll(SLASH);
+ }
+ // if directory replacements (e.g. ..) exist, resolve and remove
+ // them
+ if (path.indexOf("/.") != -1 || path.indexOf("./") != -1) {
+ path = removeChangeDirectories(path);
+ }
+ }
+ }
+ return path;
+ }
+
+ private boolean mightBeExternalReference(String internalPath) {
+ boolean isExternalReference = false;
+ // if the fileReference is a external relative documentURL..
+ if (internalPath.startsWith(DOUBLE_DOT) || // or absolute documentURL
+ // AND not root document
+ internalPath.startsWith(SLASH) && !internalPath.equals(SLASH) || // or
+ // absolute
+ // IRI
+ internalPath.contains(COLON)) {
+ isExternalReference = true;
+ }
+ return isExternalReference;
+ }
+
+ private String removeChangeDirectories(String path) {
+ boolean isDirectory = path.endsWith(SLASH);
+ StringTokenizer tokenizer = new StringTokenizer(path, SLASH);
+ int tokenCount = tokenizer.countTokens();
+ List<String> tokenList = new ArrayList<String>(tokenCount);
+ // add all paths to a list
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ tokenList.add(token);
+ }
+ if (!isDirectory) {
+ String lastPath = tokenList.get(tokenCount - 1);
+ if (lastPath.equals(DOT) || lastPath.equals(DOUBLE_DOT)) {
+ isDirectory = true;
+ }
+ }
+ String currentToken;
+ int removeDirLevel = 0;
+ StringBuilder out = new StringBuilder();
+ // work on the list from back to front
+ for (int i = tokenCount - 1; i >= 0; i--) {
+ currentToken = tokenList.get(i);
+ // every ".." will remove an upcoming path
+ if (currentToken.equals(DOUBLE_DOT)) {
+ removeDirLevel++;
+ } else if (currentToken.equals(DOT)) {
+ } else {
+ // if a path have to be remove, neglect current path
+ if (removeDirLevel > 0) {
+ removeDirLevel--;
+ } else {
+ // add the path segment
+ out.insert(0, SLASH);
+ out.insert(0, currentToken);
+ }
+ }
+ }
+ if (removeDirLevel > 0) {
+ return EMPTY_STRING;
+ } else {
+ if (!isDirectory) {
+ // remove trailing slash /
+ out.deleteCharAt(out.length() - 1);
+ }
+ return out.toString();
+ }
+ }
+
+ /*****************************/
+ // FIXME: These two methods are only used in method copyResourcesFrom to
+ // improve copy performance, should not be used in any other way.
+ // methods loadDocument(String documentPath) and loadDocument(File file)
+ // will initialize mFile.
+ // This field and these two methods should be removed after ODFDOM supplies
+ // batch copy.
+ private void setFile(File thisFile) {
+ mFile = thisFile;
+ }
+
+ private File getFile() {
+ return mFile;
+ }
+
+ /**
+ * This method will copy resources from source document to this document.
+ * The second parameter contains a map between all the name of resources in
+ * the source document and the rename string. If the source document is
+ * loaded from a file, a good performance method will be used. If the source
+ * document is loaded from a input stream, package layer methods will be
+ * invoked to copy these resources, with bad performance.
+ *
+ * In future, the code of this method will move to ODFDOM package layer.
+ * Till then, good performance will be gotten whether the source document is
+ * loaded from file or from input stream.
+ *
+ */
+ void copyResourcesFrom(Document srcDoc, HashMap<String, String> objectRenameMap) throws Exception {
+ Set<Map.Entry<String, String>> renameEntrySet = objectRenameMap.entrySet();
+ if (srcDoc.getFile() != null) {
+ ArrayList<String> copiedFolder = new ArrayList<String>();
+ FileInputStream tempFileStream = new FileInputStream(srcDoc.getFile());
+ ZipInputStream zipStream = new ZipInputStream(tempFileStream);
+ ZipEntry zipEntry = zipStream.getNextEntry();
+ while (zipEntry != null) {
+ String refObjPath = zipEntry.getName();
+ for (Map.Entry<String, String> renameEntry : renameEntrySet) {
+ String path = renameEntry.getKey();
+ String renamedPath = renameEntry.getValue();
+ if (refObjPath.equals(path)) {
+ String newObjPath = renamedPath;
+ refObjPath = normalizeFilePath(refObjPath);
+ String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString();
+ InputStream is = readAsInputStream(zipStream);
+ getPackage().insert(is, newObjPath, mediaType);
+ break;
+ } else if (refObjPath.startsWith(path + "/")) {
+ String suffix = refObjPath.substring(path.length());
+ String newObjPath = renamedPath + suffix;
+ refObjPath = normalizeFilePath(refObjPath);
+ String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString();
+ InputStream is = readAsInputStream(zipStream);
+ getPackage().insert(is, newObjPath, mediaType);
+ if (!copiedFolder.contains(path)) {
+ mediaType = srcDoc.getPackage().getFileEntry(path + "/").getMediaTypeString();
+ getPackage().insert((InputStream) null, renamedPath + "/", mediaType);
+ copiedFolder.add(path);
+ }
+ break;
+ }
+ }
+ zipEntry = zipStream.getNextEntry();
+ }
+ zipStream.close();
+ tempFileStream.close();
+ } else {
+ for (Map.Entry<String, String> renameEntry : renameEntrySet) {
+ String path = renameEntry.getKey();
+ String renamedPath = renameEntry.getValue();
+ InputStream is = srcDoc.getPackage().getInputStream(path);
+ if (is != null) {
+ String mediaType = srcDoc.getPackage().getFileEntry(path).getMediaTypeString();
+ getPackage().insert(is, renamedPath, mediaType);
+ } else {
+ Document embedDoc = ((Document) srcDoc).getEmbeddedDocument(path);
+ if (embedDoc != null) {
+ insertDocument(embedDoc, renamedPath);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This method will copy the linked resource of the element which need to be
+ * copied, from the source package to the target package.
+ * <p>
+ * If the target package contains a resource with the same path and name,
+ * the name of the resource will be renamed.
+ *
+ * @param sourceCloneEle
+ * - the element that need to be copied
+ */
+ // clone the source clone element's referred object path to the current
+ // package
+ // if the current package contains the same name with the referred object
+ // path,
+ // rename the object path and path reference of this slide element
+ // notes: the source clone element is the copied one to avoid changing the
+ // content of the source document.
+ void copyLinkedRef(OdfElement sourceCloneEle) {
+ try {
+ OdfFileDom fileDom = (OdfFileDom) sourceCloneEle.getOwnerDocument();
+ XPath xpath;
+ if (fileDom instanceof OdfContentDom) {
+ xpath = ((OdfContentDom) fileDom).getXPath();
+ } else {
+ xpath = ((OdfStylesDom) fileDom).getXPath();
+ }
+ OdfPackageDocument srcDoc = fileDom.getDocument();
+ // new a map to put the original name and the rename string, in case
+ // that the same name might be referred by the slide several times.
+ HashMap<String, String> objectRenameMap = new HashMap<String, String>();
+ NodeList linkNodes = (NodeList) xpath.evaluate(".//*[@xlink:href]", sourceCloneEle, XPathConstants.NODESET);
+ for (int i = 0; i <= linkNodes.getLength(); i++) {
+ OdfElement object = null;
+ if (linkNodes.getLength() == i) {
+ if (sourceCloneEle.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) {
+ object = sourceCloneEle;
+ } else {
+ break;
+ }
+ } else {
+ object = (OdfElement) linkNodes.item(i);
+ }
+ String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
+ if (refObjPath != null && refObjPath.length() > 0) {
+ // the path of the object is start with "./"
+ boolean hasPrefix = false;
+ String prefix = "./";
+ if (refObjPath.startsWith(prefix)) {
+ refObjPath = refObjPath.substring(2);
+ hasPrefix = true;
+ }
+ // check if the current document contains the same path
+ OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath);
+ // note: if refObjPath is a directory, it must end with '/'
+ if (fileEntry == null) {
+ fileEntry = getPackage().getFileEntry(refObjPath + "/");
+ }
+ String newObjPath = refObjPath;
+ if (fileEntry != null) {
+ // rename the object path
+ newObjPath = objectRenameMap.get(refObjPath);
+ if (newObjPath == null) {
+ // if refObjPath still contains ".", it means that
+ // it has the suffix
+ // then change the name before the suffix string
+ int dotIndex = refObjPath.indexOf(".");
+ if (dotIndex != -1) {
+ newObjPath = refObjPath.substring(0, dotIndex) + "-" + makeUniqueName() + refObjPath.substring(dotIndex);
+ } else {
+ newObjPath = refObjPath + "-" + makeUniqueName();
+ }
+ objectRenameMap.put(refObjPath, newObjPath);
+ }
+ object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath);
+ }
+ InputStream is = srcDoc.getPackage().getInputStream(refObjPath);
+ if (is != null) {
+ String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString();
+ getPackage().insert(is, newObjPath, mediaType);
+ } else {
+ Document embedDoc = ((Document) srcDoc).getEmbeddedDocument(refObjPath);
+ if (embedDoc != null) {
+ insertDocument(embedDoc, newObjPath);
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ }
+ }
+
+ /**
+ * When a element needs to be copied to a different document, all the style
+ * definitions that are related with this element need to be copied.
+ *
+ * @param sourceCloneEle
+ * - the element that need to be copied
+ * @param srcDoc
+ * - the source document
+ */
+ void copyForeignStyleRef(OdfElement sourceCloneEle, Document srcDoc) {
+ try {
+ ArrayList<String> tempList = new ArrayList<String>();
+ OdfFileDom srcContentDom = srcDoc.getContentDom();
+ XPath xpath = srcContentDom.getXPath();
+ // 1. collect all the referred style element which has "style:name"
+ // attribute
+ // 1.1. style:name of content.xml
+ String styleQName = "style:name";
+ NodeList srcStyleDefNodeList = (NodeList) xpath.evaluate("*/office:automatic-styles/*[@" + styleQName + "]", srcContentDom, XPathConstants.NODESET);
+ IdentityHashMap<OdfElement, List<OdfElement>> srcContentStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>();
+ IdentityHashMap<OdfElement, OdfElement> appendContentStyleList = new IdentityHashMap<OdfElement, OdfElement>();
+ getCopyStyleList(null, sourceCloneEle, styleQName, srcStyleDefNodeList, srcContentStyleCloneEleList, appendContentStyleList, tempList, true);
+ // 1.2. style:name of styles.xml
+ srcStyleDefNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", srcDoc.getStylesDom(), XPathConstants.NODESET);
+ IdentityHashMap<OdfElement, List<OdfElement>> srcStylesStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>();
+ IdentityHashMap<OdfElement, OdfElement> appendStylesStyleList = new IdentityHashMap<OdfElement, OdfElement>();
+ tempList.clear();
+ getCopyStyleList(null, sourceCloneEle, styleQName, srcStyleDefNodeList, srcStylesStyleCloneEleList, appendStylesStyleList, tempList, true);
+ // 1.3 rename, copy the referred style element to the corresponding
+ // position in the dom tree
+ insertCollectedStyle(styleQName, srcContentStyleCloneEleList, getContentDom(), appendContentStyleList);
+ insertCollectedStyle(styleQName, srcStylesStyleCloneEleList, getStylesDom(), appendStylesStyleList);
+
+ // 2. collect all the referred style element which has "draw:name"
+ // attribute
+ // 2.1 draw:name of styles.xml
+ // the value of draw:name is string or StyleName,
+ // only when the value is StyleName type, the style definition
+ // should be cloned to the destination document
+ // in ODF spec, such attribute type is only exist in <office:styles>
+ // element, so only search it in styles.xml dom
+ tempList.clear();
+ styleQName = "draw:name";
+ srcStyleDefNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", srcDoc.getStylesDom(), XPathConstants.NODESET);
+ IdentityHashMap<OdfElement, List<OdfElement>> srcDrawStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>();
+ IdentityHashMap<OdfElement, OdfElement> appendDrawStyleList = new IdentityHashMap<OdfElement, OdfElement>();
+ for (Map.Entry<OdfElement, OdfElement> entry : appendContentStyleList.entrySet()) {
+ OdfElement styleElement = entry.getKey();
+ OdfElement cloneStyleElement = entry.getValue();
+ getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, tempList,
+ false);
+ }
+ for (Map.Entry<OdfElement, OdfElement> entry : appendStylesStyleList.entrySet()) {
+ OdfElement styleElement = entry.getKey();
+ OdfElement cloneStyleElement = entry.getValue();
+ getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, tempList,
+ false);
+ }
+ // 2.2 rename, copy the referred style element to the corresponding
+ // position in the dom tree
+ // note: "draw:name" style element only exist in styles.dom
+ insertCollectedStyle(styleQName, srcDrawStyleCloneEleList, getStylesDom(), appendDrawStyleList);
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ }
+
+ }
+
+ // 1. modified the style name of the style definition element which has the
+ // same name with the source document
+ // 2. As to the style definition which match 1) condition, modified the
+ // referred style name of the element which reference this style
+ // 3. All the style which also contains other style reference, should be
+ // copied to the source document.
+ private void insertCollectedStyle(String styleQName, IdentityHashMap<OdfElement, List<OdfElement>> srcStyleCloneEleList, OdfFileDom dom,
+ IdentityHashMap<OdfElement, OdfElement> appendStyleList) {
+ try {
+ String stylePrefix = OdfNamespace.getPrefixPart(styleQName);
+ String styleLocalName = OdfNamespace.getLocalPart(styleQName);
+ String styleURI = OdfDocumentNamespace.STYLE.getUri();
+ if (stylePrefix.equals("draw"))
+ styleURI = OdfDocumentNamespace.DRAW.getUri();
+ // is the DOM always the styles.xml
+ XPath xpath = dom.getXPath();
+ NodeList destStyleNodeList;
+ if (dom instanceof OdfContentDom)
+ destStyleNodeList = (NodeList) xpath.evaluate("*/office:automatic-styles/*[@" + styleQName + "]", dom, XPathConstants.NODESET);
+ else
+ destStyleNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", dom, XPathConstants.NODESET);
+ for (Map.Entry<OdfElement, List<OdfElement>> srcEntry : srcStyleCloneEleList.entrySet()) {
+ OdfElement styleElement = srcEntry.getKey();
+ OdfElement cloneStyleElement = appendStyleList.get(styleElement);
+ if (cloneStyleElement == null) {
+ cloneStyleElement = (OdfElement) styleElement.cloneNode(true);
+ appendStyleList.put(styleElement, cloneStyleElement);
+ }
+ String styleName = styleElement.getAttributeNS(styleURI, styleLocalName);
+ List<String> newStyleNameList = styleRenameMap.get(styleName);
+ // if the newStyleNameList != null, means that styleName exists
+ // in dest document
+ // and it has already been renamed
+ if ((newStyleNameList != null) || (isStyleNameExist(destStyleNodeList, styleName) != null)) {
+ String newStyleName = null;
+ if (newStyleNameList == null) {
+ newStyleNameList = new ArrayList<String>();
+ newStyleName = styleName + "-" + makeUniqueName();
+ newStyleNameList.add(newStyleName);
+ styleRenameMap.put(styleName, newStyleNameList);
+ } else {
+ for (int i = 0; i < newStyleNameList.size(); i++) {
+ String styleNameIter = newStyleNameList.get(i);
+ OdfElement destStyleElementWithNewName = isStyleNameExist(destStyleNodeList, styleNameIter);
+ // check if the two style elements have the same
+ // content
+ // if not, the cloneStyleElement should rename,
+ // rather than reuse the new style name
+ cloneStyleElement.setAttributeNS(styleURI, styleQName, styleNameIter);
+ if ((destStyleElementWithNewName != null) && destStyleElementWithNewName.equals(cloneStyleElement)) {
+ newStyleName = styleNameIter;
+ break;
+ }
+ }
+ if (newStyleName == null) {
+ newStyleName = styleName + "-" + makeUniqueName();
+ newStyleNameList.add(newStyleName);
+ }
+ }
+ // System.out.println("renaming:"+styleName+"-"+newStyleName);
+ // if newStyleName has been set in the element as the new
+ // name
+ // which means that the newStyleName is conform to the odf
+ // spec
+ // then change element style reference name
+ if (changeStyleRefName(srcEntry.getValue(), styleName, newStyleName)) {
+ cloneStyleElement.setAttributeNS(styleURI, styleQName, newStyleName);
+ // if display name should also be renamed
+ String displayName = cloneStyleElement.getAttributeNS(styleURI, "display-name");
+ if ((displayName != null) && (displayName.length() > 0)) {
+ cloneStyleElement.setAttributeNS(styleURI, stylePrefix + ":display-name",
+ displayName + newStyleName.substring(newStyleName.length() - 8));
+ }
+ }
+ }
+ }
+
+ for (Map.Entry<OdfElement, OdfElement> entry : appendStyleList.entrySet()) {
+ OdfElement styleElement = entry.getKey();
+ OdfElement cloneStyleElement = entry.getValue();
+ String newStyleName = cloneStyleElement.getAttributeNS(styleURI, styleLocalName);
+ Boolean isAppended = styleAppendMap.get(newStyleName);
+ // if styleAppendMap contain the newStyleName,
+ // means that cloneStyleElement has already been appended
+ if ((isAppended != null) && isAppended.booleanValue() == true) {
+ continue;
+ } else {
+ styleAppendMap.put(newStyleName, true);
+ }
+ OdfElement cloneForeignStyleElement = (OdfElement) cloneForeignElement(cloneStyleElement, dom, true);
+ String styleElePath = getElementPath(styleElement);
+ appendForeignStyleElement(cloneForeignStyleElement, dom, styleElePath);
+ copyLinkedRef(cloneStyleElement);
+ }
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ }
+
+ }
+
+ // get all the copy of referred style element which is directly referred or
+ // indirectly referred by cloneEle
+ // styleQName is style:name
+ // all the style are defined in srcStyleNodeList
+ // and these style are all have the styleName defined in styleQName
+ // attribute
+ // the key of copyStyleEleList is the style definition element
+ // the value of the corresponding key is the clone of the element which
+ // refer to the key,
+ // the cloned element can be the content of slide or the style element.
+ // the key of appendStyleList is the style definition element which has the
+ // other style reference
+ // the value of the corresponding key is the the style definition clone
+ // element
+ // loop means if recursive call this function
+ // if loop == true, get the style definition element reference other style
+ // definition element
+ private void getCopyStyleList(OdfElement ele, OdfElement cloneEle, String styleQName, NodeList srcStyleNodeList,
+ IdentityHashMap<OdfElement, List<OdfElement>> copyStyleEleList, IdentityHashMap<OdfElement, OdfElement> appendStyleList, List<String> attrStrList,
+ boolean loop) {
+ try {
+ String styleLocalName = OdfNamespace.getLocalPart(styleQName);
+ String stylePrefix = OdfNamespace.getPrefixPart(styleQName);
+ String styleURI = (stylePrefix.equals("draw") ? OdfDocumentNamespace.DRAW : OdfDocumentNamespace.STYLE).getUri();
+ // OdfElement override the "toString" method
+ String cloneEleStr = cloneEle.toString();
+ for (int i = 0; i < srcStyleNodeList.getLength(); i++) {
+ OdfElement styleElement = (OdfElement) srcStyleNodeList.item(i);
+ final String styleName = styleElement.getAttributeNS(styleURI, styleLocalName);
+ if (styleName != null) {
+ final String eqStyleName = "=\"" + styleName + "\"";
+ for (int index = cloneEleStr.indexOf(eqStyleName); index >= 0; index = cloneEleStr.indexOf(eqStyleName, index + eqStyleName.length())) {
+ int lastSpaceIndex = cloneEleStr.lastIndexOf(' ', index);
+ final String attrStr = cloneEleStr.substring(lastSpaceIndex + 1, index);
+ final String attrEqStyleName;
+ if (attrStr.equals(styleQName) || attrStrList.contains(attrEqStyleName = attrStr + "=" + "\"" + styleName + "\"")) {
+ continue;
+ }
+ attrStrList.add(attrEqStyleName);
+ XPath xpath = ((OdfFileDom) cloneEle.getOwnerDocument()).getXPath();
+ NodeList styleRefNodes = (NodeList) xpath.evaluate(".//*[@" + attrStr + "='" + styleName + "']", cloneEle, XPathConstants.NODESET);
+ boolean isExist = false;
+ for (int j = 0; j <= styleRefNodes.getLength(); j++) {
+ OdfElement styleRefElement = null;
+ if (j == styleRefNodes.getLength()) {
+ isExist = isStyleNameRefExist(cloneEle, styleName, false);
+ if (isExist) {
+ styleRefElement = cloneEle;
+ } else {
+ continue;
+ }
+ } else {
+ OdfElement tmpElement = (OdfElement) styleRefNodes.item(j);
+ if (isStyleNameRefExist(tmpElement, styleName, false)) {
+ styleRefElement = tmpElement;
+ } else {
+ continue;
+ }
+ }
+ boolean hasLoopStyleDef = true;
+ if (!(styleElement instanceof StyleFontFaceElement)) {
+ List<OdfElement> styleRefEleList = copyStyleEleList.get(styleElement);
+ if (styleRefEleList == null) {
+ styleRefEleList = new ArrayList<OdfElement>();
+ copyStyleEleList.put(styleElement, styleRefEleList);
+ hasLoopStyleDef = false;
+ }
+ styleRefEleList.add(styleRefElement);
+ }
+
+ OdfElement cloneStyleElement = appendStyleList.get(styleElement);
+ if (cloneStyleElement == null) {
+ cloneStyleElement = (OdfElement) styleElement.cloneNode(true);
+ appendStyleList.put(styleElement, cloneStyleElement);
+ }
+ if (loop && !hasLoopStyleDef) {
+ getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleNodeList, copyStyleEleList, appendStyleList, attrStrList,
+ loop);
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ }
+ }
+
+ // append the cloneStyleElement to the contentDom which position is defined
+ // by styleElePath
+ private void appendForeignStyleElement(OdfElement cloneStyleEle, OdfFileDom dom, String styleElePath) {
+ StringTokenizer token = new StringTokenizer(styleElePath, "/");
+ boolean isExist = true;
+ boolean found = false;
+ Node iterNode = dom.getFirstChild();
+ Node parentNode = dom;
+ while (token.hasMoreTokens()) {
+ String onePath = token.nextToken();
+ found = false;
+
+ while ((iterNode != null) && isExist) {
+ String path = iterNode.getNamespaceURI();
+ String prefix = iterNode.getPrefix();
+ if (prefix == null) {
+ path += "@" + iterNode.getLocalName();
+ } else {
+ path += "@" + prefix + ":" + iterNode.getLocalName();
+ }
+ if (!path.equals(onePath)) {
+ // not found, then get the next sibling to find such path
+ // node
+ iterNode = iterNode.getNextSibling();
+ } else {
+ // found, then get the child nodes to find the next path
+ // node
+ parentNode = iterNode;
+ found = true;
+ iterNode = iterNode.getFirstChild();
+ break;
+ }
+ }
+
+ if (!found) {
+ // should new the element since the current path node
+ if (isExist) {
+ isExist = false;
+ }
+ StringTokenizer token2 = new StringTokenizer(onePath, "@");
+ OdfElement newElement = dom.createElementNS(OdfName.newName(token2.nextToken(), token2.nextToken()));
+ parentNode.appendChild(newElement);
+ parentNode = newElement;
+ }
+ }
+ parentNode.appendChild(cloneStyleEle);
+ }
+
+ // The returned string is a path from the top of the dom tree to the
+ // specified element
+ // and the path is split by "/" between each node
+ private String getElementPath(OdfElement styleEle) {
+ String path = "";
+ Node parentNode = styleEle.getParentNode();
+ while (!(parentNode instanceof OdfFileDom)) {
+ String qname = null;
+ String prefix = parentNode.getPrefix();
+ if (prefix == null) {
+ qname = parentNode.getLocalName();
+ } else {
+ qname = prefix + ":" + parentNode.getLocalName();
+ }
+ path = parentNode.getNamespaceURI() + "@" + qname + "/" + path;
+ parentNode = parentNode.getParentNode();
+ }
+ return path;
+ }
+
+ // change the element referred oldStyleName to the new name
+ // if true then set newStyleName attribute value successfully
+ // if false means that the newStyleName value is not conform to the ODF
+ // spec, so do not modify the oldStyleName
+ private boolean changeStyleRefName(List<OdfElement> list, String oldStyleName, String newStyleName) {
+ boolean rtn = false;
+ for (int index = 0; index < list.size(); index++) {
+ OdfElement element = list.get(index);
+ NamedNodeMap attributes = element.getAttributes();
+
+ if (attributes != null) {
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Node item = attributes.item(i);
+ String value = item.getNodeValue();
+ if (oldStyleName.equals(value)) {
+ try {
+ item.setNodeValue(newStyleName);
+ rtn = true;
+ break;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return rtn;
+ }
+
+ // check if the element contains the referred styleName
+ private boolean isStyleNameRefExist(Node element, String styleName, boolean deep) {
+ NamedNodeMap attributes = element.getAttributes();
+ if (attributes != null) {
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Node item = attributes.item(i);
+ if (item.getNodeValue().equals(styleName) && !item.getNodeName().equals("style:name")) {
+ // this is style definition, not reference.
+ return true;
+ }
+ }
+ }
+ if (deep) {
+ Node childNode = element.getFirstChild();
+ while (childNode != null) {
+ if (!isStyleNameRefExist(childNode, styleName, true)) {
+ childNode = childNode.getNextSibling();
+ } else {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // check if nodeList contains the node that "style:name" attribute has the
+ // same value with styleName
+ // Note: nodeList here is all the style definition list
+ private OdfElement isStyleNameExist(NodeList nodeList, String styleName) {
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ OdfElement element = (OdfElement) nodeList.item(i);
+ String name = element.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name");
+ if (name.equals(styleName)) {
+ // return true;
+ return element;
+ }
+ }
+ // return false;
+ return null;
+ }
+
+ /**
+ * This method will delete all the linked resources that are only related
+ * with this element.
+ *
+ * @param odfEle
+ * - the element to be deleted.
+ * @return true if successfully delete, or else, false will be returned
+ */
+ // delete all the xlink:href object which is contained in slideElement and
+ // does not referred by other slides
+ boolean deleteLinkedRef(OdfElement odfEle) {
+ boolean success = true;
+ try {
+ OdfFileDom contentDom = getContentDom();
+ XPath xpath = contentDom.getXPath();
+ NodeList linkNodes = (NodeList) xpath.evaluate("//*[@xlink:href]", contentDom, XPathConstants.NODESET);
+ for (int i = 0; i < linkNodes.getLength(); i++) {
+ OdfElement object = (OdfElement) linkNodes.item(i);
+ String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
+ int relation = odfEle.compareDocumentPosition(object);
+ // if slide element contains the returned element which has the
+ // xlink:href reference
+ if ((relation & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0 && refObjPath != null && refObjPath.length() > 0) {
+ // the path of the object is start with "./"
+ NodeList pathNodes = (NodeList) xpath.evaluate("//*[@xlink:href='" + refObjPath + "']", getContentDom(), XPathConstants.NODESET);
+ int refCount = pathNodes.getLength();
+ if (refCount == 1) {
+ // delete "./"
+ if (refObjPath.startsWith("./")) {
+ refObjPath = refObjPath.substring(2);
+ }
+ // check if the current document contains the same path
+ OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath);
+ if (fileEntry != null) {
+ // it is a stream, such as image, binary file
+ getPackage().remove(refObjPath);
+ } else {
+ // note: if refObjPath is a directory, it must end
+ // with '/'
+ fileEntry = getPackage().getFileEntry(refObjPath + "/");
+ removeDocument(refObjPath);
+ }
+ }
+ }
+ }
+ } catch (XPathExpressionException e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ success = false;
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ success = false;
+ }
+ return success;
+ }
+
+ /**
+ * This method will delete all the style definitions that are only related
+ * with this element.
+ *
+ * @param odfEle
+ * - the element to be deleted.
+ * @return true if successfully delete, or else, false will be returned
+ */
+ boolean deleteStyleRef(OdfElement odfEle) {
+ boolean success = true;
+ try {
+ // method 1:
+ // 1.1. iterate child element of the content element
+ // 1.2. if the child element is an OdfStylableElement, get the
+ // style-name ref count
+ // //////////////
+ // method 2:
+ // 2.1. get the list of the style definition
+ ArrayList<OdfElement> removeStyles = new ArrayList<OdfElement>();
+ OdfOfficeAutomaticStyles autoStyles = getContentDom().getAutomaticStyles();
+
+ NodeList stylesList = autoStyles.getChildNodes();
+ OdfFileDom contentDom = getContentDom();
+ XPath xpath = contentDom.getXPath();
+
+ // 2.2. get the reference of each style which occurred in the
+ // current page
+ for (int i = 0; i < stylesList.getLength(); i++) {
+ Node item = stylesList.item(i);
+ if (item instanceof OdfElement) {
+ OdfElement node = (OdfElement) item;
+ String styleName = node.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name");
+ if (styleName != null) {
+ // search the styleName contained at the current page
+ // element
+ NodeList styleNodes = (NodeList) xpath.evaluate("//*[@*='" + styleName + "']", contentDom, XPathConstants.NODESET);
+ int styleCnt = styleNodes.getLength();
+ if (styleCnt > 1) {
+ // the first styleName is occurred in the style
+ // definition
+ // so check if the second styleName and last
+ // styleName is occurred in the current page element
+ // if yes, then remove it
+ OdfElement elementFirst = (OdfElement) styleNodes.item(1);
+ OdfElement elementLast = (OdfElement) styleNodes.item(styleCnt - 1);
+ boolean isSamePage = false;
+ if (elementFirst instanceof DrawPageElement) {
+ DrawPageElement tempPage = (DrawPageElement) elementFirst;
+ if (tempPage.equals(odfEle)) {
+ isSamePage = true;
+ }
+ }
+ int relationFirst = odfEle.compareDocumentPosition(elementFirst);
+ int relationLast = odfEle.compareDocumentPosition(elementLast);
+ // if slide element contains the child element which
+ // has the styleName reference
+ if (((relationFirst & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0 && (relationLast & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0)
+ || (isSamePage && (styleCnt == 1))) {
+ if (node instanceof OdfStyleBase) {
+ removeStyles.add(node);
+ }
+ }
+ } else {
+ continue;
+ }
+ }
+ }
+ }
+ for (int i = 0; i < removeStyles.size(); i++) {
+ autoStyles.removeChild(removeStyles.get(i));
+ }
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ success = false;
+ }
+ return success;
+ }
+
+ public Table addTable() {
+ return getTableContainerImpl().addTable();
+ }
+
+ public Table addTable(int numRows, int numCols) {
+ return getTableContainerImpl().addTable(numRows, numCols);
+ }
+
+ public Table getTableByName(String name) {
+ return getTableContainerImpl().getTableByName(name);
+ }
+
+ public java.util.List<Table> getTableList() {
+ return getTableContainerImpl().getTableList();
+ }
+
+ public TableBuilder getTableBuilder() {
+ return getTableContainerImpl().getTableBuilder();
+ }
+
+ protected TableContainer getTableContainerImpl() {
+ if (tableContainerImpl == null) {
+ tableContainerImpl = new TableContainerImpl();
+ }
+ return tableContainerImpl;
+ }
+
+ private class TableContainerImpl extends AbstractTableContainer {
+
+ public OdfElement getTableContainerElement() {
+ OdfElement containerElement = null;
+ try {
+ containerElement = getContentRoot();
+ } catch (Exception e) {
+ Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e);
+ }
+ return containerElement;
+ }
+ }
+
+ /**
+ * Return the component repository of this document.
+ *
+ * @return the component repository of this document.
+ */
+ protected IdentityHashMap<OdfElement, Component> getComponentMap() {
+ return mComponentRepository;
+ }
+
+ /**
+ * Construct a
+ *
+ *
+ * <code>TableTemplate<code> feature by extracting style template from an pre-defined table in a foreign document. The styles loaded by the template will be copied into the document as well and can be referenced by table directly.
+ * <p>
+ * The imported table need to be at least a 5*5 table (e.g. A1E5). Each type of style in the template will be set according to the style reference in a specific table cell, as following:
+ * <br>first column - A2
+ * <br>last column - E2
+ * <br>first row - A2
+ * <br>last row - E2
+ * <br>even rows - B3
+ * <br>odd rows - B2
+ * <br>even columns - C2
+ * <br>odd columns - B2
+ * <br>body - B2
+ * <br>first-row-start-column -A1
+ * <br>first-row-end-column -E1
+ * <br>last-row-start-column -A5
+ * <br>last-row-end-column -E5
+ *
+ * @param templateFileInputStream
+ * - the InputStream of the ODF document.
+ * @param tableName
+ * - the table name which will be used to load styles as template
+ * @throws Exception
+ * - if content DOM could not be initialized
+ */
+ public TableTemplate LoadTableTemplateFromForeignTable(
+ InputStream templateFileInputStream, String tableName) throws Exception {
+
+ Document doc = Document.loadDocument(templateFileInputStream);
+
+ if (doc == null)
+ throw new IllegalStateException(
+ "Cannot load specified template file.");
+
+ Table table = doc.getTableByName(tableName);
+ if (table == null)
+ throw new IllegalStateException(
+ "Cannot load table template from specified file.");
+
+ if (table.getRowCount() < 5 || table.getColumnCount() < 5)
+ throw new IllegalStateException(
+ "The template cannot be loaded. It should be at least a 5*5 table.");
+
+ TableTemplate template = new TableTemplate(getStylesDom()
+ .newOdfElement(TableTableTemplateElement.class));
+
+ // first-row-start-column
+ Cell cell = table.getCellByPosition(0, 0);
+ cell.getParagraphIterator().hasNext();
+ cell.getParagraphIterator().next().getStyleName();
+ Paragraph para = cell.getParagraphByIndex(0, false);
+ String paraStyle = (para != null ? para.getStyleName() : null);
+ template.setExtendedStyleByType(
+ TableTemplate.ExtendedStyleType.FIRSTROWSTARTCOLUM, cell
+ .getStyleName(), paraStyle);
+ TableTableCellElementBase oldCellEle = cell.getOdfElement();
+ TableTableCellElementBase newCellEle = (TableTableCellElementBase) oldCellEle
+ .cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // first-row-end-column
+ cell = table.getCellByPosition(4, 0);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setExtendedStyleByType(
+ TableTemplate.ExtendedStyleType.FIRSTROWENDCOLUMN, cell
+ .getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // last-row-start-column
+ cell = table.getCellByPosition(0, 4);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setExtendedStyleByType(
+ TableTemplate.ExtendedStyleType.LASTROWSTARTCOLUMN, cell
+ .getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // last-row-end-column
+ cell = table.getCellByPosition(4, 4);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setExtendedStyleByType(
+ TableTemplate.ExtendedStyleType.LASTROWENDCOLUMN, cell
+ .getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // first column
+ cell = table.getCellByPosition(0, 1);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setTableFirstColumnStyle(cell.getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // last column
+ cell = table.getCellByPosition(4, 2);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setTableLastColumnStyle(cell.getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // first row
+ cell = table.getCellByPosition(1, 0);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setTableFirstRowStyle(cell.getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // last row
+ cell = table.getCellByPosition(1, 4);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setTableLastRowStyle(cell.getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // body (=odd row/column)
+ cell = table.getCellByPosition(1, 1);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setTableBodyStyle(cell.getStyleName(), paraStyle);
+ template.setTableOddRowsStyle(cell.getStyleName(), paraStyle);
+ template.setTableOddColumnsStyle(cell.getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // even row
+ cell = table.getCellByPosition(1, 2);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setTableEvenRowsStyle(cell.getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ // even row
+ cell = table.getCellByPosition(2, 1);
+ para = cell.getParagraphByIndex(0, false);
+ paraStyle = (para != null ? para.getStyleName() : null);
+ template.setTableEvenColumnsStyle(cell.getStyleName(), paraStyle);
+ oldCellEle = cell.getOdfElement();
+ newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true);
+ copyForeignStyleRef(newCellEle, cell.getOwnerDocument());
+
+ return template;
+ }
+
+}