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

org.odftoolkit.odfdom.pkg.OdfFileDom Maven / Gradle / Ivy

Go to download

ODFDOM is an OpenDocument Format (ODF) framework. Its purpose is to provide an easy common way to create, access and manipulate ODF files, without requiring detailed knowledge of the ODF specification. It is designed to provide the ODF developer community with an easy lightwork programming API portable to any object-oriented language. The current reference implementation is written in Java.

There is a newer version: 1.0.0-BETA1
Show newest version
/**
 * **********************************************************************
 *
 * 

DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * *

Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved. * *

Use is subject to license terms. * *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. You can also obtain a copy of the License at * http://odftoolkit.org/docs/license.txt * *

Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. * *

See the License for the specific language governing permissions and limitations under the * License. * *

********************************************************************** */ package org.odftoolkit.odfdom.pkg; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathFactory; import org.apache.jena.rdf.model.Model; import org.apache.xerces.dom.DocumentImpl; import org.odftoolkit.odfdom.dom.OdfContentDom; import org.odftoolkit.odfdom.dom.OdfMetaDom; import org.odftoolkit.odfdom.dom.OdfSchemaDocument; import org.odftoolkit.odfdom.dom.OdfSettingsDom; import org.odftoolkit.odfdom.dom.OdfStylesDom; import org.odftoolkit.odfdom.dom.rdfa.BookmarkRDFMetadataExtractor; import org.odftoolkit.odfdom.pkg.manifest.OdfManifestDom; import org.odftoolkit.odfdom.pkg.rdfa.DOMRDFaParser; import org.odftoolkit.odfdom.pkg.rdfa.JenaSink; import org.odftoolkit.odfdom.pkg.rdfa.MultiContentHandler; import org.odftoolkit.odfdom.pkg.rdfa.SAXRDFaParser; import org.odftoolkit.odfdom.pkg.rdfa.Util; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; /** The DOM representation of an XML file within the ODF document. */ public class OdfFileDom extends DocumentImpl implements NamespaceContext { private static final long serialVersionUID = 766167617530147000L; protected String mPackagePath; protected OdfPackageDocument mPackageDocument; protected OdfPackage mPackage; protected XPath mXPath; protected Map mUriByPrefix; protected Map mPrefixByUri; /** Contains only the duplicate prefix. The primary hold by mPrefixByUri still have to be added */ protected Map> mDuplicatePrefixesByUri; /** * The cache of in content metadata: key: a Node in the dom ; value: the Jena RDF model of triples * of the Node */ protected Map inCententMetadataCache; protected JenaSink sink; /** * Creates the DOM representation of an XML file of an Odf document. * * @param packageDocument the document the XML files belongs to * @param packagePath the internal package path to the XML file */ protected OdfFileDom(OdfPackageDocument packageDocument, String packagePath) { super(false); if (packageDocument != null && packagePath != null) { mPackageDocument = packageDocument; mPackage = packageDocument.getPackage(); mPackagePath = packagePath; mUriByPrefix = new HashMap(); mPrefixByUri = new HashMap(); mDuplicatePrefixesByUri = new HashMap>(); inCententMetadataCache = new IdentityHashMap(); initialize(); // Register every DOM to OdfPackage, // so a package close might save this DOM (similar as OdfDocumentPackage) this.addDomToCache(mPackage, packagePath); } else { throw new IllegalArgumentException( "Arguments are not allowed to be NULL for OdfFileDom constructor!"); } } /** * Creates the DOM representation of an XML file of an Odf document. * * @param pkg the package the XML files belongs to * @param packagePath the internal package path to the XML file */ protected OdfFileDom(OdfPackage pkg, String packagePath) { super(false); if (pkg != null && packagePath != null) { mPackageDocument = null; mPackage = pkg; mPackagePath = packagePath; mUriByPrefix = new HashMap(); mPrefixByUri = new HashMap(); mDuplicatePrefixesByUri = new HashMap>(); inCententMetadataCache = new HashMap(); initialize(); // Register every DOM to OdfPackage, // so a package close might save this DOM (similar as // OdfDocumentPackage) addDomToCache(mPackage, packagePath); } else { throw new IllegalArgumentException( "Arguments are not allowed to be NULL for OdfFileDom constructor!"); } } /** * Adds the document to the pool of open documents of the package. A document of a certain path is * opened only once to avoid data duplication. */ private void addDomToCache(OdfPackage pkg, String internalPath) { pkg.cacheDom(this, internalPath); } public static OdfFileDom newFileDom(OdfPackageDocument packageDocument, String packagePath) { OdfFileDom newFileDom = null; // before creating a new dom, make sure that there no DOM opened for this file already Document existingDom = packageDocument.getPackage().getCachedDom(packagePath); if (existingDom == null) { // ToDo: bug 264 - register OdfFileDom to this class if (packagePath.equals("content.xml") || packagePath.endsWith("/content.xml")) { newFileDom = new OdfContentDom((OdfSchemaDocument) packageDocument, packagePath); } else if (packagePath.equals("styles.xml") || packagePath.endsWith("/styles.xml")) { newFileDom = new OdfStylesDom((OdfSchemaDocument) packageDocument, packagePath); } else if (packagePath.equals("meta.xml") || packagePath.endsWith("/meta.xml")) { newFileDom = new OdfMetaDom((OdfSchemaDocument) packageDocument, packagePath); } else if (packagePath.equals("settings.xml") || packagePath.endsWith("/settings.xml")) { newFileDom = new OdfSettingsDom((OdfSchemaDocument) packageDocument, packagePath); } else if (packagePath.equals("META-INF/manifest.xml") || packagePath.endsWith("/META-INF/manifest.xml")) { newFileDom = new OdfManifestDom((OdfSchemaDocument) packageDocument, packagePath); } else { newFileDom = new OdfFileDom(packageDocument, packagePath); } } else { if (existingDom instanceof OdfFileDom) { newFileDom = (OdfFileDom) existingDom; // ToDO: Issue 264 - Otherwise if NOT an OdfFileDom serialize old DOM AND CREATE A NEW ONE?! // Or shall we always reference to the dom, than we can not inherit from Document? Pro/Con?s // }else{ // // Create an OdfFileDOM from an existing DOM // newFileDom = } } return newFileDom; } public static OdfFileDom newFileDom(OdfPackage pkg, String packagePath) { OdfFileDom newFileDom = null; // before creating a new dom, make sure that there no DOM opened for this file already Document existingDom = pkg.getCachedDom(packagePath); if (existingDom == null) { if (packagePath.equals("META-INF/manifest.xml") || packagePath.endsWith("/META-INF/manifest.xml")) { newFileDom = new OdfManifestDom(pkg, packagePath); } else { newFileDom = new OdfFileDom(pkg, packagePath); } } else { if (existingDom instanceof OdfFileDom) { newFileDom = (OdfFileDom) existingDom; // ToDO: Issue 264 - Otherwise if NOT an OdfFileDom serialize old DOM AND CREATE A NEW ONE?! // Or shall we always reference to the dom, than we can not inherit from Document? Pro/Con?s // }else{ // // Create an OdfFileDOM from an existing DOM // newFileDom = } } return newFileDom; } protected void initialize() { InputStream fileStream = null; try { fileStream = mPackage.getInputStream(mPackagePath); if (fileStream != null) { XMLReader xmlReader = mPackage.getXMLReader(); OdfFileSaxHandler odf = new OdfFileSaxHandler(this); String baseUri = Util.getRDFBaseUri(mPackage.getBaseURI(), mPackagePath); sink = new JenaSink(this); odf.setSink(sink); SAXRDFaParser rdfa = SAXRDFaParser.createInstance(sink); rdfa.setBase(baseUri); // the file is parsed by ODF ContentHandler, and then RDFa ContentHandler MultiContentHandler multi = new MultiContentHandler(odf, rdfa); xmlReader.setContentHandler(multi); InputSource xmlSource = new InputSource(fileStream); xmlReader.parse(xmlSource); } } catch (Exception ex) { Logger.getLogger(OdfFileDom.class.getName()).log(Level.SEVERE, null, ex); } finally { try { if (fileStream != null) { fileStream.close(); } } catch (IOException ex) { Logger.getLogger(OdfFileDom.class.getName()).log(Level.SEVERE, null, ex); } } } /** * Retrieves the OdfPackageDocument of the XML file. A package document is usually * represented as a directory with a mediatype. * * @return The document holding the XML file. */ public OdfPackageDocument getDocument() { return mPackageDocument; } /** * Retrieves the String of Package Path * * @return The path of the XML file relative to the package root */ public String getPackagePath() { return mPackagePath; } /** * Retrieves the ODF root element. * * @return The OdfElement being the root of the document. */ public OdfElement getRootElement() { return (OdfElement) getDocumentElement(); } /** * Create ODF element with namespace uri and qname * * @param name The element name */ @Override public OdfElement createElement(String name) throws DOMException { return createElementNS(OdfName.newName(name)); } /** * Create ODF element with namespace uri and qname * * @param nsuri The namespace uri * @param qname The element qname */ @Override public OdfElement createElementNS(String nsuri, String qname) throws DOMException { return createElementNS(OdfName.newName(nsuri, qname)); } /** * Create ODF element with ODF name * * @param name The OdfName * @return The OdfElement * @throws DOMException */ public OdfElement createElementNS(OdfName name) throws DOMException { return OdfXMLFactory.newOdfElement(this, name); } /** * Create the ODF attribute with its name * * @param name the attribute qname * @return The OdfAttribute * @throws DOMException */ @Override public OdfAttribute createAttribute(String name) throws DOMException { return createAttributeNS(OdfName.newName(name)); } /** * Create the ODF attribute with namespace uri and qname * * @param nsuri The namespace uri * @param qname the attribute qname * @return The OdfAttribute * @throws DOMException */ @Override public OdfAttribute createAttributeNS(String nsuri, String qname) throws DOMException { return createAttributeNS(OdfName.newName(nsuri, qname)); } /** * Create the ODF attribute with ODF name * * @param name The OdfName * @return The OdfAttribute * @throws DOMException */ public OdfAttribute createAttributeNS(OdfName name) throws DOMException { return OdfXMLFactory.newOdfAttribute(this, name); } @SuppressWarnings("unchecked") public T newOdfElement(Class clazz) { // return (T) OdfXMLFactory.getNodeFromClass(this, clazz); try { Field fname = clazz.getField("ELEMENT_NAME"); OdfName name = (OdfName) fname.get(null); return (T) createElementNS(name); } catch (Exception ex) { if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } return null; } } @Override public String toString() { return ((OdfElement) this.getDocumentElement()).toString(); } // JDK Namespace handling /** * Create an XPath instance to select one or more nodes from an ODF document. Therefore the * namespace context is set to the OdfNamespace * * @return an XPath instance with namespace context set to include the standard ODFDOM prefixes. */ public XPath getXPath() { if (mXPath == null) { mXPath = XPathFactory.newInstance().newXPath(); } return mXPath; } /** * Get Namespace URI bound to a prefix in the current scope (the XML file). * *

When requesting a Namespace URI by prefix, the following table describes the returned * Namespace URI value for all possible prefix values: * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* getNamespaceURI(prefix) * return value for specified prefixes *
prefix parameterNamespace URI return value
DEFAULT_NS_PREFIX ("")default Namespace URI in the current scope or * {@link * javax.xml.XMLConstants#NULL_NS_URI XMLConstants.NULL_NS_URI("")} * * when there is no default Namespace URI in the current scope
bound prefixNamespace URI bound to prefix in current scope
unbound prefix * {@link * javax.xml.XMLConstants#NULL_NS_URI XMLConstants.NULL_NS_URI("")} * *
XMLConstants.XML_NS_PREFIX ("xml")XMLConstants.XML_NS_URI * ("http://www.w3.org/XML/1998/namespace")
XMLConstants.XMLNS_ATTRIBUTE ("xmlns")XMLConstants.XMLNS_ATTRIBUTE_NS_URI * ("http://www.w3.org/2000/xmlns/")
nullIllegalArgumentException is thrown
* * @param prefix prefix to look up * @return Namespace URI bound to prefix in the current scope * @throws IllegalArgumentException When prefix is null */ public String getNamespaceURI(String prefix) { String nsURI = null; nsURI = mUriByPrefix.get(prefix); if (nsURI == null) { // look in Duplicate URI prefixes Set urisWithDuplicatePrefixes = this.mDuplicatePrefixesByUri.keySet(); for (String aURI : urisWithDuplicatePrefixes) { Set prefixes = this.mDuplicatePrefixesByUri.get(aURI); // check if requested prefix exists in hashset if (prefixes.contains(prefix)) { nsURI = aURI; break; } } } // there is a possibility it still may be null - so we check if (nsURI == null) { nsURI = XMLConstants.NULL_NS_URI; } return nsURI; } /** * Get prefix bound to Namespace URI in the current scope (the XML file). * *

Multiple prefixes bound to Namespace URI will be normalized to the first prefix defined. * *

When requesting a prefix by Namespace URI, the following table describes the returned prefix * value for all Namespace URI values: * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* getPrefix(namespaceURI) return value for * specified Namespace URIs *
Namespace URI parameterprefix value returned
<default Namespace URI>XMLConstants.DEFAULT_NS_PREFIX ("") *
bound Namespace URIprefix bound to Namespace URI in the current scope, * if multiple prefixes are bound to the Namespace URI in * the current scope, a single arbitrary prefix, whose * choice is implementation dependent, is returned
unbound Namespace URInull
XMLConstants.XML_NS_URI * ("http://www.w3.org/XML/1998/namespace")XMLConstants.XML_NS_PREFIX ("xml")
XMLConstants.XMLNS_ATTRIBUTE_NS_URI * ("http://www.w3.org/2000/xmlns/")XMLConstants.XMLNS_ATTRIBUTE ("xmlns")
nullIllegalArgumentException is thrown
* * @param namespaceURI URI of Namespace to lookup * @return prefix bound to Namespace URI in current context * @throws IllegalArgumentException When namespaceURI is null */ public String getPrefix(String namespaceURI) { return mPrefixByUri.get(namespaceURI); } /** * Get all prefixes bound to a Namespace URI in the current scope. (the XML file) * *

NOTE: Multiple prefixes bound to a similar Namespace URI will be normalized to the first * prefix defined. Still the namespace attributes exist in the XML as inner value prefixes might * be used. * *

The Iterator is not modifiable. e.g. the remove() * method will throw UnsupportedOperationException. * *

When requesting prefixes by Namespace URI, the following table describes the returned * prefixes value for all Namespace URI values: * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* getPrefixes(namespaceURI) return value for * specified Namespace URIs
Namespace URI parameterprefixes value returned
bound Namespace URI, * including the <default Namespace URI> * Iterator over prefixes bound to Namespace URI in * the current scope in an arbitrary, * implementation dependent, * order *
unbound Namespace URIempty Iterator
XMLConstants.XML_NS_URI * ("http://www.w3.org/XML/1998/namespace")Iterator with one element set to * XMLConstants.XML_NS_PREFIX ("xml")
XMLConstants.XMLNS_ATTRIBUTE_NS_URI * ("http://www.w3.org/2000/xmlns/")Iterator with one element set to * XMLConstants.XMLNS_ATTRIBUTE ("xmlns")
nullIllegalArgumentException is thrown
* * @param namespaceURI URI of Namespace to lookup * @return Iterator for all prefixes bound to the Namespace URI in the current scope * @throws IllegalArgumentException When namespaceURI is null */ public Iterator getPrefixes(String namespaceURI) { Set prefixes = mDuplicatePrefixesByUri.get(namespaceURI); if (prefixes == null) { prefixes = new HashSet(); } String givenPrefix = mPrefixByUri.get(namespaceURI); if (givenPrefix != null) { prefixes.add(givenPrefix); } return prefixes.iterator(); } /** @return a map of namespaces, where the URI is the key and the prefix is the value */ Map getMapNamespacePrefixByUri() { return mPrefixByUri; } /** * Adds a new Namespace to the DOM. Making the prefix usable with JDK XPath. All * namespace attributes will be written to the root element during later serialization of the DOM * by the OdfPackage. * * @param prefix of the namespace to be set to this DOM * @param uri of the namespace to be set to this DOM * @return the namespace that was set. If an URI was registered before to the DOM, the previous * prefix will be taken. In case of a given prefix that was already registered, but related to * a new URI, the prefix will be adapted. The new prefix receives the suffix '__' plus * integer, e.g. "__1" for the first duplicate and "__2" for the second. */ public OdfNamespace setNamespace(String prefix, String uri) { // collision detection, when a new prefix/URI pair exists OdfNamespace newNamespace = null; // Scenario a) the URI already registered, use existing prefix // but save all others for the getPrefixes function. There might be still some // in attribute values using prefixes, that were not exchanged. String existingPrefix = mPrefixByUri.get(uri); if (existingPrefix != null) { // Use the existing prefix of the used URL, neglect the given newNamespace = OdfNamespace.newNamespace(existingPrefix, uri); // Add the new prefix to the duplicate prefix map for getPrefixes(String uri) Set prefixes = mDuplicatePrefixesByUri.get(uri); if (prefixes == null) { prefixes = new HashSet(); mDuplicatePrefixesByUri.put(uri, prefixes); } prefixes.add(prefix); } else { // Scenario b) the prefix already exists and the URI does not exist String existingURI = mUriByPrefix.get(prefix); if (existingURI != null && !existingURI.equals(uri)) { // Change the prefix appending "__" plus counter. int i = 1; do { int suffixStart = prefix.lastIndexOf("__"); if (suffixStart != -1) { prefix = prefix.substring(0, suffixStart); } // users have to take care for their attribute values using namespace prefixes. prefix = prefix + "__" + i; i++; existingURI = mUriByPrefix.get(prefix); } while (existingURI != null && !existingURI.equals(uri)); } newNamespace = OdfNamespace.newNamespace(prefix, uri); mPrefixByUri.put(uri, prefix); mUriByPrefix.put(prefix, uri); } // if the file Dom is already associated to parsed XML add the new namespace to the root element Element root = getRootElement(); if (root != null) { root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:" + prefix, uri); } return newNamespace; } /** * Adds a new Namespace to the DOM. Making the prefix usable with JDK XPath. All * namespace attributes will be written to the root element during later serialization of the DOM * by the OdfPackage. * * @param name the namespace to be set * @return the namespace that was set. If an URI was registered before to the DOM, the previous * prefix will be taken. In case of a given prefix that was already registered, but related to * a new URI, the prefix will be adapted. The new prefix receives the suffix '__' plus * integer, e.g. "__1" for the first duplicate and "__2" for the second. */ public OdfNamespace setNamespace(NamespaceName name) { return setNamespace(name.getPrefix(), name.getUri()); } /** * Get in-content metadata cache model * * @return in-content metadata cache model */ public Map getInContentMetadataCache() { return this.inCententMetadataCache; } /** * Update the in content metadata of the node. It should be called whenever the xhtml:xxx * attrbutes values of the node are changed. * * @param the node, whose in content metadata will be updated */ public void updateInContentMetadataCache(Node node) { this.getInContentMetadataCache().remove(node); DOMRDFaParser parser = DOMRDFaParser.createInstance(this.sink); String baseUri = Util.getRDFBaseUri(mPackage.getBaseURI(), mPackagePath); parser.setBase(baseUri); parser.parse(node); } /** @return the RDF metadata of all the bookmarks within the dom */ public Model getBookmarkRDFMetadata() { return BookmarkRDFMetadataExtractor.newBookmarkTextExtractor().getBookmarkRDFMetadata(this); } /** * The end users needn't to care of this method, which is used by BookmarkRDFMetadataExtractor * * @return the JenaSink */ public JenaSink getSink() { return sink; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy