Package org.odftoolkit.odfdom.doc

Source Code of org.odftoolkit.odfdom.doc.OdfDocument

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

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;

import org.odftoolkit.odfdom.doc.table.OdfTable;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.OdfMetaDom;
import org.odftoolkit.odfdom.dom.OdfSchemaConstraint;
import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
import org.odftoolkit.odfdom.dom.attribute.text.TextAnchorTypeAttribute;
import org.odftoolkit.odfdom.dom.element.draw.DrawPageElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeBodyElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableElement;
import org.odftoolkit.odfdom.dom.element.text.TextPElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.dom.style.props.OdfTextProperties;
import org.odftoolkit.odfdom.incubator.doc.draw.OdfDrawFrame;
import org.odftoolkit.odfdom.incubator.doc.draw.OdfDrawImage;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfDefaultStyle;
import org.odftoolkit.odfdom.incubator.meta.OdfOfficeMeta;
import org.odftoolkit.odfdom.pkg.MediaType;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.pkg.OdfPackage;
import org.odftoolkit.odfdom.pkg.OdfValidationException;
import org.odftoolkit.odfdom.type.Duration;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;

/** This abstract class is representing one of the possible ODF documents */
public abstract class OdfDocument extends OdfSchemaDocument {
  // Static parts of file references

  private static final String SLASH = "/";
  private OdfMediaType mMediaType;
  private OdfOfficeMeta mOfficeMeta;
  private long documentOpeningTime;
  private static final Pattern CONTROL_CHAR_PATTERN = Pattern.compile("\\p{Cntrl}");
  private static final String EMPTY_STRING = "";
  private Calendar mCreationDate;

  // Using static factory instead of constructor
  protected OdfDocument(OdfPackage pkg, String internalPath, OdfMediaType mediaType) throws SAXException {
    super(pkg, internalPath, mediaType.getMediaTypeString());
    mMediaType = mediaType;
    //set document opening time.
    documentOpeningTime = System.currentTimeMillis();
  }

  /**
   * This enum contains all possible media types of OpenDocument documents.
   */
  public enum OdfMediaType implements MediaType {

    CHART("application/vnd.oasis.opendocument.chart", "odc"),
    CHART_TEMPLATE("application/vnd.oasis.opendocument.chart-template", "otc"),
    FORMULA("application/vnd.oasis.opendocument.formula", "odf"),
    FORMULA_TEMPLATE("application/vnd.oasis.opendocument.formula-template", "otf"),
    DATABASE_FRONT_END("application/vnd.oasis.opendocument.base", "odb"),
    GRAPHICS("application/vnd.oasis.opendocument.graphics", "odg"),
    GRAPHICS_TEMPLATE("application/vnd.oasis.opendocument.graphics-template", "otg"),
    IMAGE("application/vnd.oasis.opendocument.image", "odi"),
    IMAGE_TEMPLATE("application/vnd.oasis.opendocument.image-template", "oti"),
    PRESENTATION("application/vnd.oasis.opendocument.presentation", "odp"),
    PRESENTATION_TEMPLATE("application/vnd.oasis.opendocument.presentation-template", "otp"),
    SPREADSHEET("application/vnd.oasis.opendocument.spreadsheet", "ods"),
    SPREADSHEET_TEMPLATE("application/vnd.oasis.opendocument.spreadsheet-template", "ots"),
    TEXT("application/vnd.oasis.opendocument.text", "odt"),
    TEXT_MASTER("application/vnd.oasis.opendocument.text-master", "odm"),
    TEXT_TEMPLATE("application/vnd.oasis.opendocument.text-template", "ott"),
    TEXT_WEB("application/vnd.oasis.opendocument.text-web", "oth");
    private final String mMediaType;
    private final String mSuffix;

    OdfMediaType(String mediaType, String suffix) {
      this.mMediaType = mediaType;
      this.mSuffix = suffix;
    }

    /**
     * @return the mediatype String of this document
     */
    public String getMediaTypeString() {
      return mMediaType;
    }

    /**
     * @return the ODF filesuffix of this document
     */
    public String getSuffix() {
      return mSuffix;
    }

    /**
     *
     * @param mediaType string defining an ODF document
     * @return the according OdfMediatype encapuslating the given string and the suffix
     */
    public static OdfMediaType getOdfMediaType(String mediaType) {
      OdfMediaType odfMediaType = null;
      if (mediaType != null) {

        String mediaTypeShort = mediaType.substring(mediaType.lastIndexOf(".") + 1, mediaType.length());
        mediaTypeShort = mediaTypeShort.replace('-', '_').toUpperCase();
        try {
          odfMediaType = OdfMediaType.valueOf(mediaTypeShort);

        } catch (IllegalArgumentException e) {
          throw new IllegalArgumentException("Given mediaType '" + mediaType + "' is not an ODF mediatype!");
        }
      }
      return odfMediaType;
    }
  }

