| /************************************************************************ |
| * |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER |
| * |
| * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved. |
| * |
| * Use is subject to license terms. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy |
| * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also |
| * obtain a copy of the License at http://odftoolkit.org/docs/license.txt |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| ************************************************************************/ |
| package org.odftoolkit.simple; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| import javax.xml.datatype.DatatypeConfigurationException; |
| import javax.xml.datatype.DatatypeFactory; |
| import javax.xml.xpath.XPath; |
| import javax.xml.xpath.XPathConstants; |
| import javax.xml.xpath.XPathExpressionException; |
| import org.odftoolkit.odfdom.dom.OdfContentDom; |
| import org.odftoolkit.odfdom.dom.OdfDocumentNamespace; |
| import org.odftoolkit.odfdom.dom.OdfSchemaConstraint; |
| import org.odftoolkit.odfdom.dom.OdfSchemaDocument; |
| import org.odftoolkit.odfdom.dom.OdfStylesDom; |
| import org.odftoolkit.odfdom.dom.attribute.text.TextAnchorTypeAttribute; |
| import org.odftoolkit.odfdom.dom.element.OdfStyleBase; |
| import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement; |
| import org.odftoolkit.odfdom.dom.element.draw.DrawPageElement; |
| import org.odftoolkit.odfdom.dom.element.office.OfficeBodyElement; |
| import org.odftoolkit.odfdom.dom.element.office.OfficeMasterStylesElement; |
| import org.odftoolkit.odfdom.dom.element.style.StyleFontFaceElement; |
| import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement; |
| import org.odftoolkit.odfdom.dom.element.table.TableTableCellElementBase; |
| import org.odftoolkit.odfdom.dom.element.table.TableTableTemplateElement; |
| import org.odftoolkit.odfdom.dom.element.text.TextPElement; |
| import org.odftoolkit.odfdom.dom.element.text.TextSectionElement; |
| import org.odftoolkit.odfdom.dom.style.OdfStyleFamily; |
| import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty; |
| import org.odftoolkit.odfdom.dom.style.props.OdfTextProperties; |
| import org.odftoolkit.odfdom.incubator.doc.draw.OdfDrawFrame; |
| import org.odftoolkit.odfdom.incubator.doc.draw.OdfDrawImage; |
| import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles; |
| import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles; |
| import org.odftoolkit.odfdom.incubator.doc.style.OdfDefaultStyle; |
| import org.odftoolkit.odfdom.pkg.MediaType; |
| import org.odftoolkit.odfdom.pkg.OdfElement; |
| import org.odftoolkit.odfdom.pkg.OdfFileDom; |
| import org.odftoolkit.odfdom.pkg.OdfName; |
| import org.odftoolkit.odfdom.pkg.OdfNamespace; |
| import org.odftoolkit.odfdom.pkg.OdfPackage; |
| import org.odftoolkit.odfdom.pkg.OdfPackageDocument; |
| import org.odftoolkit.odfdom.pkg.OdfValidationException; |
| import org.odftoolkit.odfdom.pkg.manifest.OdfFileEntry; |
| import org.odftoolkit.odfdom.type.Duration; |
| import org.odftoolkit.simple.meta.Meta; |
| import org.odftoolkit.simple.table.AbstractTableContainer; |
| import org.odftoolkit.simple.table.Cell; |
| import org.odftoolkit.simple.table.Table; |
| import org.odftoolkit.simple.table.Table.TableBuilder; |
| import org.odftoolkit.simple.table.TableContainer; |
| import org.odftoolkit.simple.table.TableTemplate; |
| import org.odftoolkit.simple.text.Paragraph; |
| import org.odftoolkit.simple.text.Section; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.ErrorHandler; |
| |
| /** |
| * This abstract class is representing one of the possible ODF documents |
| */ |
| public abstract class Document extends OdfSchemaDocument implements TableContainer { |
| // Static parts of file references |
| private static final String SLASH = "/"; |
| private OdfMediaType mMediaType; |
| private Meta mOfficeMeta; |
| private long documentOpeningTime; |
| private TableContainerImpl tableContainerImpl; |
| private static final Pattern CONTROL_CHAR_PATTERN = Pattern.compile("\\p{Cntrl}"); |
| private static final String EMPTY_STRING = ""; |
| |
| private IdentityHashMap<OdfElement, Component> mComponentRepository = new IdentityHashMap<OdfElement, Component>(); |
| |
| // FIXME: This field is only used in method copyResourcesFrom to improve |
| // copy performance, should not be used in any other way. |
| // methods loadDocument(String documentPath) and loadDocument(File file) |
| // will initialize it. |
| // This field and its methods should be removed after ODFDOM supplies batch |
| // copy. |
| private File mFile = null; |
| |
| // if the copy foreign slide for several times, |
| // the same style might be copied for several times with the different name |
| // so use styleRenameMap to keep track the renamed style so we can reuse the |
| // style, |
| // rather than new several styles which only have the different style names. |
| // while if the style elements really have the same style name but with |
| // different content |
| // such as that these style elements are from different document |
| // so the value for each key should be a list |
| private HashMap<String, List<String>> styleRenameMap = new HashMap<String, List<String>>(); |
| // the map is used to record if the renamed style name is appended to the |
| // current dom |
| private HashMap<String, Boolean> styleAppendMap = new HashMap<String, Boolean>(); |
| |
| // the object rename map for image. |
| // can not easily recognize if the embedded document are the same. |
| // private HashMap<String, String> objectRenameMap = new HashMap<String, |
| // String>(); |
| |
| // Using static factory instead of constructor |
| protected Document(OdfPackage pkg, String internalPath, OdfMediaType mediaType) { |
| super(pkg, internalPath, mediaType.getMediaTypeString()); |
| mMediaType = mediaType; |
| // set document opening time. |
| documentOpeningTime = System.currentTimeMillis(); |
| } |
| |
| /** |
| * This enum contains all possible media types of Document documents. |
| */ |
| public enum OdfMediaType implements MediaType { |
| |
| CHART("application/vnd.oasis.opendocument.chart", "odc"), |
| CHART_TEMPLATE("application/vnd.oasis.opendocument.chart-template", "otc"), |
| FORMULA("application/vnd.oasis.opendocument.formula", "odf"), |
| FORMULA_TEMPLATE("application/vnd.oasis.opendocument.formula-template", "otf"), |
| DATABASE_FRONT_END("application/vnd.oasis.opendocument.base", "odb"), |
| GRAPHICS("application/vnd.oasis.opendocument.graphics", "odg"), |
| GRAPHICS_TEMPLATE("application/vnd.oasis.opendocument.graphics-template", "otg"), |
| IMAGE("application/vnd.oasis.opendocument.image", "odi"), |
| IMAGE_TEMPLATE("application/vnd.oasis.opendocument.image-template", "oti"), |
| PRESENTATION("application/vnd.oasis.opendocument.presentation", "odp"), |
| PRESENTATION_TEMPLATE("application/vnd.oasis.opendocument.presentation-template", "otp"), |
| SPREADSHEET("application/vnd.oasis.opendocument.spreadsheet", "ods"), |
| SPREADSHEET_TEMPLATE("application/vnd.oasis.opendocument.spreadsheet-template", "ots"), |
| TEXT("application/vnd.oasis.opendocument.text", "odt"), |
| TEXT_MASTER("application/vnd.oasis.opendocument.text-master", "odm"), |
| TEXT_TEMPLATE("application/vnd.oasis.opendocument.text-template", "ott"), |
| TEXT_WEB("application/vnd.oasis.opendocument.text-web", "oth"); |
| |
| private final String mMediaType; |
| private final String mSuffix; |
| |
| OdfMediaType(String mediaType, String suffix) { |
| this.mMediaType = mediaType; |
| this.mSuffix = suffix; |
| } |
| |
| /** |
| * @return the mediatype String of this document |
| */ |
| public String getMediaTypeString() { |
| return mMediaType; |
| } |
| |
| /** |
| * @return the ODF filesuffix of this document |
| */ |
| public String getSuffix() { |
| return mSuffix; |
| } |
| |
| /** |
| * |
| * @param mediaType |
| * string defining an ODF document |
| * @return the according OdfMediatype encapuslating the given string and |
| * the suffix |
| */ |
| public static OdfMediaType getOdfMediaType(String mediaType) { |
| OdfMediaType odfMediaType = null; |
| if (mediaType != null) { |
| String mediaTypeShort = mediaType.substring(mediaType.lastIndexOf(".") + 1, mediaType.length()); |
| mediaTypeShort = mediaTypeShort.replace('-', '_').toUpperCase(); |
| try { |
| odfMediaType = OdfMediaType.valueOf(mediaTypeShort); |
| } catch (IllegalArgumentException e) { |
| throw new IllegalArgumentException("Given mediaType '" + mediaType + "' is either not yet supported or not an ODF mediatype!"); |
| } |
| } |
| return odfMediaType; |
| } |
| } |
| |
| /** |
| * Loads an Document from the given resource. NOTE: Initial meta data will |
| * be added in this method. |
| * |
| * @param res |
| * a resource containing a package with a root document |
| * @param odfMediaType |
| * the media type of the root document |
| * @return the Document document or NULL if the media type is not supported |
| * by SIMPLE. |
| * @throws java.lang.Exception |
| * - if the document could not be created. |
| */ |
| protected static Document loadTemplate(Resource res, OdfMediaType odfMediaType) throws Exception { |
| InputStream in = res.createInputStream(); |
| OdfPackage pkg = null; |
| try { |
| pkg = OdfPackage.loadPackage(in); |
| } finally { |
| in.close(); |
| } |
| Document newDocument = newDocument(pkg, ROOT_DOCUMENT_PATH, odfMediaType); |
| // add initial meta data to new document. |
| initializeMetaData(newDocument); |
| return newDocument; |
| } |
| |
| /** |
| * Loads a Document from the provided path. |
| * |
| * <p> |
| * Document relies on the file being available for read access over the |
| * whole life cycle of Document. |
| * </p> |
| * |
| * @param documentPath |
| * - the path from where the document can be loaded |
| * @param password |
| * - file password. |
| * @return the Document from the given path or NULL if the media type is not |
| * supported by SIMPLE. |
| * @throws java.lang.Exception |
| * - if the document could not be created. |
| * @since 0.8 |
| */ |
| public static Document loadDocument(String documentPath, String password) throws Exception { |
| File file = new File(documentPath); |
| return loadDocument(file, password); |
| } |
| |
| /** |
| * Loads a Document from the provided path. |
| * |
| * <p> |
| * Document relies on the file being available for read access over the |
| * whole life cycle of Document. |
| * </p> |
| * |
| * @param documentPath |
| * - the path from where the document can be loaded |
| * @return the Document from the given path or NULL if the media type is not |
| * supported by SIMPLE. |
| * @throws java.lang.Exception |
| * - if the document could not be created. |
| */ |
| public static Document loadDocument(String documentPath) throws Exception { |
| File file = new File(documentPath); |
| return loadDocument(file); |
| } |
| |
| /** |
| * Creates a Document from the Document provided by a resource Stream. |
| * |
| * <p> |
| * Since an InputStream does not provide the arbitrary (non sequentiell) |
| * read access needed by Document, the InputStream is cached. This usually |
| * takes more time compared to the other createInternalDocument methods. An |
| * advantage of caching is that there are no problems overwriting an input |
| * file. |
| * </p> |
| * |
| * @param inStream |
| * - the InputStream of the ODF document. |
| * @return the document created from the given InputStream |
| * @throws java.lang.Exception |
| * - if the document could not be created. |
| */ |
| public static Document loadDocument(InputStream inStream) throws Exception { |
| return loadDocument(OdfPackage.loadPackage(inStream)); |
| } |
| |
| /** |
| * Creates a Document from the Document provided by a File. |
| * |
| * <p> |
| * Document relies on the file being available for read access over the |
| * whole lifecycle of Document. |
| * </p> |
| * |
| * @param file |
| * - a file representing the ODF document. |
| * @return the document created from the given File |
| * @throws java.lang.Exception |
| * - if the document could not be created. |
| */ |
| public static Document loadDocument(File file) throws Exception { |
| Document doc = loadDocument(OdfPackage.loadPackage(file)); |
| doc.setFile(file); |
| return doc; |
| } |
| |
| /** |
| * Creates a Document from the Document provided by a File. |
| * |
| * <p> |
| * Document relies on the file being available for read access over the |
| * whole lifecycle of Document. |
| * </p> |
| * |
| * @param file |
| * - a file representing the ODF document. |
| * @param password |
| * - file password. |
| * @return the document created from the given File |
| * @throws java.lang.Exception |
| * - if the document could not be created. |
| * @since 0.7 |
| */ |
| public static Document loadDocument(File file, String password) throws Exception { |
| Document doc = loadDocument(OdfPackage.loadPackage(file, password, null)); |
| doc.setFile(file); |
| return doc; |
| } |
| |
| /** |
| * Creates a Document from the Document provided by an ODF package. |
| * |
| * @param odfPackage |
| * - the ODF package containing the ODF document. |
| * @return the root document of the given OdfPackage |
| * @throws java.lang.Exception |
| * - if the ODF document could not be created. |
| */ |
| public static Document loadDocument(OdfPackage odfPackage) throws Exception { |
| return loadDocument(odfPackage, ROOT_DOCUMENT_PATH); |
| } |
| |
| /** |
| * Creates a Document from the Document provided by an ODF package. |
| * |
| * @param odfPackage |
| * - the ODF package containing the ODF document. |
| * @param internalPath |
| * - the path to the ODF document relative to the package root. |
| * @return the root document of the given OdfPackage |
| * @throws java.lang.Exception |
| * - if the ODF document could not be created. |
| */ |
| public static Document loadDocument(OdfPackage odfPackage, String internalPath) throws Exception { |
| String documentMediaType = odfPackage.getMediaTypeString(internalPath); |
| if (documentMediaType == null) { |
| throw new IllegalArgumentException("Given internalPath '" + internalPath + "' is an illegal or inappropriate argument."); |
| } |
| OdfMediaType odfMediaType = OdfMediaType.getOdfMediaType(documentMediaType); |
| if (odfMediaType == null) { |
| ErrorHandler errorHandler = odfPackage.getErrorHandler(); |
| Matcher matcherCTRL = CONTROL_CHAR_PATTERN.matcher(documentMediaType); |
| if (matcherCTRL.find()) { |
| documentMediaType = matcherCTRL.replaceAll(EMPTY_STRING); |
| } |
| OdfValidationException ve = new OdfValidationException(OdfSchemaConstraint.DOCUMENT_WITHOUT_ODF_MIMETYPE, internalPath, documentMediaType); |
| if (errorHandler != null) { |
| errorHandler.fatalError(ve); |
| } |
| throw ve; |
| } |
| return newDocument(odfPackage, internalPath, odfMediaType); |
| } |
| |
| /** |
| * Sets password of this document. |
| * |
| * @param password |
| * the password of this document. |
| * @since 0.8 |
| */ |
| public void setPassword(String password) { |
| getPackage().setPassword(password); |
| } |
| |
| // return null if the media type can not be recognized. |
| private static Document loadDocumentFromTemplate(OdfMediaType odfMediaType) throws Exception { |
| |
| switch (odfMediaType) { |
| case TEXT: |
| case TEXT_TEMPLATE: |
| case TEXT_MASTER: |
| case TEXT_WEB: |
| // documentTemplate = TextDocument.EMPTY_TEXT_DOCUMENT_RESOURCE; |
| TextDocument document = TextDocument.newTextDocument(); |
| document.changeMode(TextDocument.OdfMediaType.TEXT_WEB); |
| return document; |
| |
| case SPREADSHEET: |
| SpreadsheetDocument spreadsheet = SpreadsheetDocument.newSpreadsheetDocument(); |
| spreadsheet.changeMode(SpreadsheetDocument.OdfMediaType.SPREADSHEET); |
| return spreadsheet; |
| |
| case SPREADSHEET_TEMPLATE: |
| SpreadsheetDocument spreadsheettemplate = SpreadsheetDocument.newSpreadsheetDocument(); |
| spreadsheettemplate.changeMode(SpreadsheetDocument.OdfMediaType.SPREADSHEET_TEMPLATE); |
| return spreadsheettemplate; |
| |
| case PRESENTATION: |
| PresentationDocument presentation = PresentationDocument.newPresentationDocument(); |
| presentation.changeMode(PresentationDocument.OdfMediaType.PRESENTATION); |
| return presentation; |
| |
| case PRESENTATION_TEMPLATE: |
| PresentationDocument presentationtemplate = PresentationDocument.newPresentationDocument(); |
| presentationtemplate.changeMode(PresentationDocument.OdfMediaType.PRESENTATION_TEMPLATE); |
| return presentationtemplate; |
| |
| case GRAPHICS: |
| GraphicsDocument graphics = GraphicsDocument.newGraphicsDocument(); |
| graphics.changeMode(GraphicsDocument.OdfMediaType.GRAPHICS); |
| return graphics; |
| |
| case GRAPHICS_TEMPLATE: |
| GraphicsDocument graphicstemplate = GraphicsDocument.newGraphicsDocument(); |
| graphicstemplate.changeMode(GraphicsDocument.OdfMediaType.GRAPHICS_TEMPLATE); |
| return graphicstemplate; |
| |
| case CHART: |
| ChartDocument chart = ChartDocument.newChartDocument(); |
| chart.changeMode(ChartDocument.OdfMediaType.CHART); |
| return chart; |
| |
| case CHART_TEMPLATE: |
| ChartDocument charttemplate = ChartDocument.newChartDocument(); |
| charttemplate.changeMode(ChartDocument.OdfMediaType.CHART_TEMPLATE); |
| return charttemplate; |
| |
| // case IMAGE: |
| // case IMAGE_TEMPLATE: |
| |
| default: |
| throw new IllegalArgumentException("Given mediaType '" + odfMediaType.toString() + "' is either not yet supported or not an ODF mediatype!"); |
| } |
| } |
| |
| /** |
| * Creates one of the ODF documents based a given mediatype. |
| * |
| * @param odfMediaType |
| * The ODF Mediatype of the ODF document to be created. |
| * @return The ODF document, which mediatype dependends on the parameter or |
| * NULL if media type were not supported. |
| */ |
| private static Document newDocument(OdfPackage pkg, String internalPath, OdfMediaType odfMediaType) { |
| Document newDoc = null; |
| switch (odfMediaType) { |
| case TEXT: |
| newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT); |
| break; |
| |
| case TEXT_TEMPLATE: |
| newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_TEMPLATE); |
| break; |
| |
| case TEXT_MASTER: |
| newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_MASTER); |
| break; |
| |
| case TEXT_WEB: |
| newDoc = new TextDocument(pkg, internalPath, TextDocument.OdfMediaType.TEXT_WEB); |
| break; |
| |
| case SPREADSHEET: |
| newDoc = new SpreadsheetDocument(pkg, internalPath, SpreadsheetDocument.OdfMediaType.SPREADSHEET); |
| break; |
| |
| case SPREADSHEET_TEMPLATE: |
| newDoc = new SpreadsheetDocument(pkg, internalPath, SpreadsheetDocument.OdfMediaType.SPREADSHEET_TEMPLATE); |
| break; |
| |
| case PRESENTATION: |
| newDoc = new PresentationDocument(pkg, internalPath, PresentationDocument.OdfMediaType.PRESENTATION); |
| break; |
| |
| case PRESENTATION_TEMPLATE: |
| newDoc = new PresentationDocument(pkg, internalPath, PresentationDocument.OdfMediaType.PRESENTATION_TEMPLATE); |
| break; |
| |
| case GRAPHICS: |
| newDoc = new GraphicsDocument(pkg, internalPath, GraphicsDocument.OdfMediaType.GRAPHICS); |
| break; |
| |
| case GRAPHICS_TEMPLATE: |
| newDoc = new GraphicsDocument(pkg, internalPath, GraphicsDocument.OdfMediaType.GRAPHICS_TEMPLATE); |
| break; |
| |
| case CHART: |
| newDoc = new ChartDocument(pkg, internalPath, ChartDocument.OdfMediaType.CHART); |
| break; |
| |
| case CHART_TEMPLATE: |
| newDoc = new ChartDocument(pkg, internalPath, ChartDocument.OdfMediaType.CHART_TEMPLATE); |
| break; |
| // case IMAGE: |
| // case IMAGE_TEMPLATE: |
| |
| default: |
| newDoc = null; |
| throw new IllegalArgumentException("Given mediaType '" + odfMediaType.mMediaType + "' is not yet supported!"); |
| } |
| // returning null if MediaType is not supported |
| return newDoc; |
| } |
| |
| /** |
| * Returns an embedded OdfPackageDocument from the given package path. |
| * |
| * @param documentPath |
| * path to the ODF document within the package. The path is |
| * relative to the current document. |
| * @return an embedded Document |
| */ |
| public Document getEmbeddedDocument(String documentPath) { |
| String internalPath = getDocumentPath() + documentPath; |
| internalPath = normalizeDocumentPath(internalPath); |
| Document embeddedDocument = (Document) mPackage.getCachedDocument(internalPath); |
| // if the document was not already loaded, fine mimetype and create a |
| // new instance |
| if (embeddedDocument == null) { |
| String mediaTypeString = getMediaTypeString(); |
| OdfMediaType odfMediaType = OdfMediaType.getOdfMediaType(mediaTypeString); |
| if (odfMediaType == null) { |
| embeddedDocument = newDocument(mPackage, internalPath, odfMediaType); |
| } else { |
| try { |
| String documentMediaType = mPackage.getMediaTypeString(internalPath); |
| odfMediaType = OdfMediaType.getOdfMediaType(documentMediaType); |
| if (odfMediaType == null) { |
| return null; |
| } |
| embeddedDocument = Document.loadDocument(mPackage, internalPath); |
| } catch (Exception ex) { |
| Logger.getLogger(OdfPackageDocument.class.getName()).log(Level.SEVERE, null, ex); |
| } |
| } |
| } |
| return embeddedDocument; |
| } |
| |
| /** |
| * Method returns all embedded OdfPackageDocuments, which match a valid |
| * OdfMediaType, of the root OdfPackageDocument. |
| * |
| * @return a list with all embedded documents of the root OdfPackageDocument |
| */ |
| // ToDo: (Issue 219 - PackageRefactoring) - Better return Path of |
| // Documents?? |
| public List<Document> getEmbeddedDocuments() { |
| List<Document> embeddedObjects = new ArrayList<Document>(); |
| // ToDo: (Issue 219 - PackageRefactoring) - Algorithm enhancement: |
| // Instead going through all the files for each mimetype, better |
| // Check all files, which have a mimetype if it is one of the desired, |
| // perhaps start with ODF prefix |
| for (OdfMediaType mediaType : OdfMediaType.values()) { |
| embeddedObjects.addAll(getEmbeddedDocuments(mediaType)); |
| } |
| return embeddedObjects; |
| } |
| |
| /** |
| * Method returns all embedded OdfPackageDocuments of the root |
| * OdfPackageDocument matching the according MediaType. This is done by |
| * matching the subfolder entries of the manifest file with the given |
| * OdfMediaType. |
| * |
| * @param mediaType |
| * media type which is used as a filter |
| * @return embedded documents of the root OdfPackageDocument matching the |
| * given media type |
| */ |
| public List<Document> getEmbeddedDocuments(OdfMediaType mediaType) { |
| String wantedMediaString = null; |
| if (mediaType != null) { |
| wantedMediaString = mediaType.getMediaTypeString(); |
| } |
| List<Document> embeddedObjects = new ArrayList<Document>(); |
| // check manifest for current embedded OdfPackageDocuments |
| Set<String> manifestEntries = mPackage.getFilePaths(); |
| for (String path : manifestEntries) { |
| // any directory that is not the root document "/" |
| if (path.length() > 1 && path.endsWith(SLASH)) { |
| String entryMediaType = mPackage.getFileEntry(path).getMediaTypeString(); |
| // if the entry is a document (directory has mediaType) |
| if (entryMediaType != null) { |
| // if a specific ODF mediatype was requested |
| if (wantedMediaString != null) { |
| // test if the desired mediatype matches the current |
| if (entryMediaType.equals(wantedMediaString)) { |
| normalizeDocumentPath(path); |
| embeddedObjects.add(getEmbeddedDocument(path)); |
| } |
| } else { |
| // test if any ODF mediatype matches the current |
| for (OdfMediaType type : OdfMediaType.values()) { |
| if (entryMediaType.equals(type.getMediaTypeString())) { |
| embeddedObjects.add(getEmbeddedDocument(path)); |
| } |
| } |
| } |
| } |
| } |
| } |
| return embeddedObjects; |
| } |
| |
| /** |
| * Embed an OdfPackageDocument to the current OdfPackageDocument. All the |
| * file entries of child document will be embedded as well to the current |
| * document package. |
| * |
| * @param documentPath |
| * to the directory the ODF document should be inserted (relative |
| * to the current document). |
| * @param sourceDocument |
| * the OdfPackageDocument to be embedded. |
| */ |
| public void insertDocument(OdfPackageDocument sourceDocument, String documentPath) { |
| super.insertDocument(sourceDocument, documentPath); |
| } |
| |
| /** |
| * Sets the media type of the Document |
| * |
| * @param odfMediaType |
| * media type to be set |
| */ |
| protected void setOdfMediaType(OdfMediaType odfMediaType) { |
| mMediaType = odfMediaType; |
| super.setMediaTypeString(odfMediaType.getMediaTypeString()); |
| } |
| |
| /** |
| * Gets the media type of the Document |
| */ |
| protected OdfMediaType getOdfMediaType() { |
| return mMediaType; |
| } |
| |
| /** |
| * Get the meta data feature instance of the current document |
| * |
| * @return the meta data feature instance which represent |
| * <code>office:meta</code> in the meta.xml |
| */ |
| public Meta getOfficeMetadata() { |
| if (mOfficeMeta == null) { |
| try { |
| mOfficeMeta = new Meta(getMetaDom()); |
| } catch (Exception ex) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, ex); |
| } |
| } |
| return mOfficeMeta; |
| } |
| |
| /** |
| * Save the document to an OutputStream. Delegate to the root document and |
| * save possible embedded Documents. |
| * |
| * <p> |
| * If the input file has been cached (this is the case when loading from an |
| * InputStream), the input file can be overwritten. |
| * </p> |
| * |
| * <p> |
| * If not, the OutputStream may not point to the input file! Otherwise this |
| * will result in unwanted behaviour and broken files. |
| * </p> |
| * |
| * <p> |
| * When save the embedded document to a stand alone document, all the file |
| * entries of the embedded document will be copied to a new document |
| * package. If the embedded document is outside of the current document |
| * directory, you have to embed it to the sub directory and refresh the link |
| * of the embedded document. you should reload it from the stream to get the |
| * saved embedded document. |
| * |
| * @param out |
| * - the OutputStream to write the file to |
| * @throws java.lang.Exception |
| * if the document could not be saved |
| */ |
| public void save(OutputStream out) throws Exception { |
| // 2DO FLUSH AND SAVE IN PACKAGE |
| flushDoms(); |
| updateMetaData(); |
| if (!isRootDocument()) { |
| Document newDoc = loadDocumentFromTemplate(getOdfMediaType()); |
| newDoc.insertDocument(this, ROOT_DOCUMENT_PATH); |
| newDoc.updateMetaData(); |
| newDoc.mPackage.save(out); |
| // ToDo: (Issue 219 - PackageRefactoring) - Return the document, |
| // when not closing! |
| // Should we close the sources now? User will never receive the open |
| // package! |
| } else { |
| // 2DO MOVE CACHE TO PACKAGE |
| // // the root document only have to flush the DOM of all open child |
| // documents |
| // flushAllDOMs(); |
| mPackage.save(out); |
| } |
| } |
| |
| /** |
| * Save the document to a given file. |
| * |
| * <p> |
| * If the input file has been cached (this is the case when loading from an |
| * InputStream), the input file can be overwritten. |
| * </p> |
| * |
| * <p> |
| * Otherwise it's allowed to overwrite the input file as long as the same |
| * path name is used that was used for loading (no symbolic link foo2.odt |
| * pointing to the loaded file foo1.odt, no network path X:\foo.odt pointing |
| * to the loaded file D:\foo.odt). |
| * </p> |
| * |
| * <p> |
| * When saving the embedded document to a stand alone document, all files of |
| * the embedded document will be copied to a new document package. If the |
| * embedded document is outside of the current document directory, you have |
| * to embed it to the sub directory and refresh the link of the embedded |
| * document. You should reload it from the given file to get the saved |
| * embedded document. |
| * |
| * @param file |
| * - the file to save the document |
| * @throws java.lang.Exception |
| * if the document could not be saved |
| */ |
| public void save(File file) throws Exception { |
| // 2DO FLUSH AND SAVE IN PACKAGE |
| flushDoms(); |
| updateMetaData(); |
| if (!isRootDocument()) { |
| Document newDoc = loadDocumentFromTemplate(getOdfMediaType()); |
| newDoc.insertDocument(this, ROOT_DOCUMENT_PATH); |
| newDoc.updateMetaData(); |
| newDoc.mPackage.save(file); |
| // ToDo: (Issue 219 - PackageRefactoring) - Return the document, |
| // when not closing! |
| // Should we close the sources now? User will never receive the open |
| // package! |
| } else { |
| this.mPackage.save(file); |
| } |
| } |
| |
| /** |
| * Save the document to a given file with given password. |
| * |
| * <p> |
| * If the input file has been cached (this is the case when loading from an |
| * InputStream), the input file can be overwritten. |
| * </p> |
| * |
| * <p> |
| * Otherwise it's allowed to overwrite the input file as long as the same |
| * path name is used that was used for loading (no symbolic link foo2.odt |
| * pointing to the loaded file foo1.odt, no network path X:\foo.odt pointing |
| * to the loaded file D:\foo.odt). |
| * </p> |
| * |
| * <p> |
| * When saving the embedded document to a stand alone document, all files of |
| * the embedded document will be copied to a new document package. If the |
| * embedded document is outside of the current document directory, you have |
| * to embed it to the sub directory and refresh the link of the embedded |
| * document. You should reload it from the given file to get the saved |
| * embedded document. |
| * |
| * @param file |
| * the file to save the document. |
| * @param file |
| * the password of this document. |
| * |
| * @throws java.lang.Exception |
| * if the document could not be saved |
| * @since 0.8 |
| */ |
| public void save(File file, String password) throws Exception { |
| // 2DO FLUSH AND SAVE IN PACKAGE |
| flushDoms(); |
| updateMetaData(); |
| if (!isRootDocument()) { |
| Document newDoc = loadDocumentFromTemplate(getOdfMediaType()); |
| newDoc.insertDocument(this, ROOT_DOCUMENT_PATH); |
| newDoc.updateMetaData(); |
| newDoc.mPackage.setPassword(password); |
| newDoc.mPackage.save(file); |
| // ToDo: (Issue 219 - PackageRefactoring) - Return the document, |
| // when not closing! |
| // Should we close the sources now? User will never receive the open |
| // package! |
| } else { |
| mPackage.setPassword(password); |
| mPackage.save(file); |
| } |
| } |
| |
| /** |
| * Close the OdfPackage and release all temporary created data. Acter |
| * execution of this method, this class is no longer usable. Do this as the |
| * last action to free resources. Closing an already closed document has no |
| * effect. Note that this will not close any cached documents. |
| */ |
| @Override |
| public void close() { |
| // set all member variables explicit to null |
| mMediaType = null; |
| mOfficeMeta = null; |
| mComponentRepository.clear(); |
| super.close(); |
| } |
| |
| /** |
| * Get the content root of a document. |
| * |
| * You may prefer to use the getContentRoot methods of subclasses of |
| * Document. Their return parameters are already casted to respective |
| * subclasses of OdfElement. |
| * |
| * @param clazz |
| * the type of the content root, depend on the document type |
| * @return the child element of office:body, e.g. office:text for text docs |
| * @throws Exception |
| * if the file DOM could not be created. |
| */ |
| @SuppressWarnings("unchecked") |
| protected <T extends OdfElement> T getContentRoot(Class<T> clazz) throws Exception { |
| OdfElement contentRoot = getContentDom().getRootElement(); |
| OfficeBodyElement contentBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, contentRoot); |
| NodeList childs = contentBody.getChildNodes(); |
| for (int i = 0; i < childs.getLength(); i++) { |
| Node cur = childs.item(i); |
| if ((cur != null) && clazz.isInstance(cur)) { |
| return (T) cur; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Get the content root of a document. |
| * |
| * You may prefer to use the getContentRoot methods of subclasses of |
| * Document. |
| * |
| * @return the child element of office:body, e.g. office:text for text docs |
| * @throws Exception |
| * if the file DOM could not be created. |
| */ |
| public OdfElement getContentRoot() throws Exception { |
| return getContentRoot(OdfElement.class); |
| } |
| |
| @Override |
| public String toString() { |
| return "\n" + getMediaTypeString() + " - ID: " + this.hashCode() + " " + getPackage().getBaseURI(); |
| } |
| |
| /** |
| * Insert an Image from the specified uri to the end of the Document. |
| * |
| * @param imageUri |
| * The URI of the image that will be added to the document, add |
| * image stream to the package, in the 'Pictures/' graphic |
| * directory with the same image file name as in the URI. If the |
| * imageURI is relative first the user.dir is taken to make it |
| * absolute. |
| * @return Returns the internal package path of the image, which was created |
| * based on the given URI. |
| * */ |
| public String newImage(URI imageUri) { |
| try { |
| OdfContentDom contentDom = this.getContentDom(); |
| OdfDrawFrame drawFrame = contentDom.newOdfElement(OdfDrawFrame.class); |
| XPath xpath = contentDom.getXPath(); |
| if (this instanceof SpreadsheetDocument) { |
| TableTableCellElement lastCell = (TableTableCellElement) xpath.evaluate("//table:table-cell[last()]", contentDom, XPathConstants.NODE); |
| lastCell.appendChild(drawFrame); |
| drawFrame.removeAttribute("text:anchor-type"); |
| |
| } else if (this instanceof TextDocument) { |
| TextPElement lastPara = (TextPElement) xpath.evaluate("//text:p[last()]", contentDom, XPathConstants.NODE); |
| if (lastPara == null) { |
| lastPara = ((TextDocument) this).newParagraph(); |
| } |
| lastPara.appendChild(drawFrame); |
| drawFrame.setTextAnchorTypeAttribute(TextAnchorTypeAttribute.Value.PARAGRAPH.toString()); |
| } else if (this instanceof PresentationDocument) { |
| DrawPageElement lastPage = (DrawPageElement) xpath.evaluate("//draw:page[last()]", contentDom, XPathConstants.NODE); |
| lastPage.appendChild(drawFrame); |
| } |
| OdfDrawImage image = (OdfDrawImage) drawFrame.newDrawImageElement(); |
| String imagePath = image.newImage(imageUri); |
| return imagePath; |
| } catch (Exception ex) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, ex); |
| } |
| return null; |
| } |
| |
| /** |
| * Meta data about the document will be initialized. Following metadata data |
| * is being added: |
| * <ul> |
| * <li>The initial creator name will be the Java user.name System property.</li> |
| * <li>The date and time when this document was created using the current |
| * data.</li> |
| * <li>The number of times this document has been edited.</li> |
| * <li>The default language will be the Java user.language System property.</li> |
| * </ul> |
| * |
| * @param newDoc |
| * the Document object which need to initialize meta data. |
| * |
| * TODO:This method will be moved to OdfMetadata class. see |
| * http://odftoolkit.org/bugzilla/show_bug.cgi?id=204 |
| */ |
| private static void initializeMetaData(Document newDoc) { |
| Meta metaData = newDoc.getOfficeMetadata(); |
| // add initial-creator info. |
| String creator = System.getProperty("user.name"); |
| metaData.setInitialCreator(creator); |
| // add creation-date info. |
| Calendar calendar = Calendar.getInstance(); |
| metaData.setCreationDate(calendar); |
| // add editing-cycles info. |
| metaData.setEditingCycles(0); |
| // add language info. |
| String language = System.getProperty("user.language"); |
| if (language != null) { |
| metaData.setLanguage(language); |
| } |
| } |
| |
| /** |
| * Update document meta data in the ODF document. Following metadata data is |
| * being updated: |
| * <ul> |
| * <li>The name of the person who last modified this document will be the |
| * Java user.name System property</li> |
| * <li>The date and time when the document was last modified using current |
| * data</li> |
| * <li>The number of times this document has been edited is incremented by 1 |
| * </li> |
| * <li>The total time spent editing this document</li> |
| * </ul> |
| * |
| * TODO:This method will be moved to OdfMetadata class. see |
| * http://odftoolkit.org/bugzilla/show_bug.cgi?id=204 |
| * |
| * @throws Exception |
| */ |
| private void updateMetaData() throws Exception { |
| if (mMetaDom != null) { |
| Meta metaData = getOfficeMetadata(); |
| String creator = System.getProperty("user.name"); |
| // update creator info. |
| metaData.setCreator(creator); |
| // update date info. |
| Calendar calendar = Calendar.getInstance(); |
| metaData.setDcdate(calendar); |
| // update editing-cycles info. |
| Integer cycle = metaData.getEditingCycles(); |
| if (cycle != null) { |
| metaData.setEditingCycles(++cycle); |
| } else { |
| metaData.setEditingCycles(1); |
| } |
| // update editing-duration info. |
| long editingDuration = calendar.getTimeInMillis() - documentOpeningTime; |
| editingDuration = (editingDuration < 1) ? 1 : editingDuration; |
| try { |
| DatatypeFactory aFactory = DatatypeFactory.newInstance(); |
| metaData.setEditingDuration(new Duration(aFactory.newDurationDayTime(editingDuration))); |
| } catch (DatatypeConfigurationException e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "editing duration update fail as DatatypeFactory can not be instanced", e); |
| } |
| } |
| } |
| |
| // ///////////////// |
| // Following is the implementation of locale settings |
| // //////////////// |
| |
| /** |
| * <p> |
| * Unicode characters are in general divided by office applications into |
| * three different types: |
| * |
| * <p> |
| * 1) There is CJK: the Chinese, Japanese and Korean script (also old |
| * Vietnamese belong to this group). See |
| * http://en.wikipedia.org/wiki/CJK_characters |
| * |
| * <p> |
| * 2) There is CTL: Complex Text Layout, which uses BIDI algorithms and/or |
| * glyph modules. See http://en.wikipedia.org/wiki/Complex_Text_Layout |
| * |
| * <p> |
| * 3) And there is all the rest, which was once called by MS Western. |
| */ |
| public enum ScriptType { |
| /** |
| * Western language |
| */ |
| WESTERN, |
| /** |
| * Chinese, Japanese and Korean |
| */ |
| CJK, |
| /** |
| * Complex Text Layout language |
| */ |
| CTL; |
| |
| } |
| |
| private final static HashSet<String> CJKLanguage = new HashSet<String>(); |
| private final static HashSet<String> CTLLanguage = new HashSet<String>(); |
| { |
| CJKLanguage.add("zh"); // LANGUAGE_CHINES |
| CJKLanguage.add("ja"); // LANGUAGE_JAPANESE |
| CJKLanguage.add("ko"); // LANGUAGE_KOREANE |
| |
| CTLLanguage.add("am"); // LANGUAGE_AMHARIC_ETHIOPIA |
| CTLLanguage.add("ar"); // LANGUAGE_ARABIC_SAUDI_ARABIA |
| CTLLanguage.add("as"); // LANGUAGE_ASSAMESE |
| CTLLanguage.add("bn"); // LANGUAGE_BENGALI |
| CTLLanguage.add("bo"); // LANGUAGE_TIBETAN |
| CTLLanguage.add("brx");// LANGUAGE_USER_BODO_INDIA |
| CTLLanguage.add("dgo");// LANGUAGE_USER_DOGRI_INDIA |
| CTLLanguage.add("dv"); // LANGUAGE_DHIVEHI |
| CTLLanguage.add("dz"); // LANGUAGE_DZONGKHA |
| CTLLanguage.add("fa"); // LANGUAGE_FARSI |
| CTLLanguage.add("gu"); // LANGUAGE_GUJARATI |
| CTLLanguage.add("he"); // LANGUAGE_HEBREW |
| CTLLanguage.add("hi"); // LANGUAGE_HINDI |
| CTLLanguage.add("km"); // LANGUAGE_KHMER |
| CTLLanguage.add("kn"); // LANGUAGE_KANNADA |
| CTLLanguage.add("ks"); // LANGUAGE_KASHMIRI |
| CTLLanguage.add("ku"); // LANGUAGE_USER_KURDISH_IRAQ |
| CTLLanguage.add("lo"); // LANGUAGE_LAO |
| CTLLanguage.add("mai");// LANGUAGE_USER_MAITHILI_INDIA |
| CTLLanguage.add("ml"); // LANGUAGE_MALAYALAM |
| CTLLanguage.add("mn"); // LANGUAGE_MONGOLIAN_MONGOLIAN |
| CTLLanguage.add("mni");// LANGUAGE_MANIPURI |
| CTLLanguage.add("mr"); // LANGUAGE_MARATHI |
| CTLLanguage.add("my"); // LANGUAGE_BURMESE |
| CTLLanguage.add("ne"); // LANGUAGE_NEPALI |
| CTLLanguage.add("or"); // LANGUAGE_ORIYA |
| CTLLanguage.add("pa"); // LANGUAGE_PUNJABI |
| CTLLanguage.add("sa"); // LANGUAGE_SANSKRIT |
| CTLLanguage.add("sd"); // LANGUAGE_SINDHI |
| CTLLanguage.add("si"); // LANGUAGE_SINHALESE_SRI_LANKA |
| CTLLanguage.add("syr");// LANGUAGE_SYRIAC |
| CTLLanguage.add("ta"); // LANGUAGE_TAMIL |
| CTLLanguage.add("te"); // LANGUAGE_TELUGU |
| CTLLanguage.add("th"); // LANGUAGE_THAI |
| CTLLanguage.add("ug"); // LANGUAGE_UIGHUR_CHINA |
| CTLLanguage.add("ur"); // LANGUAGE_URDU |
| CTLLanguage.add("yi"); // LANGUAGE_YIDDISH |
| } |
| |
| /** |
| * <p> |
| * Set a locale information. |
| * <p> |
| * The locale information will affect the language and country setting of |
| * the document. Thus the font settings, the spell checkings and etc will be |
| * affected. |
| * |
| * @param locale |
| * - an instance of Locale |
| */ |
| public void setLocale(Locale locale) { |
| setLocale(locale, getScriptType(locale)); |
| } |
| |
| public static ScriptType getScriptType(Locale locale) { |
| String language = locale.getLanguage(); |
| if (CJKLanguage.contains(language)) |
| return ScriptType.CJK; |
| if (CTLLanguage.contains(language)) |
| return ScriptType.CTL; |
| return ScriptType.WESTERN; |
| |
| } |
| |
| /** |
| * <p> |
| * Set a locale of a specific script type. |
| * <p> |
| * If the locale is not belone to the script type, nothing will happen. |
| * |
| * @param locale |
| * - Locale information |
| * @param scriptType |
| * - The script type |
| */ |
| public void setLocale(Locale locale, ScriptType scriptType) { |
| try { |
| switch (scriptType) { |
| case WESTERN: |
| setWesternLanguage(locale); |
| break; |
| case CJK: |
| setDefaultAsianLanguage(locale); |
| break; |
| case CTL: |
| setDefaultComplexLanguage(locale); |
| break; |
| } |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed to set locale", e); |
| } |
| } |
| |
| /** |
| * <p> |
| * Get a locale information of a specific script type. |
| * |
| * @param scriptType |
| * - The script type |
| * @return the Locale information |
| */ |
| public Locale getLocale(ScriptType scriptType) { |
| try { |
| switch (scriptType) { |
| case WESTERN: |
| return getDefaultLanguageByProperty(OdfTextProperties.Country, OdfTextProperties.Language); |
| case CJK: |
| return getDefaultLanguageByProperty(OdfTextProperties.CountryAsian, OdfTextProperties.LanguageAsian); |
| case CTL: |
| return getDefaultLanguageByProperty(OdfTextProperties.CountryComplex, OdfTextProperties.LanguageComplex); |
| } |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed to get locale", e); |
| } |
| return null; |
| } |
| |
| /** |
| * This method will set the default language and country information of the |
| * document, based on the parameter of the Locale information. |
| * |
| * @param locale |
| * - an instance of Locale that the default language and country |
| * will be set to. |
| * @throws Exception |
| */ |
| private void setWesternLanguage(Locale locale) throws Exception { |
| if (getScriptType(locale) != ScriptType.WESTERN) |
| return; |
| |
| OdfOfficeStyles styles = getStylesDom().getOfficeStyles(); |
| Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles(); |
| if (defaultStyles != null) { |
| Iterator<OdfDefaultStyle> itera = defaultStyles.iterator(); |
| while (itera.hasNext()) { |
| OdfDefaultStyle style = itera.next(); |
| if (style.getFamily().getProperties().contains(OdfTextProperties.Language)) { |
| style.setProperty(OdfTextProperties.Language, locale.getLanguage()); |
| style.setProperty(OdfTextProperties.Country, locale.getCountry()); |
| } |
| } |
| } |
| } |
| |
| private Locale getDefaultLanguageByProperty(OdfStyleProperty countryProp, OdfStyleProperty languageProp) throws Exception { |
| String lang = null, ctry = null; |
| |
| OdfOfficeStyles styles = getStylesDom().getOfficeStyles(); |
| |
| // get language and country setting from default style setting for |
| // paragraph |
| OdfDefaultStyle defaultStyle = styles.getDefaultStyle(OdfStyleFamily.Paragraph); |
| if (defaultStyle != null) { |
| if (defaultStyle.hasProperty(countryProp) && defaultStyle.hasProperty(languageProp)) { |
| ctry = defaultStyle.getProperty(countryProp); |
| lang = defaultStyle.getProperty(languageProp); |
| return new Locale(lang, ctry); |
| } |
| } |
| // if no default style setting for paragraph |
| // get language and country setting from other default style settings |
| Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles(); |
| Iterator<OdfDefaultStyle> itera = defaultStyles.iterator(); |
| while (itera.hasNext()) { |
| OdfDefaultStyle style = itera.next(); |
| if (style.hasProperty(countryProp) && style.hasProperty(languageProp)) { |
| ctry = style.getProperty(countryProp); |
| lang = style.getProperty(languageProp); |
| return new Locale(lang, ctry); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * This method will return an instance of Locale, which presents the default |
| * language and country information settings in this document. |
| * |
| * @return an instance of Locale that the default language and country is |
| * set to. |
| */ |
| |
| /** |
| * This method will set the default Asian language and country information |
| * of the document, based on the parameter of the Locale information. If the |
| * Locale instance is not set a Asian language (Chinese, Traditional |
| * Chinese, Japanese and Korean, nothing will take effect. |
| * |
| * @param locale |
| * - an instance of Locale that the default Asian language and |
| * country will be set to. |
| * @throws Exception |
| */ |
| private void setDefaultAsianLanguage(Locale locale) throws Exception { |
| if (getScriptType(locale) != ScriptType.CJK) |
| return; |
| String user_language = locale.getLanguage(); |
| if (!user_language.equals(Locale.CHINESE.getLanguage()) && !user_language.equals(Locale.TRADITIONAL_CHINESE.getLanguage()) |
| && !user_language.equals(Locale.JAPANESE.getLanguage()) && !user_language.equals(Locale.KOREAN.getLanguage())) |
| return; |
| |
| OdfOfficeStyles styles = getStylesDom().getOfficeStyles(); |
| Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles(); |
| if (defaultStyles != null) { |
| Iterator<OdfDefaultStyle> itera = defaultStyles.iterator(); |
| while (itera.hasNext()) { |
| OdfDefaultStyle style = itera.next(); |
| if (style.getFamily().getProperties().contains(OdfTextProperties.LanguageAsian)) { |
| style.setProperty(OdfTextProperties.LanguageAsian, locale.getLanguage()); |
| style.setProperty(OdfTextProperties.CountryAsian, locale.getCountry()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method will set the default complex language and country information |
| * of the document, based on the parameter of the Locale information. |
| * |
| * @param locale |
| * - an instance of Locale that the default complex language and |
| * country will be set to. |
| * @throws Exception |
| */ |
| private void setDefaultComplexLanguage(Locale locale) throws Exception { |
| if (getScriptType(locale) != ScriptType.CTL) |
| return; |
| OdfOfficeStyles styles = getStylesDom().getOfficeStyles(); |
| Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles(); |
| if (defaultStyles != null) { |
| Iterator<OdfDefaultStyle> itera = defaultStyles.iterator(); |
| while (itera.hasNext()) { |
| OdfDefaultStyle style = itera.next(); |
| if (style.getFamily().getProperties().contains(OdfTextProperties.LanguageComplex)) { |
| style.setProperty(OdfTextProperties.LanguageComplex, locale.getLanguage()); |
| style.setProperty(OdfTextProperties.CountryComplex, locale.getCountry()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method will search both the document content and header/footer, |
| * return an iterator of section objects. |
| * <p> |
| * The sections defined in embed document won't be covered. |
| * |
| * @return an iterator of Section objects |
| */ |
| public Iterator<Section> getSectionIterator() { |
| TextSectionElement element; |
| ArrayList<Section> list = new ArrayList<Section>(); |
| try { |
| // search in content.xml |
| OdfElement root = getContentDom().getRootElement(); |
| OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root); |
| NodeList sectionList = officeBody.getElementsByTagNameNS(OdfDocumentNamespace.TEXT.getUri(), "section"); |
| for (int i = 0; i < sectionList.getLength(); i++) { |
| element = (TextSectionElement) sectionList.item(i); |
| list.add(Section.getInstance(element)); |
| } |
| |
| // Search in style.xml |
| root = getStylesDom().getRootElement(); |
| OfficeMasterStylesElement masterStyle = OdfElement.findFirstChildNode(OfficeMasterStylesElement.class, root); |
| sectionList = masterStyle.getElementsByTagNameNS(OdfDocumentNamespace.TEXT.getUri(), "section"); |
| for (int i = 0; i < sectionList.getLength(); i++) { |
| element = (TextSectionElement) sectionList.item(i); |
| list.add(Section.getInstance(element)); |
| } |
| |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in sectionIterator", e); |
| } |
| return list.iterator(); |
| } |
| |
| /** |
| * This method will search both the document content and header/footer, |
| * return a section with a specific name. |
| * <p> |
| * This method won't search in the embed document. |
| * <p> |
| * Null will be returned if there is no section found. |
| * |
| * @param name |
| * - the name of a section |
| * @return a section object with a specific name |
| */ |
| public Section getSectionByName(String name) { |
| TextSectionElement element; |
| try { |
| OdfElement root = getContentDom().getRootElement(); |
| OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root); |
| XPath xpath = getContentDom().getXPath(); |
| String xpathValue = ".//text:section[@text:name=\"" + name + "\"]"; |
| element = (TextSectionElement) xpath.evaluate(xpathValue, officeBody, XPathConstants.NODE); |
| if (element != null) { |
| return Section.getInstance(element); |
| } |
| |
| root = getStylesDom().getRootElement(); |
| OfficeMasterStylesElement masterStyle = OdfElement.findFirstChildNode(OfficeMasterStylesElement.class, root); |
| xpath = getStylesDom().getXPath(); |
| element = (TextSectionElement) xpath.evaluate(".//text:section[@text:name=\"" + name + "\"]", masterStyle, XPathConstants.NODE); |
| if (element != null) { |
| return Section.getInstance(element); |
| } |
| |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in getSectionByName", e); |
| } |
| |
| return null; |
| |
| } |
| |
| /** |
| * Remove an ODF element from the document. All the resources that are only |
| * related with this element will be removed at the same time. |
| * |
| * @param odfElement |
| * - the odf element that would be moved. |
| */ |
| public boolean removeElementLinkedResource(OdfElement odfElement) { |
| boolean success = deleteLinkedRef(odfElement); |
| success &= deleteStyleRef(odfElement); |
| return success; |
| } |
| |
| /** |
| * Return a unique string with a character "a" followed by randomly |
| * generating 6 hex numbers |
| * |
| * @return a unique string |
| */ |
| String makeUniqueName() { |
| return String.format("a%06x", (int) (Math.random() * 0xffffff)); |
| } |
| |
| private String getNewUniqueString(String oldStr) { |
| int lastIndex = oldStr.lastIndexOf("-"); |
| if (lastIndex == -1) { |
| return oldStr + "-" + makeUniqueName(); |
| } |
| String suffix = oldStr.substring(lastIndex + 1); |
| if (suffix.matches("a[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]")) { |
| return oldStr.substring(0, lastIndex + 1) + makeUniqueName(); |
| } else |
| return oldStr + "-" + makeUniqueName(); |
| } |
| |
| private void updateAttribute(Attr attr) { |
| String oldID = attr.getValue(); |
| String newID = getNewUniqueString(oldID); |
| attr.setValue(newID); |
| } |
| |
| /** |
| * Make a content copy of the specified element, and the returned element |
| * should have the specified ownerDocument. |
| * |
| * @param element |
| * The element that need to be copied |
| * @param dom |
| * The specified DOM tree that the returned element belong to |
| * @param deep |
| * If true, recursively clone the subtree under the element, |
| * false, only clone the element itself |
| * @return Returns a duplicated element which is not in the DOM tree with |
| * the specified element |
| */ |
| Node cloneForeignElement(Node element, OdfFileDom dom, boolean deep) { |
| if (element instanceof OdfElement) { |
| OdfElement cloneElement = dom.createElementNS(((OdfElement) element).getOdfName()); |
| |
| NamedNodeMap attributes = element.getAttributes(); |
| if (attributes != null) { |
| for (int i = 0; i < attributes.getLength(); i++) { |
| Node item = attributes.item(i); |
| String qname = null; |
| String prefix = item.getPrefix(); |
| if (prefix == null) { |
| qname = item.getLocalName(); |
| cloneElement.setAttribute(qname, item.getNodeValue()); |
| } else { |
| qname = prefix + ":" + item.getLocalName(); |
| cloneElement.setAttributeNS(item.getNamespaceURI(), qname, item.getNodeValue()); |
| } |
| } |
| } |
| |
| if (deep) { |
| Node childNode = element.getFirstChild(); |
| while (childNode != null) { |
| cloneElement.appendChild(cloneForeignElement(childNode, dom, true)); |
| childNode = childNode.getNextSibling(); |
| } |
| } |
| |
| return cloneElement; |
| } else { |
| return dom.createTextNode(element.getNodeValue()); |
| } |
| |
| } |
| |
| /** |
| * This method will update all the attribute "xml:id" to make it unique |
| * within the whole document content |
| * <p> |
| * This method is usually be invoked before inserting a copied ODF element |
| * to document content. |
| * |
| * @param element |
| * - the element that need to be inserted. |
| */ |
| void updateXMLIds(OdfElement element) { |
| try { |
| XPath xpath = getContentDom().getXPath(); |
| String xpathValue = "//*[@xml:id]"; |
| NodeList childList = (NodeList) xpath.evaluate(xpathValue, element, XPathConstants.NODESET); |
| if (childList == null) |
| return; |
| |
| for (int i = 0; i < childList.getLength(); i++) { |
| OdfElement ele = (OdfElement) childList.item(i); |
| Attr attri = ele.getAttributeNodeNS(OdfDocumentNamespace.XML.getUri(), "id"); |
| updateAttribute(attri); |
| } |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in updateXMLIds", e); |
| } |
| } |
| |
| /** |
| * This method will update all the attribute |
| * "text:name","table:name","draw:name","chart:name", to make it unique |
| * within the whole document content. |
| * <p> |
| * This method is usually be invoked before inserting a copied ODF element |
| * to document content. |
| * |
| * @param element |
| * - the element that need to be inserted. |
| */ |
| // anim:name, chart:name, config:name, office:name, presentation:name, |
| // svg:name, |
| void updateNames(OdfElement element) { |
| try { |
| XPath xpath = getContentDom().getXPath(); |
| String xpathValue = "descendant-or-self::node()[@text:name|@table:name|@draw:name|@chart:name]"; |
| NodeList childList = (NodeList) xpath.evaluate(xpathValue, element, XPathConstants.NODESET); |
| if (childList == null) |
| return; |
| for (int i = 0; i < childList.getLength(); i++) { |
| OdfElement ele = (OdfElement) childList.item(i); |
| Attr attri = ele.getAttributeNodeNS(OdfDocumentNamespace.TEXT.getUri(), "name"); |
| if (attri != null) |
| updateAttribute(attri); |
| attri = ele.getAttributeNodeNS(OdfDocumentNamespace.TABLE.getUri(), "name"); |
| if (attri != null) |
| updateAttribute(attri); |
| if (ele instanceof DrawFrameElement)// only update draw:frame |
| { |
| attri = ele.getAttributeNodeNS(OdfDocumentNamespace.DRAW.getUri(), "name"); |
| if (attri != null) |
| updateAttribute(attri); |
| } |
| } |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, "Failed in updateXMLIds", e); |
| } |
| } |
| |
| /** |
| * This method will copy the linked resource of the element which need to be |
| * copied, from the source package to the target package. |
| * <p> |
| * If the target package contains a resource with the same path and name, |
| * the name of the resource will be renamed. |
| * <p> |
| * This method will copy resources all in one batch. |
| * |
| * @param sourceCloneEle |
| * - the element that need to be copied |
| * @param srcDocument |
| * - the source document |
| */ |
| void copyLinkedRefInBatch(OdfElement sourceCloneEle, Document srcDocument) { |
| try { |
| OdfFileDom fileDom = (OdfFileDom) sourceCloneEle.getOwnerDocument(); |
| XPath xpath; |
| if (fileDom instanceof OdfContentDom) { |
| xpath = ((OdfContentDom) fileDom).getXPath(); |
| } else { |
| xpath = ((OdfStylesDom) fileDom).getXPath(); |
| } |
| // OdfPackageDocument srcDoc = fileDom.getDocument(); |
| // new a map to put the original name and the rename string, in case |
| // that the same name might be referred by the slide several times. |
| HashMap<String, String> objectRenameMap = new HashMap<String, String>(); |
| NodeList linkNodes = (NodeList) xpath.evaluate(".//*[@xlink:href]", sourceCloneEle, XPathConstants.NODESET); |
| for (int i = 0; i <= linkNodes.getLength(); i++) { |
| OdfElement object = null; |
| if (linkNodes.getLength() == i) { |
| if (sourceCloneEle.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) { |
| object = sourceCloneEle; |
| } else { |
| break; |
| } |
| } else { |
| object = (OdfElement) linkNodes.item(i); |
| } |
| String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href"); |
| if (refObjPath != null && refObjPath.length() > 0) { |
| // the path of the object is start with "./" |
| boolean hasPrefix = false; |
| String prefix = "./"; |
| String newObjPath; |
| if (refObjPath.startsWith(prefix)) { |
| refObjPath = refObjPath.substring(2); |
| hasPrefix = true; |
| } |
| // check if this linked resource has been copied |
| if (objectRenameMap.containsKey(refObjPath)) { |
| // this object has been copied already |
| newObjPath = objectRenameMap.get(refObjPath); |
| object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath); |
| continue; |
| } |
| // check if the current document contains the same path |
| OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath); |
| // note: if refObjPath is a directory, it must end with '/' |
| if (fileEntry == null) { |
| fileEntry = getPackage().getFileEntry(refObjPath + "/"); |
| } |
| if (fileEntry != null) { |
| // rename the object path |
| newObjPath = objectRenameMap.get(refObjPath); |
| if (newObjPath == null) { |
| // if refObjPath still contains ".", it means that |
| // it has the suffix |
| // then change the name before the suffix string |
| int dotIndex = refObjPath.indexOf("."); |
| if (dotIndex != -1) { |
| newObjPath = refObjPath.substring(0, dotIndex) + "-" + makeUniqueName() + refObjPath.substring(dotIndex); |
| } else { |
| newObjPath = refObjPath + "-" + makeUniqueName(); |
| } |
| objectRenameMap.put(refObjPath, newObjPath); |
| } |
| object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath); |
| } else |
| objectRenameMap.put(refObjPath, refObjPath); |
| } |
| } |
| // copy resource in batch |
| copyResourcesFrom(srcDocument, objectRenameMap); |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| } |
| } |
| |
| /*****************************/ |
| /* |
| * These codes are moved from OdfPackage, and should be removed till |
| * OdfPackage can provide a mechanism to copy resources in batch. |
| */ |
| /*****************************/ |
| private InputStream readAsInputStream(ZipInputStream inputStream) throws IOException { |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| if (outputStream != null) { |
| byte[] buf = new byte[4096]; |
| int r = 0; |
| while ((r = inputStream.read(buf, 0, 4096)) > -1) { |
| outputStream.write(buf, 0, r); |
| } |
| } |
| return new ByteArrayInputStream(outputStream.toByteArray()); |
| } |
| |
| private String normalizeFilePath(String internalPath) { |
| if (internalPath.equals(EMPTY_STRING)) { |
| String errMsg = "The internalPath given by parameter is an empty string!"; |
| Logger.getLogger(OdfPackage.class.getName()).severe(errMsg); |
| throw new IllegalArgumentException(errMsg); |
| } else { |
| return normalizePath(internalPath); |
| } |
| } |
| |
| private static final String DOUBLE_DOT = ".."; |
| private static final String DOT = "."; |
| private static final String COLON = ":"; |
| private static final Pattern BACK_SLASH_PATTERN = Pattern.compile("\\\\"); |
| private static final Pattern DOUBLE_SLASH_PATTERN = Pattern.compile("//"); |
| |
| private String normalizePath(String path) { |
| if (path == null) { |
| String errMsg = "The internalPath given by parameter is NULL!"; |
| Logger.getLogger(OdfPackage.class.getName()).severe(errMsg); |
| throw new IllegalArgumentException(errMsg); |
| } else if (!mightBeExternalReference(path)) { |
| if (path.equals(EMPTY_STRING)) { |
| path = SLASH; |
| } else { |
| // exchange all backslash "\" with a slash "/" |
| if (path.indexOf('\\') != -1) { |
| path = BACK_SLASH_PATTERN.matcher(path).replaceAll(SLASH); |
| } |
| // exchange all double slash "//" with a slash "/" |
| while (path.indexOf("//") != -1) { |
| path = DOUBLE_SLASH_PATTERN.matcher(path).replaceAll(SLASH); |
| } |
| // if directory replacements (e.g. ..) exist, resolve and remove |
| // them |
| if (path.indexOf("/.") != -1 || path.indexOf("./") != -1) { |
| path = removeChangeDirectories(path); |
| } |
| } |
| } |
| return path; |
| } |
| |
| private boolean mightBeExternalReference(String internalPath) { |
| boolean isExternalReference = false; |
| // if the fileReference is a external relative documentURL.. |
| if (internalPath.startsWith(DOUBLE_DOT) || // or absolute documentURL |
| // AND not root document |
| internalPath.startsWith(SLASH) && !internalPath.equals(SLASH) || // or |
| // absolute |
| // IRI |
| internalPath.contains(COLON)) { |
| isExternalReference = true; |
| } |
| return isExternalReference; |
| } |
| |
| private String removeChangeDirectories(String path) { |
| boolean isDirectory = path.endsWith(SLASH); |
| StringTokenizer tokenizer = new StringTokenizer(path, SLASH); |
| int tokenCount = tokenizer.countTokens(); |
| List<String> tokenList = new ArrayList<String>(tokenCount); |
| // add all paths to a list |
| while (tokenizer.hasMoreTokens()) { |
| String token = tokenizer.nextToken(); |
| tokenList.add(token); |
| } |
| if (!isDirectory) { |
| String lastPath = tokenList.get(tokenCount - 1); |
| if (lastPath.equals(DOT) || lastPath.equals(DOUBLE_DOT)) { |
| isDirectory = true; |
| } |
| } |
| String currentToken; |
| int removeDirLevel = 0; |
| StringBuilder out = new StringBuilder(); |
| // work on the list from back to front |
| for (int i = tokenCount - 1; i >= 0; i--) { |
| currentToken = tokenList.get(i); |
| // every ".." will remove an upcoming path |
| if (currentToken.equals(DOUBLE_DOT)) { |
| removeDirLevel++; |
| } else if (currentToken.equals(DOT)) { |
| } else { |
| // if a path have to be remove, neglect current path |
| if (removeDirLevel > 0) { |
| removeDirLevel--; |
| } else { |
| // add the path segment |
| out.insert(0, SLASH); |
| out.insert(0, currentToken); |
| } |
| } |
| } |
| if (removeDirLevel > 0) { |
| return EMPTY_STRING; |
| } else { |
| if (!isDirectory) { |
| // remove trailing slash / |
| out.deleteCharAt(out.length() - 1); |
| } |
| return out.toString(); |
| } |
| } |
| |
| /*****************************/ |
| // FIXME: These two methods are only used in method copyResourcesFrom to |
| // improve copy performance, should not be used in any other way. |
| // methods loadDocument(String documentPath) and loadDocument(File file) |
| // will initialize mFile. |
| // This field and these two methods should be removed after ODFDOM supplies |
| // batch copy. |
| private void setFile(File thisFile) { |
| mFile = thisFile; |
| } |
| |
| private File getFile() { |
| return mFile; |
| } |
| |
| /** |
| * This method will copy resources from source document to this document. |
| * The second parameter contains a map between all the name of resources in |
| * the source document and the rename string. If the source document is |
| * loaded from a file, a good performance method will be used. If the source |
| * document is loaded from a input stream, package layer methods will be |
| * invoked to copy these resources, with bad performance. |
| * |
| * In future, the code of this method will move to ODFDOM package layer. |
| * Till then, good performance will be gotten whether the source document is |
| * loaded from file or from input stream. |
| * |
| */ |
| void copyResourcesFrom(Document srcDoc, HashMap<String, String> objectRenameMap) throws Exception { |
| if (srcDoc.getFile() != null) { |
| ArrayList<String> copiedFolder = new ArrayList<String>(); |
| Set<String> refObjPathSet = objectRenameMap.keySet(); |
| FileInputStream tempFileStream = new FileInputStream(srcDoc.getFile()); |
| ZipInputStream zipStream = new ZipInputStream(tempFileStream); |
| ZipEntry zipEntry = zipStream.getNextEntry(); |
| while (zipEntry != null) { |
| String refObjPath = zipEntry.getName(); |
| for (String path : refObjPathSet) { |
| if (refObjPath.equals(path)) { |
| String newObjPath = objectRenameMap.get(refObjPath); |
| refObjPath = normalizeFilePath(refObjPath); |
| String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString(); |
| InputStream is = readAsInputStream(zipStream); |
| getPackage().insert(is, newObjPath, mediaType); |
| break; |
| } else if (refObjPath.startsWith(path + "/")) { |
| String suffix = refObjPath.substring(path.length()); |
| String newObjPath = objectRenameMap.get(path) + suffix; |
| refObjPath = normalizeFilePath(refObjPath); |
| String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString(); |
| InputStream is = readAsInputStream(zipStream); |
| getPackage().insert(is, newObjPath, mediaType); |
| if (!copiedFolder.contains(path)) { |
| mediaType = srcDoc.getPackage().getFileEntry(path + "/").getMediaTypeString(); |
| getPackage().insert((InputStream) null, objectRenameMap.get(path) + "/", mediaType); |
| copiedFolder.add(path); |
| } |
| break; |
| } |
| } |
| zipEntry = zipStream.getNextEntry(); |
| } |
| zipStream.close(); |
| tempFileStream.close(); |
| } else { |
| Set<String> refObjPathSet = objectRenameMap.keySet(); |
| for (String refObjPath : refObjPathSet) { |
| String newObjPath = objectRenameMap.get(refObjPath); |
| InputStream is = srcDoc.getPackage().getInputStream(refObjPath); |
| if (is != null) { |
| String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString(); |
| getPackage().insert(is, newObjPath, mediaType); |
| } else { |
| Document embedDoc = ((Document) srcDoc).getEmbeddedDocument(refObjPath); |
| if (embedDoc != null) { |
| insertDocument(embedDoc, newObjPath); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method will copy the linked resource of the element which need to be |
| * copied, from the source package to the target package. |
| * <p> |
| * If the target package contains a resource with the same path and name, |
| * the name of the resource will be renamed. |
| * |
| * @param sourceCloneEle |
| * - the element that need to be copied |
| */ |
| // clone the source clone element's referred object path to the current |
| // package |
| // if the current package contains the same name with the referred object |
| // path, |
| // rename the object path and path reference of this slide element |
| // notes: the source clone element is the copied one to avoid changing the |
| // content of the source document. |
| void copyLinkedRef(OdfElement sourceCloneEle) { |
| try { |
| OdfFileDom fileDom = (OdfFileDom) sourceCloneEle.getOwnerDocument(); |
| XPath xpath; |
| if (fileDom instanceof OdfContentDom) { |
| xpath = ((OdfContentDom) fileDom).getXPath(); |
| } else { |
| xpath = ((OdfStylesDom) fileDom).getXPath(); |
| } |
| OdfPackageDocument srcDoc = fileDom.getDocument(); |
| // new a map to put the original name and the rename string, in case |
| // that the same name might be referred by the slide several times. |
| HashMap<String, String> objectRenameMap = new HashMap<String, String>(); |
| NodeList linkNodes = (NodeList) xpath.evaluate(".//*[@xlink:href]", sourceCloneEle, XPathConstants.NODESET); |
| for (int i = 0; i <= linkNodes.getLength(); i++) { |
| OdfElement object = null; |
| if (linkNodes.getLength() == i) { |
| if (sourceCloneEle.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) { |
| object = sourceCloneEle; |
| } else { |
| break; |
| } |
| } else { |
| object = (OdfElement) linkNodes.item(i); |
| } |
| String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href"); |
| if (refObjPath != null && refObjPath.length() > 0) { |
| // the path of the object is start with "./" |
| boolean hasPrefix = false; |
| String prefix = "./"; |
| if (refObjPath.startsWith(prefix)) { |
| refObjPath = refObjPath.substring(2); |
| hasPrefix = true; |
| } |
| // check if the current document contains the same path |
| OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath); |
| // note: if refObjPath is a directory, it must end with '/' |
| if (fileEntry == null) { |
| fileEntry = getPackage().getFileEntry(refObjPath + "/"); |
| } |
| String newObjPath = refObjPath; |
| if (fileEntry != null) { |
| // rename the object path |
| newObjPath = objectRenameMap.get(refObjPath); |
| if (newObjPath == null) { |
| // if refObjPath still contains ".", it means that |
| // it has the suffix |
| // then change the name before the suffix string |
| int dotIndex = refObjPath.indexOf("."); |
| if (dotIndex != -1) { |
| newObjPath = refObjPath.substring(0, dotIndex) + "-" + makeUniqueName() + refObjPath.substring(dotIndex); |
| } else { |
| newObjPath = refObjPath + "-" + makeUniqueName(); |
| } |
| objectRenameMap.put(refObjPath, newObjPath); |
| } |
| object.setAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "xlink:href", hasPrefix ? (prefix + newObjPath) : newObjPath); |
| } |
| InputStream is = srcDoc.getPackage().getInputStream(refObjPath); |
| if (is != null) { |
| String mediaType = srcDoc.getPackage().getFileEntry(refObjPath).getMediaTypeString(); |
| getPackage().insert(is, newObjPath, mediaType); |
| } else { |
| Document embedDoc = ((Document) srcDoc).getEmbeddedDocument(refObjPath); |
| if (embedDoc != null) { |
| insertDocument(embedDoc, newObjPath); |
| } |
| } |
| } |
| } |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| } |
| } |
| |
| /** |
| * When a element needs to be copied to a different document, all the style |
| * definitions that are related with this element need to be copied. |
| * |
| * @param sourceCloneEle |
| * - the element that need to be copied |
| * @param srcDoc |
| * - the source document |
| */ |
| void copyForeignStyleRef(OdfElement sourceCloneEle, Document srcDoc) { |
| try { |
| ArrayList<String> tempList = new ArrayList<String>(); |
| OdfFileDom srcContentDom = srcDoc.getContentDom(); |
| XPath xpath = srcContentDom.getXPath(); |
| // 1. collect all the referred style element which has "style:name" |
| // attribute |
| // 1.1. style:name of content.xml |
| String styleQName = "style:name"; |
| NodeList srcStyleDefNodeList = (NodeList) xpath.evaluate("*/office:automatic-styles/*[@" + styleQName + "]", srcContentDom, XPathConstants.NODESET); |
| IdentityHashMap<OdfElement, List<OdfElement>> srcContentStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>(); |
| IdentityHashMap<OdfElement, OdfElement> appendContentStyleList = new IdentityHashMap<OdfElement, OdfElement>(); |
| getCopyStyleList(null, sourceCloneEle, styleQName, srcStyleDefNodeList, srcContentStyleCloneEleList, appendContentStyleList, tempList, true); |
| // 1.2. style:name of styles.xml |
| srcStyleDefNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", srcDoc.getStylesDom(), XPathConstants.NODESET); |
| IdentityHashMap<OdfElement, List<OdfElement>> srcStylesStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>(); |
| IdentityHashMap<OdfElement, OdfElement> appendStylesStyleList = new IdentityHashMap<OdfElement, OdfElement>(); |
| tempList.clear(); |
| getCopyStyleList(null, sourceCloneEle, styleQName, srcStyleDefNodeList, srcStylesStyleCloneEleList, appendStylesStyleList, tempList, true); |
| // 1.3 rename, copy the referred style element to the corresponding |
| // position in the dom tree |
| insertCollectedStyle(styleQName, srcContentStyleCloneEleList, getContentDom(), appendContentStyleList); |
| insertCollectedStyle(styleQName, srcStylesStyleCloneEleList, getStylesDom(), appendStylesStyleList); |
| |
| // 2. collect all the referred style element which has "draw:name" |
| // attribute |
| // 2.1 draw:name of styles.xml |
| // the value of draw:name is string or StyleName, |
| // only when the value is StyleName type, the style definition |
| // should be cloned to the destination document |
| // in ODF spec, such attribute type is only exist in <office:styles> |
| // element, so only search it in styles.xml dom |
| tempList.clear(); |
| styleQName = "draw:name"; |
| srcStyleDefNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", srcDoc.getStylesDom(), XPathConstants.NODESET); |
| IdentityHashMap<OdfElement, List<OdfElement>> srcDrawStyleCloneEleList = new IdentityHashMap<OdfElement, List<OdfElement>>(); |
| IdentityHashMap<OdfElement, OdfElement> appendDrawStyleList = new IdentityHashMap<OdfElement, OdfElement>(); |
| Iterator<OdfElement> iter = appendContentStyleList.keySet().iterator(); |
| while (iter.hasNext()) { |
| OdfElement styleElement = iter.next(); |
| OdfElement cloneStyleElement = appendContentStyleList.get(styleElement); |
| getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, tempList, |
| false); |
| } |
| iter = appendStylesStyleList.keySet().iterator(); |
| while (iter.hasNext()) { |
| OdfElement styleElement = iter.next(); |
| OdfElement cloneStyleElement = appendStylesStyleList.get(styleElement); |
| getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleDefNodeList, srcDrawStyleCloneEleList, appendDrawStyleList, tempList, |
| false); |
| } |
| // 2.2 rename, copy the referred style element to the corresponding |
| // position in the dom tree |
| // note: "draw:name" style element only exist in styles.dom |
| insertCollectedStyle(styleQName, srcDrawStyleCloneEleList, getStylesDom(), appendDrawStyleList); |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| } |
| |
| } |
| |
| // 1. modified the style name of the style definition element which has the |
| // same name with the source document |
| // 2. As to the style definition which match 1) condition, modified the |
| // referred style name of the element which reference this style |
| // 3. All the style which also contains other style reference, should be |
| // copied to the source document. |
| private void insertCollectedStyle(String styleQName, IdentityHashMap<OdfElement, List<OdfElement>> srcStyleCloneEleList, OdfFileDom dom, |
| IdentityHashMap<OdfElement, OdfElement> appendStyleList) { |
| try { |
| String stylePrefix = OdfNamespace.getPrefixPart(styleQName); |
| String styleLocalName = OdfNamespace.getLocalPart(styleQName); |
| String styleURI = OdfDocumentNamespace.STYLE.getUri(); |
| if (stylePrefix.equals("draw")) |
| styleURI = OdfDocumentNamespace.DRAW.getUri(); |
| // is the DOM always the styles.xml |
| XPath xpath = dom.getXPath(); |
| NodeList destStyleNodeList; |
| if (dom instanceof OdfContentDom) |
| destStyleNodeList = (NodeList) xpath.evaluate("*/office:automatic-styles/*[@" + styleQName + "]", dom, XPathConstants.NODESET); |
| else |
| destStyleNodeList = (NodeList) xpath.evaluate(".//*[@" + styleQName + "]", dom, XPathConstants.NODESET); |
| Iterator<OdfElement> iter = srcStyleCloneEleList.keySet().iterator(); |
| while (iter.hasNext()) { |
| OdfElement styleElement = iter.next(); |
| OdfElement cloneStyleElement = appendStyleList.get(styleElement); |
| if (cloneStyleElement == null) { |
| cloneStyleElement = (OdfElement) styleElement.cloneNode(true); |
| appendStyleList.put(styleElement, cloneStyleElement); |
| } |
| String styleName = styleElement.getAttributeNS(styleURI, styleLocalName); |
| List<String> newStyleNameList = styleRenameMap.get(styleName); |
| // if the newStyleNameList != null, means that styleName exists |
| // in dest document |
| // and it has already been renamed |
| if ((newStyleNameList != null) || (isStyleNameExist(destStyleNodeList, styleName) != null)) { |
| String newStyleName = null; |
| if (newStyleNameList == null) { |
| newStyleNameList = new ArrayList<String>(); |
| newStyleName = styleName + "-" + makeUniqueName(); |
| newStyleNameList.add(newStyleName); |
| styleRenameMap.put(styleName, newStyleNameList); |
| } else { |
| for (int i = 0; i < newStyleNameList.size(); i++) { |
| String styleNameIter = newStyleNameList.get(i); |
| OdfElement destStyleElementWithNewName = isStyleNameExist(destStyleNodeList, styleNameIter); |
| // check if the two style elements have the same |
| // content |
| // if not, the cloneStyleElement should rename, |
| // rather than reuse the new style name |
| cloneStyleElement.setAttributeNS(styleURI, styleQName, styleNameIter); |
| if ((destStyleElementWithNewName != null) && destStyleElementWithNewName.equals(cloneStyleElement)) { |
| newStyleName = styleNameIter; |
| break; |
| } |
| } |
| if (newStyleName == null) { |
| newStyleName = styleName + "-" + makeUniqueName(); |
| newStyleNameList.add(newStyleName); |
| } |
| } |
| // System.out.println("renaming:"+styleName+"-"+newStyleName); |
| // if newStyleName has been set in the element as the new |
| // name |
| // which means that the newStyleName is conform to the odf |
| // spec |
| // then change element style reference name |
| if (changeStyleRefName(srcStyleCloneEleList.get(styleElement), styleName, newStyleName)) { |
| cloneStyleElement.setAttributeNS(styleURI, styleQName, newStyleName); |
| // if display name should also be renamed |
| String displayName = cloneStyleElement.getAttributeNS(styleURI, "display-name"); |
| if ((displayName != null) && (displayName.length() > 0)) { |
| cloneStyleElement.setAttributeNS(styleURI, stylePrefix + ":display-name", |
| displayName + newStyleName.substring(newStyleName.length() - 8)); |
| } |
| } |
| } |
| } |
| |
| iter = appendStyleList.keySet().iterator(); |
| while (iter.hasNext()) { |
| OdfElement styleElement = iter.next(); |
| OdfElement cloneStyleElement = appendStyleList.get(styleElement); |
| String newStyleName = cloneStyleElement.getAttributeNS(styleURI, styleLocalName); |
| Boolean isAppended = styleAppendMap.get(newStyleName); |
| // if styleAppendMap contain the newStyleName, |
| // means that cloneStyleElement has already been appended |
| if ((isAppended != null) && isAppended.booleanValue() == true) { |
| continue; |
| } else { |
| styleAppendMap.put(newStyleName, true); |
| } |
| OdfElement cloneForeignStyleElement = (OdfElement) cloneForeignElement(cloneStyleElement, dom, true); |
| String styleElePath = getElementPath(styleElement); |
| appendForeignStyleElement(cloneForeignStyleElement, dom, styleElePath); |
| copyLinkedRef(cloneStyleElement); |
| } |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| } |
| |
| } |
| |
| // get all the copy of referred style element which is directly referred or |
| // indirectly referred by cloneEle |
| // styleQName is style:name |
| // all the style are defined in srcStyleNodeList |
| // and these style are all have the styleName defined in styleQName |
| // attribute |
| // the key of copyStyleEleList is the style definition element |
| // the value of the corresponding key is the clone of the element which |
| // refer to the key, |
| // the cloned element can be the content of slide or the style element. |
| // the key of appendStyleList is the style definition element which has the |
| // other style reference |
| // the value of the corresponding key is the the style definition clone |
| // element |
| // loop means if recursive call this function |
| // if loop == true, get the style definition element reference other style |
| // definition element |
| private void getCopyStyleList(OdfElement ele, OdfElement cloneEle, String styleQName, NodeList srcStyleNodeList, |
| IdentityHashMap<OdfElement, List<OdfElement>> copyStyleEleList, IdentityHashMap<OdfElement, OdfElement> appendStyleList, List<String> attrStrList, |
| boolean loop) { |
| try { |
| String styleLocalName = OdfNamespace.getLocalPart(styleQName); |
| String stylePrefix = OdfNamespace.getPrefixPart(styleQName); |
| String styleURI = OdfDocumentNamespace.STYLE.getUri(); |
| if (stylePrefix.equals("draw")) |
| styleURI = OdfDocumentNamespace.DRAW.getUri(); |
| // OdfElement override the "toString" method |
| String cloneEleStr = cloneEle.toString(); |
| for (int i = 0; i < srcStyleNodeList.getLength(); i++) { |
| OdfElement styleElement = (OdfElement) srcStyleNodeList.item(i); |
| String styleName = styleElement.getAttributeNS(styleURI, styleLocalName); |
| if (styleName != null) { |
| int index = 0; |
| index = cloneEleStr.indexOf("=\"" + styleName + "\"", index); |
| while (index >= 0) { |
| String subStr = cloneEleStr.substring(0, index); |
| int lastSpaceIndex = subStr.lastIndexOf(' '); |
| String attrStr = subStr.substring(lastSpaceIndex + 1, index); |
| if (attrStr.equals(styleQName) || attrStrList.contains(attrStr + "=" + "\"" + styleName + "\"")) { |
| index = cloneEleStr.indexOf("=\"" + styleName + "\"", index + styleName.length()); |
| continue; |
| } |
| attrStrList.add(attrStr + "=" + "\"" + styleName + "\""); |
| XPath xpath = ((OdfFileDom) cloneEle.getOwnerDocument()).getXPath(); |
| NodeList styleRefNodes = (NodeList) xpath.evaluate(".//*[@" + attrStr + "='" + styleName + "']", cloneEle, XPathConstants.NODESET); |
| boolean isExist = false; |
| for (int j = 0; j <= styleRefNodes.getLength(); j++) { |
| OdfElement styleRefElement = null; |
| if (j == styleRefNodes.getLength()) { |
| isExist = isStyleNameRefExist(cloneEle, styleName, false); |
| if (isExist) { |
| styleRefElement = cloneEle; |
| } else { |
| continue; |
| } |
| } else { |
| OdfElement tmpElement = (OdfElement) styleRefNodes.item(j); |
| if (isStyleNameRefExist(tmpElement, styleName, false)) { |
| styleRefElement = tmpElement; |
| } else { |
| continue; |
| } |
| } |
| boolean hasLoopStyleDef = true; |
| if (!(styleElement instanceof StyleFontFaceElement)) { |
| if (copyStyleEleList.get(styleElement) == null) { |
| List<OdfElement> styleRefEleList = new ArrayList<OdfElement>(); |
| copyStyleEleList.put(styleElement, styleRefEleList); |
| hasLoopStyleDef = false; |
| } |
| copyStyleEleList.get(styleElement).add(styleRefElement); |
| } |
| |
| OdfElement cloneStyleElement = appendStyleList.get(styleElement); |
| if (cloneStyleElement == null) { |
| cloneStyleElement = (OdfElement) styleElement.cloneNode(true); |
| appendStyleList.put(styleElement, cloneStyleElement); |
| } |
| if (loop && !hasLoopStyleDef) { |
| getCopyStyleList(styleElement, cloneStyleElement, styleQName, srcStyleNodeList, copyStyleEleList, appendStyleList, attrStrList, |
| loop); |
| } |
| } |
| index = cloneEleStr.indexOf("=\"" + styleName + "\"", index + styleName.length()); |
| } |
| } |
| } |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| } |
| } |
| |
| // append the cloneStyleElement to the contentDom which position is defined |
| // by styleElePath |
| private void appendForeignStyleElement(OdfElement cloneStyleEle, OdfFileDom dom, String styleElePath) { |
| StringTokenizer token = new StringTokenizer(styleElePath, "/"); |
| boolean isExist = true; |
| boolean found = false; |
| Node iterNode = dom.getFirstChild(); |
| Node parentNode = dom; |
| while (token.hasMoreTokens()) { |
| String onePath = token.nextToken(); |
| found = false; |
| |
| while ((iterNode != null) && isExist) { |
| String path = iterNode.getNamespaceURI(); |
| String prefix = iterNode.getPrefix(); |
| if (prefix == null) { |
| path += "@" + iterNode.getLocalName(); |
| } else { |
| path += "@" + prefix + ":" + iterNode.getLocalName(); |
| } |
| if (!path.equals(onePath)) { |
| // not found, then get the next sibling to find such path |
| // node |
| iterNode = iterNode.getNextSibling(); |
| } else { |
| // found, then get the child nodes to find the next path |
| // node |
| parentNode = iterNode; |
| found = true; |
| iterNode = iterNode.getFirstChild(); |
| break; |
| } |
| } |
| |
| if (!found) { |
| // should new the element since the current path node |
| if (isExist) { |
| isExist = false; |
| } |
| StringTokenizer token2 = new StringTokenizer(onePath, "@"); |
| OdfElement newElement = dom.createElementNS(OdfName.newName(token2.nextToken(), token2.nextToken())); |
| parentNode.appendChild(newElement); |
| parentNode = newElement; |
| } |
| } |
| parentNode.appendChild(cloneStyleEle); |
| } |
| |
| // The returned string is a path from the top of the dom tree to the |
| // specified element |
| // and the path is split by "/" between each node |
| private String getElementPath(OdfElement styleEle) { |
| String path = ""; |
| Node parentNode = styleEle.getParentNode(); |
| while (!(parentNode instanceof OdfFileDom)) { |
| String qname = null; |
| String prefix = parentNode.getPrefix(); |
| if (prefix == null) { |
| qname = parentNode.getLocalName(); |
| } else { |
| qname = prefix + ":" + parentNode.getLocalName(); |
| } |
| path = parentNode.getNamespaceURI() + "@" + qname + "/" + path; |
| parentNode = parentNode.getParentNode(); |
| } |
| return path; |
| } |
| |
| // change the element referred oldStyleName to the new name |
| // if true then set newStyleName attribute value successfully |
| // if false means that the newStyleName value is not conform to the ODF |
| // spec, so do not modify the oldStyleName |
| private boolean changeStyleRefName(List<OdfElement> list, String oldStyleName, String newStyleName) { |
| boolean rtn = false; |
| for (int index = 0; index < list.size(); index++) { |
| OdfElement element = list.get(index); |
| NamedNodeMap attributes = element.getAttributes(); |
| |
| if (attributes != null) { |
| for (int i = 0; i < attributes.getLength(); i++) { |
| Node item = attributes.item(i); |
| String value = item.getNodeValue(); |
| if (oldStyleName.equals(value)) { |
| try { |
| item.setNodeValue(newStyleName); |
| rtn = true; |
| break; |
| } catch (IllegalArgumentException e) { |
| return false; |
| } |
| } |
| } |
| } |
| } |
| return rtn; |
| } |
| |
| // check if the element contains the referred styleName |
| private boolean isStyleNameRefExist(Node element, String styleName, boolean deep) { |
| NamedNodeMap attributes = element.getAttributes(); |
| if (attributes != null) { |
| for (int i = 0; i < attributes.getLength(); i++) { |
| Node item = attributes.item(i); |
| if (item.getNodeValue().equals(styleName) && !item.getNodeName().equals("style:name")) { |
| // this is style definition, not reference. |
| return true; |
| } |
| } |
| } |
| if (deep) { |
| Node childNode = element.getFirstChild(); |
| while (childNode != null) { |
| if (!isStyleNameRefExist(childNode, styleName, true)) { |
| childNode = childNode.getNextSibling(); |
| } else { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| // check if nodeList contains the node that "style:name" attribute has the |
| // same value with styleName |
| // Note: nodeList here is all the style definition list |
| private OdfElement isStyleNameExist(NodeList nodeList, String styleName) { |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| OdfElement element = (OdfElement) nodeList.item(i); |
| String name = element.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name"); |
| if (name.equals(styleName)) { |
| // return true; |
| return element; |
| } |
| } |
| // return false; |
| return null; |
| } |
| |
| /** |
| * This method will delete all the linked resources that are only related |
| * with this element. |
| * |
| * @param odfEle |
| * - the element to be deleted. |
| * @return true if successfully delete, or else, false will be returned |
| */ |
| // delete all the xlink:href object which is contained in slideElement and |
| // does not referred by other slides |
| boolean deleteLinkedRef(OdfElement odfEle) { |
| boolean success = true; |
| try { |
| OdfFileDom contentDom = getContentDom(); |
| XPath xpath = contentDom.getXPath(); |
| NodeList linkNodes = (NodeList) xpath.evaluate("//*[@xlink:href]", contentDom, XPathConstants.NODESET); |
| for (int i = 0; i < linkNodes.getLength(); i++) { |
| OdfElement object = (OdfElement) linkNodes.item(i); |
| String refObjPath = object.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href"); |
| int relation = odfEle.compareDocumentPosition(object); |
| // if slide element contains the returned element which has the |
| // xlink:href reference |
| if ((relation & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0 && refObjPath != null && refObjPath.length() > 0) { |
| // the path of the object is start with "./" |
| NodeList pathNodes = (NodeList) xpath.evaluate("//*[@xlink:href='" + refObjPath + "']", getContentDom(), XPathConstants.NODESET); |
| int refCount = pathNodes.getLength(); |
| if (refCount == 1) { |
| // delete "./" |
| if (refObjPath.startsWith("./")) { |
| refObjPath = refObjPath.substring(2); |
| } |
| // check if the current document contains the same path |
| OdfFileEntry fileEntry = getPackage().getFileEntry(refObjPath); |
| if (fileEntry != null) { |
| // it is a stream, such as image, binary file |
| getPackage().remove(refObjPath); |
| } else { |
| // note: if refObjPath is a directory, it must end |
| // with '/' |
| fileEntry = getPackage().getFileEntry(refObjPath + "/"); |
| removeDocument(refObjPath); |
| } |
| } |
| } |
| } |
| } catch (XPathExpressionException e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| success = false; |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| success = false; |
| } |
| return success; |
| } |
| |
| /** |
| * This method will delete all the style definitions that are only related |
| * with this element. |
| * |
| * @param odfEle |
| * - the element to be deleted. |
| * @return true if successfully delete, or else, false will be returned |
| */ |
| boolean deleteStyleRef(OdfElement odfEle) { |
| boolean success = true; |
| try { |
| // method 1: |
| // 1.1. iterate child element of the content element |
| // 1.2. if the child element is an OdfStylableElement, get the |
| // style-name ref count |
| // ////////////// |
| // method 2: |
| // 2.1. get the list of the style definition |
| ArrayList<OdfElement> removeStyles = new ArrayList<OdfElement>(); |
| OdfOfficeAutomaticStyles autoStyles = getContentDom().getAutomaticStyles(); |
| |
| NodeList stylesList = autoStyles.getChildNodes(); |
| OdfFileDom contentDom = getContentDom(); |
| XPath xpath = contentDom.getXPath(); |
| |
| // 2.2. get the reference of each style which occurred in the |
| // current page |
| for (int i = 0; i < stylesList.getLength(); i++) { |
| Node item = stylesList.item(i); |
| if (item instanceof OdfElement) { |
| OdfElement node = (OdfElement) item; |
| String styleName = node.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name"); |
| if (styleName != null) { |
| // search the styleName contained at the current page |
| // element |
| NodeList styleNodes = (NodeList) xpath.evaluate("//*[@*='" + styleName + "']", contentDom, XPathConstants.NODESET); |
| int styleCnt = styleNodes.getLength(); |
| if (styleCnt > 1) { |
| // the first styleName is occurred in the style |
| // definition |
| // so check if the second styleName and last |
| // styleName is occurred in the current page element |
| // if yes, then remove it |
| OdfElement elementFirst = (OdfElement) styleNodes.item(1); |
| OdfElement elementLast = (OdfElement) styleNodes.item(styleCnt - 1); |
| boolean isSamePage = false; |
| if (elementFirst instanceof DrawPageElement) { |
| DrawPageElement tempPage = (DrawPageElement) elementFirst; |
| if (tempPage.equals(odfEle)) { |
| isSamePage = true; |
| } |
| } |
| int relationFirst = odfEle.compareDocumentPosition(elementFirst); |
| int relationLast = odfEle.compareDocumentPosition(elementLast); |
| // if slide element contains the child element which |
| // has the styleName reference |
| if (((relationFirst & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0 && (relationLast & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0) |
| || (isSamePage && (styleCnt == 1))) { |
| if (node instanceof OdfStyleBase) { |
| removeStyles.add(node); |
| } |
| } |
| } else { |
| continue; |
| } |
| } |
| } |
| } |
| for (int i = 0; i < removeStyles.size(); i++) { |
| autoStyles.removeChild(removeStyles.get(i)); |
| } |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| success = false; |
| } |
| return success; |
| } |
| |
| public Table addTable() { |
| return getTableContainerImpl().addTable(); |
| } |
| |
| public Table addTable(int numRows, int numCols) { |
| return getTableContainerImpl().addTable(numRows, numCols); |
| } |
| |
| public Table getTableByName(String name) { |
| return getTableContainerImpl().getTableByName(name); |
| } |
| |
| public java.util.List<Table> getTableList() { |
| return getTableContainerImpl().getTableList(); |
| } |
| |
| public TableBuilder getTableBuilder() { |
| return getTableContainerImpl().getTableBuilder(); |
| } |
| |
| protected TableContainer getTableContainerImpl() { |
| if (tableContainerImpl == null) { |
| tableContainerImpl = new TableContainerImpl(); |
| } |
| return tableContainerImpl; |
| } |
| |
| private class TableContainerImpl extends AbstractTableContainer { |
| |
| public OdfElement getTableContainerElement() { |
| OdfElement containerElement = null; |
| try { |
| containerElement = getContentRoot(); |
| } catch (Exception e) { |
| Logger.getLogger(Document.class.getName()).log(Level.SEVERE, null, e); |
| } |
| return containerElement; |
| } |
| } |
| |
| /** |
| * Return the component repository of this document. |
| * |
| * @return the component repository of this document. |
| */ |
| protected IdentityHashMap<OdfElement, Component> getComponentMap() { |
| return mComponentRepository; |
| } |
| |
| /** |
| * Construct a |
| * |
| * |
| * <code>TableTemplate<code> feature by extracting style template from an pre-defined table in a foreign document. The styles loaded by the template will be copied into the document as well and can be referenced by table directly. |
| * <p> |
| * The imported table need to be at least a 5*5 table (e.g. A1E5). Each type of style in the template will be set according to the style reference in a specific table cell, as following: |
| * <br>first column - A2 |
| * <br>last column - E2 |
| * <br>first row - A2 |
| * <br>last row - E2 |
| * <br>even rows - B3 |
| * <br>odd rows - B2 |
| * <br>even columns - C2 |
| * <br>odd columns - B2 |
| * <br>body - B2 |
| * <br>first-row-start-column -A1 |
| * <br>first-row-end-column -E1 |
| * <br>last-row-start-column -A5 |
| * <br>last-row-end-column -E5 |
| * |
| * @param templateFileInputStream |
| * - the InputStream of the ODF document. |
| * @param tableName |
| * - the table name which will be used to load styles as template |
| * @throws Exception |
| * - if content DOM could not be initialized |
| */ |
| public TableTemplate LoadTableTemplateFromForeignTable( |
| InputStream templateFileInputStream, String tableName) throws Exception { |
| |
| Document doc = Document.loadDocument(templateFileInputStream); |
| |
| if (doc == null) |
| throw new IllegalStateException( |
| "Cannot load specified template file."); |
| |
| Table table = doc.getTableByName(tableName); |
| if (table == null) |
| throw new IllegalStateException( |
| "Cannot load table template from specified file."); |
| |
| if (table.getRowCount() < 5 || table.getColumnCount() < 5) |
| throw new IllegalStateException( |
| "The template cannot be loaded. It should be at least a 5*5 table."); |
| |
| TableTemplate template = new TableTemplate(getStylesDom() |
| .newOdfElement(TableTableTemplateElement.class)); |
| |
| // first-row-start-column |
| Cell cell = table.getCellByPosition(0, 0); |
| cell.getParagraphIterator().hasNext(); |
| cell.getParagraphIterator().next().getStyleName(); |
| Paragraph para = cell.getParagraphByIndex(0, false); |
| String paraStyle = (para != null ? para.getStyleName() : null); |
| template.setExtendedStyleByType( |
| TableTemplate.ExtendedStyleType.FIRSTROWSTARTCOLUM, cell |
| .getStyleName(), paraStyle); |
| TableTableCellElementBase oldCellEle = cell.getOdfElement(); |
| TableTableCellElementBase newCellEle = (TableTableCellElementBase) oldCellEle |
| .cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // first-row-end-column |
| cell = table.getCellByPosition(4, 0); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setExtendedStyleByType( |
| TableTemplate.ExtendedStyleType.FIRSTROWENDCOLUMN, cell |
| .getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // last-row-start-column |
| cell = table.getCellByPosition(0, 4); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setExtendedStyleByType( |
| TableTemplate.ExtendedStyleType.LASTROWSTARTCOLUMN, cell |
| .getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // last-row-end-column |
| cell = table.getCellByPosition(4, 4); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setExtendedStyleByType( |
| TableTemplate.ExtendedStyleType.LASTROWENDCOLUMN, cell |
| .getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // first column |
| cell = table.getCellByPosition(0, 1); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setTableFirstColumnStyle(cell.getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // last column |
| cell = table.getCellByPosition(4, 2); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setTableLastColumnStyle(cell.getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // first row |
| cell = table.getCellByPosition(1, 0); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setTableFirstRowStyle(cell.getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // last row |
| cell = table.getCellByPosition(1, 4); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setTableLastRowStyle(cell.getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // body (=odd row/column) |
| cell = table.getCellByPosition(1, 1); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setTableBodyStyle(cell.getStyleName(), paraStyle); |
| template.setTableOddRowsStyle(cell.getStyleName(), paraStyle); |
| template.setTableOddColumnsStyle(cell.getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // even row |
| cell = table.getCellByPosition(1, 2); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setTableEvenRowsStyle(cell.getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| // even row |
| cell = table.getCellByPosition(2, 1); |
| para = cell.getParagraphByIndex(0, false); |
| paraStyle = (para != null ? para.getStyleName() : null); |
| template.setTableEvenColumnsStyle(cell.getStyleName(), paraStyle); |
| oldCellEle = cell.getOdfElement(); |
| newCellEle = (TableTableCellElementBase) oldCellEle.cloneNode(true); |
| copyForeignStyleRef(newCellEle, cell.getOwnerDocument()); |
| |
| return template; |
| } |
| |
| } |