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

import org.apache.jena.rdf.model.Model;

/**
 * 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<String, String> mUriByPrefix;
	protected Map<String, String> mPrefixByUri;
	/** Contains only the duplicate prefix.
	 * The primary hold by mPrefixByUri still have to be added
	 */
	protected Map<String, Set<String>> 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<Node, Model> 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<String, String>();
			mPrefixByUri = new HashMap<String, String>();
			mDuplicatePrefixesByUri = new HashMap<String, Set<String>>();
			inCententMetadataCache = new IdentityHashMap<Node, Model>();
			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<String, String>();
			mPrefixByUri = new HashMap<String, String>();
			mDuplicatePrefixesByUri = new HashMap<String, Set<String>>();
			inCententMetadataCache = new HashMap<Node, Model>();
			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 <code>OdfPackageDocument</code> 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 <code>String</code> 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 <code>OdfElement</code> 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 <code>OdfName</code>
	 * @return The <code>OdfElement</code>
	 * @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 <code>OdfAttribute</code>
	 * @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 <code>OdfAttribute</code>
	 * @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 <code>OdfName</code>
	 * @return  The <code>OdfAttribute</code>
	 * @throws DOMException
	 */
	public OdfAttribute createAttributeNS(OdfName name) throws DOMException {
		return OdfXMLFactory.newOdfAttribute(this, name);
	}

	@SuppressWarnings("unchecked")
	public <T extends OdfElement> T newOdfElement(Class<T> 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;
	}

	/**
	 * <p>Get Namespace URI bound to a prefix in the current scope (the XML file).</p>
	 *
	 * <p>When requesting a Namespace URI by prefix, the following
	 * table describes the returned Namespace URI value for all
	 * possible prefix values:</p>
	 *
	 * <table border="2" rules="all" cellpadding="4">
	 *   <thead>
	 *     <tr>
	 *       <td align="center" colspan="2">
	 *         <code>getNamespaceURI(prefix)</code>
	 *         return value for specified prefixes
	 *       </td>
	 *     </tr>
	 *     <tr>
	 *       <td>prefix parameter</td>
	 *       <td>Namespace URI return value</td>
	 *     </tr>
	 *   </thead>
	 *   <tbody>
	 *     <tr>
	 *       <td><code>DEFAULT_NS_PREFIX</code> ("")</td>
	 *       <td>default Namespace URI in the current scope or
	 *         <code>{@link
	 *         javax.xml.XMLConstants#NULL_NS_URI XMLConstants.NULL_NS_URI("")}
	 *         </code>
	 *         when there is no default Namespace URI in the current scope</td>
	 *     </tr>
	 *     <tr>
	 *       <td>bound prefix</td>
	 *       <td>Namespace URI bound to prefix in current scope</td>
	 *     </tr>
	 *     <tr>
	 *       <td>unbound prefix</td>
	 *       <td>
	 *         <code>{@link
	 *         javax.xml.XMLConstants#NULL_NS_URI XMLConstants.NULL_NS_URI("")}
	 *         </code>
	 *       </td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>XMLConstants.XML_NS_PREFIX</code> ("xml")</td>
	 *       <td><code>XMLConstants.XML_NS_URI</code>
	 *           ("http://www.w3.org/XML/1998/namespace")</td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>XMLConstants.XMLNS_ATTRIBUTE</code> ("xmlns")</td>
	 *       <td><code>XMLConstants.XMLNS_ATTRIBUTE_NS_URI</code>
	 *         ("http://www.w3.org/2000/xmlns/")</td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>null</code></td>
	 *       <td><code>IllegalArgumentException</code> is thrown</td>
	 *     </tr>
	 *    </tbody>
	 * </table>
	 *
	 * @param prefix prefix to look up
	 *
	 * @return Namespace URI bound to prefix in the current scope
	 *
	 * @throws IllegalArgumentException When <code>prefix</code> is
	 *   <code>null</code>
	 */
	public String getNamespaceURI(String prefix) {
		String nsURI = null;
		nsURI = mUriByPrefix.get(prefix);
		if (nsURI == null) {
			// look in Duplicate URI prefixes
			Set<String> urisWithDuplicatePrefixes = this.mDuplicatePrefixesByUri.keySet();
			for (String aURI : urisWithDuplicatePrefixes) {
				Set<String> 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;
	}

	/**
	 * <p>Get prefix bound to Namespace URI in the current scope (the XML file).</p>
	 * <p>Multiple prefixes bound to Namespace URI will be normalized to the first prefix defined.</p>
	 *
	 * <p>When requesting a prefix by Namespace URI, the following
	 * table describes the returned prefix value for all Namespace URI
	 * values:</p>
	 *
	 * <table border="2" rules="all" cellpadding="4">
	 *   <thead>
	 *     <tr>
	 *       <th align="center" colspan="2">
	 *         <code>getPrefix(namespaceURI)</code> return value for
	 *         specified Namespace URIs
	 *       </th>
	 *     </tr>
	 *     <tr>
	 *       <th>Namespace URI parameter</th>
	 *       <th>prefix value returned</th>
	 *     </tr>
	 *   </thead>
	 *   <tbody>
	 *     <tr>
	 *       <td>&lt;default Namespace URI&gt;</td>
	 *       <td><code>XMLConstants.DEFAULT_NS_PREFIX</code> ("")
	 *       </td>
	 *     </tr>
	 *     <tr>
	 *       <td>bound Namespace URI</td>
	 *       <td>prefix 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</td>
	 *     </tr>
	 *     <tr>
	 *       <td>unbound Namespace URI</td>
	 *       <td><code>null</code></td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>XMLConstants.XML_NS_URI</code>
	 *           ("http://www.w3.org/XML/1998/namespace")</td>
	 *       <td><code>XMLConstants.XML_NS_PREFIX</code> ("xml")</td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>XMLConstants.XMLNS_ATTRIBUTE_NS_URI</code>
	 *           ("http://www.w3.org/2000/xmlns/")</td>
	 *       <td><code>XMLConstants.XMLNS_ATTRIBUTE</code> ("xmlns")</td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>null</code></td>
	 *       <td><code>IllegalArgumentException</code> is thrown</td>
	 *     </tr>
	 *   </tbody>
	 * </table>
	 *
	 * @param namespaceURI URI of Namespace to lookup
	 *
	 * @return prefix bound to Namespace URI in current context
	 *
	 * @throws IllegalArgumentException When <code>namespaceURI</code> is
	 *   <code>null</code>
	 */
	public String getPrefix(String namespaceURI) {
		return mPrefixByUri.get(namespaceURI);
	}

	/**
	 * <p>Get all prefixes bound to a Namespace URI in the current
	 * scope. (the XML file)</p>
	 * <p>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.</p>
	 *
	 * <p><strong>The <code>Iterator</code> is
	 * <em>not</em> modifiable.  e.g. the
	 * <code>remove()</code> method will throw
	 * <code>UnsupportedOperationException</code>.</strong></p>
	 *
	 * <p>When requesting prefixes by Namespace URI, the following
	 * table describes the returned prefixes value for all Namespace
	 * URI values:</p>
	 *
	 * <table border="2" rules="all" cellpadding="4">
	 *   <thead>
	 *     <tr>
	 *       <th align="center" colspan="2"><code>
	 *         getPrefixes(namespaceURI)</code> return value for
	 *         specified Namespace URIs</th>
	 *     </tr>
	 *     <tr>
	 *       <th>Namespace URI parameter</th>
	 *       <th>prefixes value returned</th>
	 *     </tr>
	 *   </thead>
	 *   <tbody>
	 *     <tr>
	 *       <td>bound Namespace URI,
	 *         including the &lt;default Namespace URI&gt;</td>
	 *       <td>
	 *         <code>Iterator</code> over prefixes bound to Namespace URI in
	 *         the current scope in an arbitrary,
	 *         <strong>implementation dependent</strong>,
	 *         order
	 *       </td>
	 *     </tr>
	 *     <tr>
	 *       <td>unbound Namespace URI</td>
	 *       <td>empty <code>Iterator</code></td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>XMLConstants.XML_NS_URI</code>
	 *           ("http://www.w3.org/XML/1998/namespace")</td>
	 *       <td><code>Iterator</code> with one element set to
	 *         <code>XMLConstants.XML_NS_PREFIX</code> ("xml")</td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>XMLConstants.XMLNS_ATTRIBUTE_NS_URI</code>
	 *           ("http://www.w3.org/2000/xmlns/")</td>
	 *       <td><code>Iterator</code> with one element set to
	 *         <code>XMLConstants.XMLNS_ATTRIBUTE</code> ("xmlns")</td>
	 *     </tr>
	 *     <tr>
	 *       <td><code>null</code></td>
	 *       <td><code>IllegalArgumentException</code> is thrown</td>
	 *     </tr>
	 *   </tbody>
	 * </table>
	 *
	 * @param namespaceURI URI of Namespace to lookup
	 *
	 * @return <code>Iterator</code> for all prefixes bound to the
	 *   Namespace URI in the current scope
	 *
	 * @throws IllegalArgumentException When <code>namespaceURI</code> is
	 *   <code>null</code>
	 */
	public Iterator<String> getPrefixes(String namespaceURI) {
		Set<String> prefixes = mDuplicatePrefixesByUri.get(namespaceURI);
		if (prefixes == null) {
			prefixes = new HashSet<String>();
		}
		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<String, String> getMapNamespacePrefixByUri() {
		return mPrefixByUri;
	}

	/** Adds a new Namespace to the DOM. Making the prefix usable with JDK <code>XPath</code>.
	 * All namespace attributes will be written to the root element during later serialization of the DOM by the <code>OdfPackage</code>.
	 * @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<String> prefixes = mDuplicatePrefixesByUri.get(uri);
			if (prefixes == null) {
				prefixes = new HashSet<String>();
				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 <code>XPath</code>.
	 * All namespace attributes will be written to the root element during later serialization of the DOM by the <code>OdfPackage</code>.
	 * @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<Node, Model> 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;
	}
}