  /**
   * Loads the ODF root document from the given Resource.
   *
   * NOTE: Initial meta data (like the document creation time) will be added in this method.
   *
   * @param res a resource containing a package with a root document
   * @param odfMediaType the media type of the root document
   * @return the OpenDocument document
   *      or NULL if the media type is not supported by ODFDOM.
   * @throws java.lang.Exception - if the document could not be created.
   */
  protected static OdfDocument loadTemplate(Resource res, OdfMediaType odfMediaType) throws Exception {
    InputStream in = res.createInputStream();
    OdfPackage pkg = null;
    try {
      pkg = OdfPackage.loadPackage(in);
    } finally {
      in.close();
    }
    OdfDocument newDocument = newDocument(pkg, ROOT_DOCUMENT_PATH, odfMediaType);
    //add creation time, the metadata have to be explicitly set
    newDocument.mCreationDate = Calendar.getInstance();
    return newDocument;
  }

  /**
   * Loads the ODF root document from the ODF package provided by its path.
   *
   * <p>OdfDocument relies on the file being available for read access over
   * the whole lifecycle of OdfDocument.</p>
   *
   * @param documentPath - the path from where the document can be loaded
   * @return the OpenDocument from the given path
   *      or NULL if the media type is not supported by ODFDOM.
   * @throws java.lang.Exception - if the document could not be created.
   */
  public static OdfDocument loadDocument(String documentPath) throws Exception {
    return loadDocument(OdfPackage.loadPackage(documentPath));
  }

  /**
   * Loads the ODF root document from the ODF package provided by a Stream.
   *
   * <p>Since an InputStream does not provide the arbitrary (non sequentiell)
   * read access needed by OdfDocument, the InputStream is cached. This usually
   * takes more time compared to the other createInternalDocument methods.
   * An advantage of caching is that there are no problems overwriting
   * an input file.</p>
   *
   * @param inStream - the InputStream of the ODF document.
   * @return the document created from the given InputStream
   * @throws java.lang.Exception - if the document could not be created.
   */
  public static OdfDocument loadDocument(InputStream inStream) throws Exception {
    return loadDocument(OdfPackage.loadPackage(inStream));
  }

  /**
   * Loads the ODF root document from the ODF package provided as a File.
   *
   * @param file - a file representing the ODF document.
   * @return the document created from the given File
   * @throws java.lang.Exception - if the document could not be created.
   */
  public static OdfDocument loadDocument(File file) throws Exception {
    return loadDocument(OdfPackage.loadPackage(file));
  }

  /**
   * Loads the ODF root document from the ODF package.
   *
   * @param odfPackage - the ODF package containing the ODF document.
   * @return the root document of the given OdfPackage
   * @throws java.lang.Exception - if the ODF document could not be created.
   */
  public static OdfDocument loadDocument(OdfPackage odfPackage) throws Exception {
    return loadDocument(odfPackage, ROOT_DOCUMENT_PATH);
  }

  /**
   * Creates an OdfDocument from the OpenDocument provided by an ODF package.
   * @param odfPackage - the ODF package containing the ODF document.
   * @param internalPath - the path to the ODF document relative to the package root, or an empty String for the root document.
   * @return the root document of the given OdfPackage
   * @throws java.lang.Exception - if the ODF document could not be created.
   */
  public static OdfDocument loadDocument(OdfPackage odfPackage, String internalPath) throws Exception {
    String documentMediaType = odfPackage.getMediaTypeString(internalPath);
    OdfMediaType odfMediaType = null;
    try {
      odfMediaType = OdfMediaType.getOdfMediaType(documentMediaType);
    } catch (IllegalArgumentException e) {
      // the returned NULL will be taking care of afterwards
    }
    if (odfMediaType == null) {
      ErrorHandler errorHandler = odfPackage.getErrorHandler();
      Matcher matcherCTRL = CONTROL_CHAR_PATTERN.matcher(documentMediaType);
      if (matcherCTRL.find()) {
        documentMediaType = matcherCTRL.replaceAll(EMPTY_STRING);
      }
      OdfValidationException ve = new OdfValidationException(OdfSchemaConstraint.DOCUMENT_WITHOUT_ODF_MIMETYPE, internalPath, documentMediaType);
      if (errorHandler != null) {
        errorHandler.fatalError(ve);
      }
      throw ve;
    }
    return newDocument(odfPackage, internalPath, odfMediaType);
  }

