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

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

The newest version!
/**
 * **********************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Use is subject to license terms.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also
 * obtain a copy of the License at http://odftoolkit.org/docs/license.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***********************************************************************
 */
package org.odftoolkit.simple;

import java.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 mComponentRepository
        = new IdentityHashMap();

    // 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> styleRenameMap = new HashMap>();
    // the map is used to record if the renamed style name is appended to the
    // current dom
    private HashMap styleAppendMap = new HashMap();

    // the object rename map for image.
    // can not easily recognize if the embedded document are the same.
    // private HashMap objectRenameMap = new HashMap();
    // 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 encapusulating 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.
     *
     * 

* Document relies on the file being available for read access over the * whole life cycle of Document. *

* * @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. * *

* Document relies on the file being available for read access over the * whole life cycle of Document. *

* * @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. * *

* 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. *

* * @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. * *

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

* * @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. * *

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

* * @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 getEmbeddedDocuments() { List embeddedObjects = new ArrayList(); // 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 getEmbeddedDocuments(OdfMediaType mediaType) { String wantedMediaString = null; if (mediaType != null) { wantedMediaString = mediaType.getMediaTypeString(); } List embeddedObjects = new ArrayList(); // check manifest for current embedded OdfPackageDocuments Set 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 * office:meta 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. * *

* If the input file has been cached (this is the case when loading from an * InputStream), the input file can be overwritten. *

* *

* If not, the OutputStream may not point to the input file! Otherwise this * will result in unwanted behaviour and broken files. *

* *

* 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. * *

* If the input file has been cached (this is the case when loading from an * InputStream), the input file can be overwritten. *

* *

* 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). *

* *

* 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. * *

* If the input file has been cached (this is the case when loading from an * InputStream), the input file can be overwritten. *

* *

* 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). *

* *

* 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 getContentRoot(Class 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: *

    *
  • The initial creator name will be the Java user.name System * property.
  • *
  • The date and time when this document was created using the current * data.
  • *
  • The number of times this document has been edited.
  • *
  • The default language will be the Java user.language System * property.
  • *
* * @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: *
    *
  • The name of the person who last modified this document will be the * Java user.name System property
  • *
  • The date and time when the document was last modified using current * data
  • *
  • The number of times this document has been edited is incremented by * 1
  • *
  • The total time spent editing this document
  • *
* * 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 // //////////////// /** *

* Unicode characters are in general divided by office applications into * three different types: * *

* 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 * *

* 2) There is CTL: Complex Text Layout, which uses BIDI algorithms and/or * glyph modules. See http://en.wikipedia.org/wiki/Complex_Text_Layout * *

* 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 CJKLanguage = new HashSet(); private final static HashSet CTLLanguage = new HashSet(); { 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 } /** *

* Set a locale information. *

* 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; } /** *

* Set a locale of a specific script type. *

* 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); } } /** *

* 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 defaultStyles = styles.getDefaultStyles(); if (defaultStyles != null) { Iterator 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 defaultStyles = styles.getDefaultStyles(); Iterator 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 defaultStyles = styles.getDefaultStyles(); if (defaultStyles != null) { Iterator 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 defaultStyles = styles.getDefaultStyles(); if (defaultStyles != null) { Iterator 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. *

* The sections defined in embed document won't be covered. * * @return an iterator of Section objects */ public Iterator

getSectionIterator() { TextSectionElement element; ArrayList
list = new ArrayList
(); 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. *

* This method won't search in the embed document. *

* 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 *

* 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; } String xpathValueRef = "//*[@text:continue-list]"; NodeList refIdList = (NodeList) xpath.evaluate(xpathValueRef, element, XPathConstants.NODESET); for (int i = 0; i < childList.getLength(); i++) { OdfElement ele = (OdfElement) childList.item(i); Attr attri = ele.getAttributeNodeNS(OdfDocumentNamespace.XML.getUri(), "id"); String oldValue = attri.getValue(); updateAttribute(attri); for (int k = 0; k < refIdList.getLength(); k++) { OdfElement eleRef = (OdfElement) refIdList.item(k); Attr attriRef = eleRef.getAttributeNodeNS(OdfDocumentNamespace.TEXT.getUri(), "continue-list"); if (attriRef.getValue().equals(oldValue)) { attriRef.setValue(attri.getValue()); break; } } } } 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. *

* 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. *

* If the target package contains a resource with the same path and name, * the name of the resource will be renamed. *

* 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 objectRenameMap = new HashMap(); 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 tokenList = new ArrayList(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 objectRenameMap) throws Exception { Set> renameEntrySet = objectRenameMap.entrySet(); if (srcDoc.getFile() != null) { ArrayList copiedFolder = new ArrayList(); 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 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 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. *

* 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 objectRenameMap = new HashMap(); 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 tempList = new ArrayList(); 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> srcContentStyleCloneEleList = new IdentityHashMap>(); IdentityHashMap appendContentStyleList = new IdentityHashMap(); 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> srcStylesStyleCloneEleList = new IdentityHashMap>(); IdentityHashMap appendStylesStyleList = new IdentityHashMap(); 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 // element, so only search it in styles.xml dom tempList.clear(); styleQName = "draw:name"; srcStyleDefNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", srcDoc.getStylesDom(), XPathConstants.NODESET); IdentityHashMap> srcDrawStyleCloneEleList = new IdentityHashMap>(); IdentityHashMap appendDrawStyleList = new IdentityHashMap(); for (Map.Entry entry : appendContentStyleList.entrySet()) { OdfElement styleElement = entry.getKey(); OdfElement cloneStyleElement = entry.getValue(); getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, tempList, false); } for (Map.Entry 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> srcStyleCloneEleList, OdfFileDom dom, IdentityHashMap 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> 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 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(); 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 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> copyStyleEleList, IdentityHashMap appendStyleList, List 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 styleRefEleList = copyStyleEleList.get(styleElement); if (styleRefEleList == null) { styleRefEleList = new ArrayList(); 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 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 removeStyles = new ArrayList(); 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

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 getComponentMap() { return mComponentRepository; } /** * Construct a * * * TableTemplate 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. *

* 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: *
first column - A2 *
last column - E2 *
first row - A2 *
last row - E2 *
even rows - B3 *
odd rows - B2 *
even columns - C2 *
odd columns - B2 *
body - B2 *
first-row-start-column -A1 *
first-row-end-column -E1 *
last-row-start-column -A5 *
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; } }