/** | |
* ********************************************************************** | |
* | |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER | |
* | |
* Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved. | |
* | |
* Use is subject to license terms. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
* use this file except in compliance with the License. You may obtain a copy | |
* of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also | |
* obtain a copy of the License at http://odftoolkit.org/docs/license.txt | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
************************************************************************/ | |
package org.odftoolkit.odfdom.dom; | |
import java.io.InputStream; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
import org.odftoolkit.odfdom.dom.element.office.OfficeBodyElement; | |
import org.odftoolkit.odfdom.dom.element.office.OfficeMasterStylesElement; | |
import org.odftoolkit.odfdom.dom.element.style.StyleMasterPageElement; | |
import org.odftoolkit.odfdom.dom.element.table.TableTableElement; | |
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeMasterStyles; | |
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles; | |
import org.odftoolkit.odfdom.pkg.OdfElement; | |
import org.odftoolkit.odfdom.pkg.OdfFileDom; | |
import org.odftoolkit.odfdom.pkg.OdfPackage; | |
import org.odftoolkit.odfdom.pkg.OdfPackageDocument; | |
import org.odftoolkit.odfdom.pkg.OdfValidationException; | |
import org.odftoolkit.odfdom.pkg.rdfa.Util; | |
import org.w3c.dom.Element; | |
import org.w3c.dom.Node; | |
import org.w3c.dom.NodeList; | |
import org.xml.sax.ErrorHandler; | |
import org.xml.sax.SAXException; | |
import org.apache.jena.rdf.model.Model; | |
import org.apache.jena.rdf.model.ModelFactory; | |
import org.apache.jena.util.ResourceUtils; | |
import java.io.InputStreamReader; | |
/** | |
* A document in ODF is from the package view a directory with a media type. | |
* If the media type represents a document described by the ODF 1.2 Schema, | |
* certain files are assumed within: | |
* content.xml, styles.xml, metadata.xml and settings.xml. | |
* | |
* The class represents such a document, providing easier access to its XML files. | |
*/ | |
public abstract class OdfSchemaDocument extends OdfPackageDocument { | |
protected OdfContentDom mContentDom; | |
protected OdfStylesDom mStylesDom; | |
protected OdfMetaDom mMetaDom; | |
protected OdfSettingsDom mSettingsDom; | |
protected OdfOfficeStyles mDocumentStyles; | |
/** | |
* Creates a new OdfSchemaDocument. | |
* | |
* @param pkg - the ODF Package that contains the document. A baseURL is being generated based on its location. | |
* @param internalPath - the directory path within the package from where the document should be loaded. | |
* @param mediaTypeString | |
* - media type of stream. If unknown null can be used. | |
*/ | |
protected OdfSchemaDocument(OdfPackage pkg, String internalPath, String mediaTypeString) { | |
super(pkg, internalPath, mediaTypeString); | |
ErrorHandler errorHandler = pkg.getErrorHandler(); | |
if (errorHandler != null) { | |
if (pkg.getFileEntry(internalPath + "content.xml") == null && pkg.getFileEntry(internalPath + "styles.xml") == null) { | |
try { | |
String baseURI = pkg.getBaseURI(); | |
if (baseURI == null) { | |
baseURI = internalPath; | |
} else { | |
if (!internalPath.equals(ROOT_DOCUMENT_PATH)) { | |
baseURI = "/" + internalPath; | |
} | |
} | |
errorHandler.error(new OdfValidationException(OdfSchemaConstraint.DOCUMENT_WITHOUT_CONTENT_NOR_STYLES_XML, baseURI)); | |
} catch (SAXException ex) { | |
Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); | |
} | |
} | |
InputStream mimetypeStream = pkg.getInputStream(OdfPackage.OdfFile.MEDIA_TYPE.getPath(), true); | |
if (internalPath.equals(ROOT_DOCUMENT_PATH) && mimetypeStream == null) { | |
try { | |
errorHandler.error(new OdfValidationException(OdfSchemaConstraint.PACKAGE_SHALL_CONTAIN_MIMETYPE, pkg.getBaseURI())); | |
} catch (SAXException ex) { | |
Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex); | |
} | |
} | |
} | |
} | |
/** | |
* This enum contains all possible standardized XML ODF files of the OpenDocument document. | |
*/ | |
public static enum OdfXMLFile { | |
/** The XML file containing the content of an ODF document as specified by the ODF 1.2 specification part 1. */ | |
CONTENT("content.xml"), | |
/** The XML file containing a predifined set of metadata related to an ODF document as specified by the ODF 1.2 specification part 1. */ | |
META("meta.xml"), | |
/** The XML file containing the settings of an ODF document as specified by the ODF 1.2 specification part 1. */ | |
SETTINGS("settings.xml"), | |
/** The XML file containing the styles of an ODF document as specified by the ODF 1.2 specification part 1. */ | |
STYLES("styles.xml"); | |
private final String mFileName; | |
/** | |
* @return the file name of xml files contained in odf packages. | |
*/ | |
public String getFileName() { | |
return mFileName; | |
} | |
OdfXMLFile(String fileName) { | |
this.mFileName = fileName; | |
} | |
} | |
/** | |
* Gets the ODF content.xml file as stream. | |
* | |
* @return - a stream of the ODF content 'content.xml' file | |
* @throws java.lang.Exception - if the stream can not be extracted | |
*/ | |
public InputStream getContentStream() throws Exception { | |
String path = getXMLFilePath(OdfXMLFile.CONTENT); | |
return mPackage.getInputStream(path); | |
} | |
/** | |
* Gets the ODF style.xml file as stream. | |
* | |
* @return - a stream of the ODF style 'styles.xml' file | |
* @throws java.lang.Exception - if the stream can not be extracted | |
*/ | |
public InputStream getStylesStream() throws Exception { | |
return mPackage.getInputStream(getXMLFilePath(OdfXMLFile.STYLES)); | |
} | |
/** | |
* Gets the ODF settings.xml file as stream. | |
* | |
* @return - a stream of the ODF settings 'setting.xml' file | |
* @throws java.lang.Exception - if the stream can not be extracted | |
*/ | |
public InputStream getSettingsStream() throws Exception { | |
return mPackage.getInputStream(getXMLFilePath(OdfXMLFile.SETTINGS)); | |
} | |
/** | |
* Gets the ODF metadata.xml file as stream. | |
* | |
* @return - a stream of the ODF metadata 'meta.xml' file | |
* @throws java.lang.Exception - if the stream can not be extracted | |
*/ | |
public InputStream getMetaStream() throws Exception { | |
return mPackage.getInputStream(getXMLFilePath(OdfXMLFile.META)); | |
} | |
/** | |
* Get the relative path for an embedded ODF document including its file name. | |
* @param file represents one of the standardized XML ODF files. | |
* @return path to embedded ODF XML file relative to ODF package root. | |
*/ | |
protected String getXMLFilePath(OdfXMLFile file) { | |
return file.mFileName; | |
} | |
/** | |
* Get the URI, where this ODF document is stored. | |
* @return the URI to the ODF document. Returns null if document is not stored yet. | |
*/ | |
public String getBaseURI() { | |
return mPackage.getBaseURI(); | |
} | |
/** | |
* Return the ODF type-based content DOM of the content.xml | |
* | |
* @return ODF type-based content DOM or null if no content.xml exists. | |
* @throws Exception if content DOM could not be initialized | |
*/ | |
public OdfContentDom getContentDom() throws Exception { | |
if (mContentDom == null) { | |
mContentDom = (OdfContentDom) getFileDom(OdfXMLFile.CONTENT); | |
} | |
return mContentDom; | |
} | |
/** | |
* Return the ODF type-based styles DOM of the styles.xml | |
* | |
* @return ODF type-based styles DOM or null if no styles.xml exists. | |
* @throws Exception if styles DOM could not be initialized | |
*/ | |
public OdfStylesDom getStylesDom() throws Exception { | |
if (mStylesDom == null) { | |
mStylesDom = (OdfStylesDom) getFileDom(OdfXMLFile.STYLES); | |
} | |
return mStylesDom; | |
} | |
/** | |
* Return the ODF type-based metadata DOM of the meta.xml | |
* | |
* @return ODF type-based meta DOM or null if no meta.xml exists. | |
* @throws Exception if meta DOM could not be initialized | |
*/ | |
public OdfMetaDom getMetaDom() throws Exception { | |
if (mMetaDom == null) { | |
mMetaDom = (OdfMetaDom) getFileDom(OdfXMLFile.META); | |
} | |
return mMetaDom; | |
} | |
/** | |
* Return the ODF type-based settings DOM of the settings.xml | |
* | |
* @return ODF type-based settings DOM or null if no settings.xml exists. | |
* @throws Exception if settings DOM could not be initialized | |
*/ | |
public OdfSettingsDom getSettingsDom() throws Exception { | |
if (mSettingsDom == null) { | |
mSettingsDom = (OdfSettingsDom) getFileDom(OdfXMLFile.SETTINGS); | |
} | |
return mSettingsDom; | |
} | |
/** | |
* | |
* @return the office:styles element from the styles dom or null if there is | |
* no such element. | |
*/ | |
public OdfOfficeStyles getDocumentStyles() { | |
if (mDocumentStyles == null) { | |
try { | |
OdfFileDom stylesDom = getStylesDom(); | |
if (stylesDom != null) { | |
mDocumentStyles = OdfElement.findFirstChildNode(OdfOfficeStyles.class, stylesDom.getFirstChild()); | |
} else { | |
return null; | |
} | |
} catch (Exception ex) { | |
Logger.getLogger(OdfSchemaDocument.class.getName()).log(Level.SEVERE, null, ex); | |
} | |
} | |
return mDocumentStyles; | |
} | |
/** | |
* return the office:master-styles element of this document. | |
* | |
* @return the office:master-styles element | |
*/ | |
public OdfOfficeMasterStyles getOfficeMasterStyles() { | |
try { | |
OdfFileDom fileDom = getStylesDom(); | |
if (fileDom != null) { | |
return OdfElement.findFirstChildNode(OdfOfficeMasterStyles.class, fileDom.getFirstChild()); | |
} | |
} catch (Exception ex) { | |
Logger.getLogger(OdfSchemaDocument.class.getName()).log(Level.SEVERE, null, ex); | |
} | |
return null; | |
} | |
/** | |
* | |
* @return the office:styles element from the styles dom. If there is not | |
* yet such an element, it is created. | |
*/ | |
public OdfOfficeStyles getOrCreateDocumentStyles() { | |
if (mDocumentStyles == null) { | |
try { | |
OdfFileDom stylesDom = getStylesDom(); | |
Node parent = stylesDom != null ? stylesDom.getFirstChild() : null; | |
if (parent != null) { | |
mDocumentStyles = OdfElement.findFirstChildNode(OdfOfficeStyles.class, parent); | |
if (mDocumentStyles == null) { | |
mDocumentStyles = stylesDom.newOdfElement(OdfOfficeStyles.class); | |
parent.insertBefore(mDocumentStyles, parent.getFirstChild()); | |
} | |
} | |
} catch (Exception ex) { | |
Logger.getLogger(OdfSchemaDocument.class.getName()).log(Level.SEVERE, null, ex); | |
} | |
} | |
return mDocumentStyles; | |
} | |
/** | |
* Return a list of table features in this document. | |
* | |
* @return a list of table features in this document. | |
*/ | |
// ToDo: Instead of a method to receive all possible feature/components on the document, there might be a generic or one each element? | |
public List<TableTableElement> getTables() { | |
List<TableTableElement> tableList = new ArrayList<TableTableElement>(); | |
try { | |
// find tables from content.xml | |
OfficeBodyElement officeBody = OdfElement.findFirstChildNode(OfficeBodyElement.class, getContentDom().getRootElement()); | |
OdfElement contentRoot = OdfElement.findFirstChildNode(OdfElement.class, officeBody); | |
tableList = fillTableList(contentRoot, tableList); | |
// find tables from styles.xml (header & footer) | |
Map<String, StyleMasterPageElement> masterPages = getMasterPages(); | |
StyleMasterPageElement defaultMasterPage = masterPages.get("Standard"); | |
if (defaultMasterPage != null) { | |
tableList = fillTableList(defaultMasterPage, tableList); | |
} | |
} catch (Exception ex) { | |
Logger.getLogger(OdfSchemaDocument.class.getName()).log(Level.SEVERE, null, ex); | |
} | |
return tableList; | |
} | |
// Only tables being on root level are being considered | |
private List<TableTableElement> fillTableList(Element startElement, List<TableTableElement> tableList) { | |
NodeList childList = startElement.getChildNodes(); | |
for (int i = 0; | |
i < childList.getLength(); | |
i++) { | |
Node childNode = childList.item(i); | |
if (childNode instanceof Element) { | |
if(childNode instanceof TableTableElement){ | |
tableList.add((TableTableElement) childList.item(i)); | |
}else{ | |
fillTableList((Element) childNode, tableList); | |
} | |
} | |
} | |
return tableList; | |
} | |
/** | |
* ToDo: Instead of adding all elements using an index to the document, we | |
* might add a pattern to the code generation to create a HashMap either on | |
* demand (whenever such a structure is required from the user) or by | |
* default | |
* | |
* @deprecated This method will be moved to the generated sources as soon | |
* code generation was improved! | |
* | |
*/ | |
public Map<String, StyleMasterPageElement> getMasterPages() throws Exception { | |
// get original values: | |
OdfStylesDom stylesDoc = getStylesDom(); | |
OfficeMasterStylesElement masterStyles = OdfElement.findFirstChildNode(OfficeMasterStylesElement.class, stylesDoc.getRootElement()); | |
Map<String, StyleMasterPageElement> masterPages = null; | |
if (masterStyles != null) { | |
NodeList lstMasterPages = stylesDoc.getElementsByTagNameNS(OdfDocumentNamespace.STYLE.getUri(), "master-page"); | |
if (lstMasterPages != null && lstMasterPages.getLength() > 0) { | |
masterPages = new HashMap(); | |
for (int i = 0; i < lstMasterPages.getLength(); i++) { | |
StyleMasterPageElement masterPage = (StyleMasterPageElement) lstMasterPages.item(i); //Take the node from the list | |
//ToDo: Drop Attribute Suffix for methods returning String values and NOT Attributes | |
String styleName = masterPage.getStyleNameAttribute(); | |
masterPages.put(styleName, masterPage); | |
} | |
} | |
} | |
return masterPages; | |
} | |
/** | |
* 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() { | |
mContentDom = null; | |
mStylesDom = null; | |
mMetaDom = null; | |
mSettingsDom = null; | |
mDocumentStyles = null; | |
super.close(); | |
} | |
public OdfFileDom getFileDom(OdfXMLFile file) throws Exception { | |
return getFileDom(getXMLFilePath(file)); | |
} | |
/** | |
* Get all two types of RDF Metadata through GRDDL XSLT: | |
* http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1415068_253892949 | |
*/ | |
public Model getRDFMetadata() throws Exception { | |
Model m = getInContentMetadata().union(this.getManifestRDFMetadata()); | |
return m; | |
} | |
/** | |
* Get In Content RDF Metadata through GRDDL XSLT | |
* http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1415070_253892949 | |
*/ | |
public Model getInContentMetadata() throws Exception { | |
Model documentRDFModel = ModelFactory.createDefaultModel(); | |
Model fileRDFModel = null; | |
for (String internalPath : this.getPackage().getFilePaths()) { | |
for (OdfXMLFile file : OdfXMLFile.values()) { | |
if (Util.isSubPathOf(internalPath, this.getDocumentPath()) | |
&& internalPath.endsWith(file.getFileName())) { | |
fileRDFModel = getXMLFileMetadata(internalPath); | |
if (fileRDFModel.size() > 0) { | |
documentRDFModel = documentRDFModel.union(fileRDFModel); | |
} | |
break; | |
} | |
} | |
} | |
if (fileRDFModel.size() > 0) { | |
documentRDFModel = documentRDFModel.union(fileRDFModel); | |
} | |
return documentRDFModel; | |
} | |
/** | |
* Get in-content metadata cache model | |
* | |
* @return The in-content metadata cache model | |
* @throws Exception | |
*/ | |
public Model getInContentMetadataFromCache() throws Exception { | |
Model m = ModelFactory.createDefaultModel(); | |
// find and merge the RDF triples cache from the OdfXMLFile files | |
for (OdfXMLFile file : OdfXMLFile.values()) { | |
for (Model m1 : this.getFileDom(file).getInContentMetadataCache().values()) { | |
m = m.union(m1); | |
} | |
} | |
return m; | |
} | |
/** | |
* Get RDF metadata from manifest.rdf and those rdf files registered in the | |
* manifest.xml as "application/rdf+xml" through GRDDL XSLT | |
* http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1415072_253892949 | |
*/ | |
public Model getManifestRDFMetadata() throws Exception { | |
Model m = ModelFactory.createDefaultModel(); | |
for (String internalPath : this.getPackage().getFilePaths()) { | |
if (Util.isSubPathOf(internalPath, this.getDocumentPath()) && this.getPackage().getMediaTypeString(internalPath).endsWith("application/rdf+xml")) { | |
Model m1 = ModelFactory.createDefaultModel(); | |
String RDFBaseUri = Util.getRDFBaseUri(this.getPackage().getBaseURI(), internalPath); | |
m1.read(new InputStreamReader(this.getPackage().getInputStream(internalPath), "utf-8"), RDFBaseUri); | |
// remove the last SLASH at the end of the RDFBaseUri: | |
// test_rdfmeta.odt/ --> test_rdfmeta.odt | |
ResourceUtils.renameResource(m1.getResource(RDFBaseUri), RDFBaseUri.substring(0, RDFBaseUri.length() - 1)); | |
if (m1.size() > 0) { | |
m = m.union(m1); | |
} | |
} | |
} | |
return m; | |
} | |
/** | |
* Get in-content metadata model of bookmarks | |
* | |
* @return The in-content metadata model of bookmarks | |
* @throws Exception | |
*/ | |
public Model getBookmarkRDFMetadata() throws Exception { | |
Model m = ModelFactory.createDefaultModel(); | |
for (OdfXMLFile file : OdfXMLFile.values()) { | |
OdfFileDom dom = getFileDom(file); | |
m = m.union(dom.getBookmarkRDFMetadata()); | |
} | |
return m; | |
} | |
} |