  //return null if the media type can not be recognized.
  private static OdfDocument loadDocumentFromTemplate(OdfMediaType odfMediaType) throws Exception {

    final Resource documentTemplate;
    switch (odfMediaType) {
      case TEXT:
      case TEXT_TEMPLATE:
      case TEXT_MASTER:
      case TEXT_WEB:
        documentTemplate = OdfTextDocument.EMPTY_TEXT_DOCUMENT_RESOURCE;
        break;

      case SPREADSHEET:
      case SPREADSHEET_TEMPLATE:
        documentTemplate = OdfSpreadsheetDocument.EMPTY_SPREADSHEET_DOCUMENT_RESOURCE;
        break;

      case PRESENTATION:
      case PRESENTATION_TEMPLATE:
        documentTemplate = OdfPresentationDocument.EMPTY_PRESENTATION_DOCUMENT_RESOURCE;
        break;

      case GRAPHICS:
      case GRAPHICS_TEMPLATE:
        documentTemplate = OdfGraphicsDocument.EMPTY_GRAPHICS_DOCUMENT_RESOURCE;
        break;

      case CHART:
      case CHART_TEMPLATE:
        documentTemplate = OdfChartDocument.EMPTY_CHART_DOCUMENT_RESOURCE;
        break;

      case IMAGE:
      case IMAGE_TEMPLATE:
        documentTemplate = OdfImageDocument.EMPTY_IMAGE_DOCUMENT_RESOURCE;
        break;

      default:
        documentTemplate = null;
        throw new IllegalArgumentException("Given mediaType '" + odfMediaType.mMediaType + "' is not yet supported!");
    }
    return loadTemplate(documentTemplate, odfMediaType);
  }

  /**
   * Creates one of the ODF documents based a given mediatype.
   *
   * @param odfMediaType The ODF Mediatype of the ODF document to be created.
   * @return The ODF document, which mediatype dependends on the parameter or
   *  NULL if media type were not supported.
   */
  private static OdfDocument newDocument(OdfPackage pkg, String internalPath, OdfMediaType odfMediaType) throws SAXException {
    OdfDocument newDoc = null;
    switch (odfMediaType) {
      case TEXT:
        newDoc = new OdfTextDocument(pkg, internalPath, OdfTextDocument.OdfMediaType.TEXT);
        break;

      case TEXT_TEMPLATE:
        newDoc = new OdfTextDocument(pkg, internalPath, OdfTextDocument.OdfMediaType.TEXT_TEMPLATE);
        break;

      case TEXT_MASTER:
        newDoc = new OdfTextDocument(pkg, internalPath, OdfTextDocument.OdfMediaType.TEXT_MASTER);
        break;

      case TEXT_WEB:
        newDoc = new OdfTextDocument(pkg, internalPath, OdfTextDocument.OdfMediaType.TEXT_WEB);
        break;

      case SPREADSHEET:
        newDoc = new OdfSpreadsheetDocument(pkg, internalPath, OdfSpreadsheetDocument.OdfMediaType.SPREADSHEET);
        break;

      case SPREADSHEET_TEMPLATE:
        newDoc = new OdfSpreadsheetDocument(pkg, internalPath, OdfSpreadsheetDocument.OdfMediaType.SPREADSHEET_TEMPLATE);
        break;

      case PRESENTATION:
        newDoc = new OdfPresentationDocument(pkg, internalPath, OdfPresentationDocument.OdfMediaType.PRESENTATION);
        break;

      case PRESENTATION_TEMPLATE:
        newDoc = new OdfPresentationDocument(pkg, internalPath, OdfPresentationDocument.OdfMediaType.PRESENTATION_TEMPLATE);
        break;

      case GRAPHICS:
        newDoc = new OdfGraphicsDocument(pkg, internalPath, OdfGraphicsDocument.OdfMediaType.GRAPHICS);
        break;

      case GRAPHICS_TEMPLATE:
        newDoc = new OdfGraphicsDocument(pkg, internalPath, OdfGraphicsDocument.OdfMediaType.GRAPHICS_TEMPLATE);
        break;

      case CHART:
        newDoc = new OdfChartDocument(pkg, internalPath, OdfChartDocument.OdfMediaType.CHART);
        break;

      case CHART_TEMPLATE:
        newDoc = new OdfChartDocument(pkg, internalPath, OdfChartDocument.OdfMediaType.CHART_TEMPLATE);
        break;

      case IMAGE:
        newDoc = new OdfImageDocument(pkg, internalPath, OdfImageDocument.OdfMediaType.IMAGE);
        break;

      case IMAGE_TEMPLATE:
        newDoc = new OdfImageDocument(pkg, internalPath, OdfImageDocument.OdfMediaType.IMAGE_TEMPLATE);
        break;

      default:
        newDoc = null;
        throw new IllegalArgumentException("Given mediaType '" + odfMediaType.mMediaType + "' is not yet supported!");
    }
    // returning null if MediaType is not supported
    return newDoc;
  }

  /**
   * Returns an embedded OdfPackageDocument from the given package path.
   *
   * @param documentPath to the ODF document within the package. The path is relative the current ODF document path.
   * @return an embedded OdfPackageDocument
   */
  @Override
  public OdfDocument loadSubDocument(String documentPath) {
    return (OdfDocument) super.loadSubDocument(documentPath);
  }

  /**
   * Method returns all embedded OdfPackageDocuments, which match a valid OdfMediaType,
   * of the current OdfPackageDocument. Note: The root document is not part of the returned collection.
   * @return a map with all embedded documents and their paths of the current OdfPackageDocument
   */
  public Map<String, OdfDocument> loadSubDocuments() {
    return loadSubDocuments(null);
  }

  /**
   * Method returns all embedded OdfPackageDocuments of sthe current OdfPackageDocument matching the
   * according MediaType. This is done by matching the subfolder entries of the
   * manifest file with the given OdfMediaType.
   * @param desiredMediaType media type of the documents to be returned (used as a filter).
   * @return embedded documents of the current OdfPackageDocument matching the given media type
   */
  public Map<String, OdfDocument> loadSubDocuments(OdfMediaType desiredMediaType) {
    String wantedMediaString = null;
    if (desiredMediaType != null) {
      wantedMediaString = desiredMediaType.getMediaTypeString();
    }
    Map<String, OdfDocument> embeddedObjectsMap = new HashMap<String, OdfDocument>();
    // check manifest for current embedded OdfPackageDocuments
    Set<String> manifestEntries = mPackage.getFilePaths();
    for (String path : manifestEntries) {
      // any directory that is not the root document "/"
      if (path.length() > 1 && path.endsWith(SLASH)) {
        String entryMediaType = mPackage.getFileEntry(path).getMediaTypeString();
        // if the entry is a document (directory has mediaType)
        if (entryMediaType != null) {
          // if a specific ODF mediatype was requested
          if (wantedMediaString != null) {
            // test if the desired mediatype matches the current
            if (entryMediaType.equals(wantedMediaString)) {
              path = normalizeDocumentPath(path);
              embeddedObjectsMap.put(path, (OdfDocument) mPackage.loadDocument(path));
            }
          } else {
            // test if any ODF mediatype matches the current
            for (OdfMediaType mediaType : OdfMediaType.values()) {
              if (entryMediaType.equals(mediaType.getMediaTypeString())) {
                embeddedObjectsMap.put(path, (OdfDocument) mPackage.loadDocument(path));
              }
            }
          }
        }
      }
    }
    return embeddedObjectsMap;
  }

  /**
   * Sets the media type of the OdfDocument
   * @param odfMediaType media type to be set
   */
  protected void setOdfMediaType(OdfMediaType odfMediaType) {
    mMediaType = odfMediaType;
    super.setMediaTypeString(odfMediaType.getMediaTypeString());
  }

  /**
   * Gets the media type of the OdfDocument
   */
  protected OdfMediaType getOdfMediaType() {
    return mMediaType;
  }

  /**
   * Get the meta data feature instance of the current document
   *
   * @return the meta data feature instance which represent
   * <code>office:meta</code> in the meta.xml  
   */
  public OdfOfficeMeta getOfficeMetadata() {
    if (mOfficeMeta == null) {
      try {
        OdfMetaDom metaDom = getMetaDom();
        if(metaDom == null){
         
          metaDom = new OdfMetaDom(this, OdfSchemaDocument.OdfXMLFile.META.getFileName());
        }
        mOfficeMeta = new OdfOfficeMeta(metaDom);
      } catch (Exception ex) {
        Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    return mOfficeMeta;
  }

  /**
   * Save the document to an OutputStream. Delegate to the root document
   * and save possible embedded OdfDocuments.
   *
   * <p>If the input file has been cached (this is the case when loading from an
   * InputStream), the input file can be overwritten.</p>
   *
   * <p>If not, the OutputStream may not point to the input file! Otherwise
   * this will result in unwanted behaviour and broken files.</p>
   *
   * <p>When save the embedded document to a stand alone document,
   * all the file entries of the embedded document will be copied to a new document package.
   * If the embedded document is outside of the current document directory,
   * you have to embed it to the sub directory and refresh the link of the embedded document.
   * you should reload it from the stream to get the saved embedded document.
   *
   * @param out - the OutputStream to write the file to
   * @throws java.lang.Exception  if the document could not be saved
   */
  public void save(OutputStream out) throws Exception {
    updateMetaData();
    if (!isRootDocument()) {
      OdfDocument newDoc = loadDocumentFromTemplate(getOdfMediaType());
      newDoc.insertDocument(this, ROOT_DOCUMENT_PATH);
      newDoc.updateMetaData();
      newDoc.mPackage.save(out);
      // ToDo: (Issue 219 - PackageRefactoring) - Return the document, when not closing!
      // Should we close the sources now? User will never receive the open package!
    } else {
      mPackage.save(out);
    }
  }

  /**
   * Save the document to a given file.
   *
   * <p>If the input file has been cached (this is the case when loading from an
   * InputStream), the input file can be overwritten.</p>
   *
   * <p>Otherwise it's allowed to overwrite the input file as long as
   * the same path name is used that was used for loading (no symbolic link
   * foo2.odt pointing to the loaded file foo1.odt, no network path X:\foo.odt
   * pointing to the loaded file D:\foo.odt).</p>
   *
   * <p>When saving the embedded document to a stand alone document,
   * all files of the embedded document will be copied to a new document package.
   * If the embedded document is outside of the current document directory,
   * you have to embed it to the sub directory and refresh the link of the embedded document.
   * You should reload it from the given file to get the saved embedded document.
   *
   * @param file - the file to save the document
   * @throws java.lang.Exception  if the document could not be saved
   */
  @Override
  public void save(File file) throws Exception {
    updateMetaData();
    if (!isRootDocument()) {
      OdfDocument newDoc = loadDocumentFromTemplate(getOdfMediaType());
      newDoc.insertDocument(this, ROOT_DOCUMENT_PATH);
      newDoc.updateMetaData();
      newDoc.mPackage.save(file);
      // ToDo: (Issue 219 - PackageRefactoring) - Return the document, when not closing!
      // Should we close the sources now? User will never receive the open package!
    } else {
      this.mPackage.save(file);
    }
  }

  /**
   * Close the OdfPackage and release all temporary created data.
   * Acter execution of this method, this class is no longer usable.
   * Do this as the last action to free resources.
   * Closing an already closed document has no effect.
   */
  @Override
  public void close() {
    // set all member variables explicit to null
    mMediaType = null;
    mOfficeMeta = null;
    super.close();
  }

  /**
   * Get the content root of a document.
   *
   * You may prefer to use the getContentRoot methods of subclasses of
   * OdfDocument. Their return parameters are already casted to
   * respective subclasses of OdfElement.
   *
   * @param the type of the content root, depend on the document type
   * @return the child element of office:body, e.g. office:text for text docs
   * @throws Exception if the file DOM could not be created.
   */
  @SuppressWarnings("unchecked")
  <T extends OdfElement> T getContentRoot(Class<T> clazz) throws Exception {
    OdfElement contentRoot = getContentDom().getRootElement();
    OfficeBodyElement contentBody = OdfElement.findFirstChildNode(
        OfficeBodyElement.class, contentRoot);
    NodeList childs = contentBody.getChildNodes();
    for (int i = 0;
        i < childs.getLength();
        i++) {
      Node cur = childs.item(i);
      if ((cur != null) && clazz.isInstance(cur)) {
        return (T) cur;
      }
    }
    return null;
  }

  /**
   * Get the content root of a document.
   *
   * You may prefer to use the getContentRoot methods of subclasses of
   * OdfDocument.
   *
   * @return the child element of office:body, e.g. office:text for text docs
   * @throws Exception if the file DOM could not be created.
   */
  public OdfElement getContentRoot() throws Exception {
    return getContentRoot(OdfElement.class);
  }

  @Override
  public String toString() {
    return "\n" + getMediaTypeString() + " - ID: " + this.hashCode() + " "
        + getPackage().getBaseURI();
  }

  /**
   * Insert an Image from the specified uri to the end of the OdfDocument.
   * @param imageUri The URI of the image that will be added to the document,
   *            add image stream to the package,
   *                 in the 'Pictures/' graphic directory with the same image file name as in the URI.
   *                 If the imageURI is relative first the user.dir is taken to make it absolute.
   * @return         Returns the internal package path of the image, which was created based on the given URI.
   * */
  public String newImage(URI imageUri) {
    try {
      OdfContentDom contentDom = this.getContentDom();
      OdfDrawFrame drawFrame = contentDom.newOdfElement(OdfDrawFrame.class);
      XPath xpath = contentDom.getXPath();
      if (this instanceof OdfSpreadsheetDocument) {
        TableTableCellElement lastCell = (TableTableCellElement) xpath.evaluate("//table:table-cell[last()]", contentDom, XPathConstants.NODE);
        lastCell.appendChild(drawFrame);
        drawFrame.removeAttribute("text:anchor-type");

      } else if (this instanceof OdfTextDocument) {
        TextPElement lastPara = (TextPElement) xpath.evaluate("//text:p[last()]", contentDom, XPathConstants.NODE);
        if (lastPara == null) {
          lastPara = ((OdfTextDocument) this).newParagraph();
        }
        lastPara.appendChild(drawFrame);
        drawFrame.setTextAnchorTypeAttribute(TextAnchorTypeAttribute.Value.PARAGRAPH.toString());
      } else if (this instanceof OdfPresentationDocument) {
        DrawPageElement lastPage = (DrawPageElement) xpath.evaluate("//draw:page[last()]", contentDom, XPathConstants.NODE);
        lastPage.appendChild(drawFrame);
      }
      OdfDrawImage image = (OdfDrawImage) drawFrame.newDrawImageElement();
      String imagePath = image.newImage(imageUri);
      return imagePath;
    } catch (Exception ex) {
      Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
  }

  /**
   * Return an instance of table feature with the specific table name.
   * @param name of the table beeing searched for.
   * @return an instance of table feature with the specific table name.
   */
  public OdfTable getTableByName(String name) {
    try {
      OdfElement root = getContentDom().getRootElement();
      OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root);
      OdfElement typedContent = OdfElement.findFirstChildNode(OdfElement.class, officeBody);

      NodeList childList = typedContent.getChildNodes();
      for (int i = 0; i < childList.getLength(); i++) {
        if (childList.item(i) instanceof TableTableElement) {
          TableTableElement table = (TableTableElement) childList.item(i);
          if (table.getOdfAttributeValue(OdfName.newName(OdfDocumentNamespace.TABLE, "name")).equals(name)) {
            return OdfTable.getInstance(table);
          }
        }
      }
    } catch (Exception e) {
      Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE, null, e);
    }
    return null;
  }

  /**
   * Return a list of table features in this document.
   * @return a list of table features in this document.
   */
  public List<OdfTable> getTableList() {
    List<OdfTable> tableList = new ArrayList<OdfTable>();
    try {
      OdfElement root = getContentDom().getRootElement();
      OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, root);
      OdfElement typedContent = OdfElement.findFirstChildNode(
          OdfElement.class, officeBody);
      NodeList childList = typedContent.getChildNodes();
      for (int i = 0;
          i < childList.getLength();
          i++) {
        if (childList.item(i) instanceof TableTableElement) {
          tableList.add(OdfTable.getInstance((TableTableElement) childList.item(i)));
        }
      }
    } catch (Exception e) {
      Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE, null, e);
    }
    return tableList;

  }

  /**
   * Update document meta data in the ODF document. Following metadata data is
   * being updated:
   * <ul>
   * <li>The name of the person who last modified this document will be the Java user.name System property</li>
   * <li>The date and time when the document was last modified using current data</li>
   * <li>The number of times this document has been edited is incremented by 1</li>
   * <li>The total time spent editing this document</li>
   * </ul>
   *
   * TODO:This method will be moved to OdfMetadata class.
   *      see http://odftoolkit.org/bugzilla/show_bug.cgi?id=204
   * @throws Exception
   * @throws IllegalArgumentException
   */
  private void updateMetaData() throws IllegalArgumentException, Exception {
    if (getOfficeMetadata().hasAutomaticUpdate()) {
      OdfOfficeMeta metaData = getOfficeMetadata();

      // set creation dateĀ“
      if (mCreationDate != null) {
        getOfficeMetadata().setCreationDate(mCreationDate);
      }

      // update late modfied date
      Calendar calendar = Calendar.getInstance();
      metaData.setDate(calendar);

      // update editing-cycles
      Integer cycle = metaData.getEditingCycles();
      if (cycle != null) {
        metaData.setEditingCycles(++cycle);
      } else {
        metaData.setEditingCycles(1);
      }
      // update editing-duration
      long editingDuration = calendar.getTimeInMillis() - documentOpeningTime;
      editingDuration = (editingDuration < 1) ? 1 : editingDuration;
      try {
        DatatypeFactory aFactory = DatatypeFactory.newInstance();
        metaData.setEditingDuration(new Duration(aFactory.newDurationDayTime(editingDuration)));
      } catch (DatatypeConfigurationException e) {
        Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE,
            "editing duration update fail as DatatypeFactory can not be instanced", e);
      }
    }
  }

  // /////////////////
  // Following is the implementation of locale settings
  // ////////////////
  /**
   * <p>
   * Unicode characters are in general divided by office applications into
   * three different groups.
   *
   * <p>
   * 1) There is CJK: the Chinese, Japanese and Korean script (also old
   * Vietnamese belong to this group). See
   * http://en.wikipedia.org/wiki/CJK_characters
   *
   * <p>
   * 2) There is CTL: Complex Text Layout, which uses BIDI algorithms and/or
   * glyph modules for instance Arabic, Hebrew, Indic and Thai. See http://en.wikipedia.org/wiki/Complex_Text_Layout
   *
   * <p>
   * 3) And there is all the rest, which was once called by MS Western.
   */
  public enum UnicodeGroup {

    /**
     * Western language
     */
    WESTERN,
    /**
     * Chinese, Japanese and Korean
     */
    CJK,
    /**
     * Complex Text Layout language
     */
    CTL;
  }
  private final static HashSet<String> CJKLanguage = new HashSet<String>();
  private final static HashSet<String> CTLLanguage = new HashSet<String>();

  {
    CJKLanguage.add("zh"); // LANGUAGE_CHINES
    CJKLanguage.add("ja"); // LANGUAGE_JAPANESE
    CJKLanguage.add("ko"); // LANGUAGE_KOREANE

    CTLLanguage.add("am"); // LANGUAGE_AMHARIC_ETHIOPIA
    CTLLanguage.add("ar"); // LANGUAGE_ARABIC_SAUDI_ARABIA
    CTLLanguage.add("as"); // LANGUAGE_ASSAMESE
    CTLLanguage.add("bn"); // LANGUAGE_BENGALI
    CTLLanguage.add("bo"); // LANGUAGE_TIBETAN
    CTLLanguage.add("brx");// LANGUAGE_USER_BODO_INDIA
    CTLLanguage.add("dgo");// LANGUAGE_USER_DOGRI_INDIA
    CTLLanguage.add("dv"); // LANGUAGE_DHIVEHI
    CTLLanguage.add("dz"); // LANGUAGE_DZONGKHA
    CTLLanguage.add("fa"); // LANGUAGE_FARSI
    CTLLanguage.add("gu"); // LANGUAGE_GUJARATI
    CTLLanguage.add("he"); // LANGUAGE_HEBREW
    CTLLanguage.add("hi"); // LANGUAGE_HINDI
    CTLLanguage.add("km"); // LANGUAGE_KHMER
    CTLLanguage.add("kn"); // LANGUAGE_KANNADA
    CTLLanguage.add("ks"); // LANGUAGE_KASHMIRI
    CTLLanguage.add("ku"); // LANGUAGE_USER_KURDISH_IRAQ
    CTLLanguage.add("lo"); // LANGUAGE_LAO
    CTLLanguage.add("mai");// LANGUAGE_USER_MAITHILI_INDIA
    CTLLanguage.add("ml"); // LANGUAGE_MALAYALAM
    CTLLanguage.add("mn"); // LANGUAGE_MONGOLIAN_MONGOLIAN
    CTLLanguage.add("mni");// LANGUAGE_MANIPURI
    CTLLanguage.add("mr"); // LANGUAGE_MARATHI
    CTLLanguage.add("my"); // LANGUAGE_BURMESE
    CTLLanguage.add("ne"); // LANGUAGE_NEPALI
    CTLLanguage.add("or"); // LANGUAGE_ORIYA
    CTLLanguage.add("pa"); // LANGUAGE_PUNJABI
    CTLLanguage.add("sa"); // LANGUAGE_SANSKRIT
    CTLLanguage.add("sd"); // LANGUAGE_SINDHI
    CTLLanguage.add("si"); // LANGUAGE_SINHALESE_SRI_LANKA
    CTLLanguage.add("syr");// LANGUAGE_SYRIAC
    CTLLanguage.add("ta"); // LANGUAGE_TAMIL
    CTLLanguage.add("te"); // LANGUAGE_TELUGU
    CTLLanguage.add("th"); // LANGUAGE_THAI
    CTLLanguage.add("ug"); // LANGUAGE_UIGHUR_CHINA
    CTLLanguage.add("ur"); // LANGUAGE_URDU
    CTLLanguage.add("yi"); // LANGUAGE_YIDDISH
  }

  /**
   * <p>
   * Set a locale information.
   * <p>
   * The locale information will affect the language and country setting of
   * the document. Thus the font settings, the spell checkings and etc will be
   * affected.
   *
   * @param locale
   *            - an instance of Locale
   */
  public void setLocale(Locale locale) {
    setLocale(locale, getUnicodeGroup(locale));
  }

  /**
   * This method will return Locale, which presents the default
   * language and country information settings in this document.
   *
   * @return an instance of Locale that the default language and country is
   *         set to.
   */
  /**
   * Similar to OpenOffice.org, ODFDOM assumes that every Locale is related
   * to one of the three Unicodes Groups, either CJK, CTL or Western.
   * @param locale the UnicodeGroup is requested for
   * @return the related UnicodeGroup
   */
  public static UnicodeGroup getUnicodeGroup(Locale locale) {
    String language = locale.getLanguage();
    if (CJKLanguage.contains(language)) {
      return UnicodeGroup.CJK;
    }
    if (CTLLanguage.contains(language)) {
      return UnicodeGroup.CTL;
    }
    return UnicodeGroup.WESTERN;

  }

  /**
   * <p>
   * Set a locale of a specific script type.
   * <p>
   * If the locale does not belong to the script type, it will not be set.
   *
   * @param locale
   *            - Locale information
   * @param unicodeGroup
   *            - The script type
   */
  private void setLocale(Locale locale, UnicodeGroup unicodeGroup) {
    try {
      switch (unicodeGroup) {
        case WESTERN:
          setDefaultWesternLanguage(locale);
          break;
        case CJK:
          setDefaultAsianLanguage(locale);
          break;
        case CTL:
          setDefaultComplexLanguage(locale);
          break;
      }
    } catch (Exception e) {
      Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE,
          "Failed to set locale", e);
    }
  }

  /**
   * This method will return Locale, which presents the default
   * language and country information settings in this document
   * <p>
   * ODF allows to set a Locale for each of the three UnicodeGroups.
   * Therefore there might be three different Locale for the document.
   *
   * @param unicodeGroup
   *            - One of the three (CJK, CTL or Western).
   * @return the Locale for the given UnicodeGroup
   */
  public Locale getLocale(UnicodeGroup unicodeGroup) {
    try {
      switch (unicodeGroup) {
        case WESTERN:
          return getDefaultLanguageByProperty(OdfTextProperties.Country,
              OdfTextProperties.Language);
        case CJK:
          return getDefaultLanguageByProperty(
              OdfTextProperties.CountryAsian,
              OdfTextProperties.LanguageAsian);
        case CTL:
          return getDefaultLanguageByProperty(
              OdfTextProperties.CountryComplex,
              OdfTextProperties.LanguageComplex);
      }
    } catch (Exception e) {
      Logger.getLogger(OdfDocument.class.getName()).log(Level.SEVERE,
          "Failed to get locale", e);
    }
    return null;
  }

  /** Returns the current Locale for the OdfStyleProperty of the corresponding UnicodeGroup */
  private Locale getDefaultLanguageByProperty(OdfStyleProperty countryProp,
      OdfStyleProperty languageProp) throws Exception {
    String lang = null, ctry = null;

    OdfOfficeStyles styles = getStylesDom().getOfficeStyles();

    // get language and country setting from default style setting for
    // paragraph
    OdfDefaultStyle defaultStyle = styles.getDefaultStyle(OdfStyleFamily.Paragraph);
    if (defaultStyle != null) {
      if (defaultStyle.hasProperty(countryProp)
          && defaultStyle.hasProperty(languageProp)) {
        ctry = defaultStyle.getProperty(countryProp);
        lang = defaultStyle.getProperty(languageProp);
        return new Locale(lang, ctry);
      }
    }
    // if no default style setting for paragraph
    // get language and country setting from other default style settings
    Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles();
    Iterator<OdfDefaultStyle> itera = defaultStyles.iterator();
    while (itera.hasNext()) {
      OdfDefaultStyle style = itera.next();
      if (style.hasProperty(countryProp)
          && style.hasProperty(languageProp)) {
        ctry = style.getProperty(countryProp);
        lang = style.getProperty(languageProp);
        return new Locale(lang, ctry);
      }
    }
    return null;
  }

  /**
   * This method will set the default language and country information of the
   * document, based on the parameter of the Locale information.
   *
   * @param locale
   *            - an instance of Locale that the default language and country
   *            will be set to.
   * @throws Exception
   */
  private void setDefaultWesternLanguage(Locale locale) throws Exception {
    OdfOfficeStyles styles = getStylesDom().getOfficeStyles();
    Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles();
    if (defaultStyles != null) {
      Iterator<OdfDefaultStyle> itera = defaultStyles.iterator();
      while (itera.hasNext()) {
        OdfDefaultStyle style = itera.next();
        if (style.getFamily().getProperties().contains(
            OdfTextProperties.Language)) {
          style.setProperty(OdfTextProperties.Language, locale.getLanguage());
          style.setProperty(OdfTextProperties.Country, locale.getCountry());
        }
      }
    }
  }

  /**
   * This method will set the default Asian language and country information
   * of the document, based on the parameter of the Locale information. If the
   * Locale instance is not set a Asian language (Chinese, Traditional
   * Chinese, Japanese and Korean, nothing will take effect.
   *
   * @param locale
   *            - an instance of Locale that the default Asian language and
   *            country will be set to.
   * @throws Exception
   */
  private void setDefaultAsianLanguage(Locale locale) throws Exception {
    OdfOfficeStyles styles = getStylesDom().getOfficeStyles();
    Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles();
    if (defaultStyles != null) {
      Iterator<OdfDefaultStyle> itera = defaultStyles.iterator();
      while (itera.hasNext()) {
        OdfDefaultStyle style = itera.next();
        if (style.getFamily().getProperties().contains(
            OdfTextProperties.LanguageAsian)) {
          style.setProperty(OdfTextProperties.LanguageAsian, locale.getLanguage());
          style.setProperty(OdfTextProperties.CountryAsian, locale.getCountry());
        }
      }
    }
  }

  /**
   * This method will set the default complex language and country information
   * of the document, based on the parameter of the Locale information.
   *
   * @param locale
   *            - an instance of Locale that the default complex language and
   *            country will be set to.
   * @throws Exception
   */
  private void setDefaultComplexLanguage(Locale locale) throws Exception {
    OdfOfficeStyles styles = getStylesDom().getOfficeStyles();
    Iterable<OdfDefaultStyle> defaultStyles = styles.getDefaultStyles();
    if (defaultStyles != null) {
      Iterator<OdfDefaultStyle> itera = defaultStyles.iterator();
      while (itera.hasNext()) {
        OdfDefaultStyle style = itera.next();
        if (style.getFamily().getProperties().contains(
            OdfTextProperties.LanguageComplex)) {
          style.setProperty(OdfTextProperties.LanguageComplex, locale.getLanguage());
          style.setProperty(OdfTextProperties.CountryComplex, locale.getCountry());
        }
      }
    }
  }
}
TOP

Related Classes of org.odftoolkit.odfdom.doc.OdfDocument

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.