View Javadoc

1   /*
2    * Copyright 1999-2006 Faculty of Mathematics, Physics and Informatics, Comenius
3    * University, Bratislava. This file is protected by the Mozilla Public License
4    * version 1.1 (the "License"); you may not use this file except in compliance
5    * with the License. You may obtain a copy of the License at
6    * http://euromath2.sourceforge.net/license.html Unless required by applicable
7    * law or agreed to in writing, software distributed under the License is
8    * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
9    * KIND, either express or implied. See the License for the specific language
10   * governing permissions and limitations under the License.
11   */
12  package sk.uniba.euromath.document;
13  import java.util.Collections;
14  import java.util.EnumSet;
15  import java.util.HashMap;
16  import java.util.HashSet;
17  import java.util.Map;
18  import java.util.Set;
19  import java.util.StringTokenizer;
20  import javax.xml.XMLConstants;
21  import org.apache.commons.lang.StringUtils;
22  import org.w3c.dom.Attr;
23  import org.w3c.dom.Document;
24  import org.w3c.dom.Element;
25  import org.w3c.dom.EntityReference;
26  import org.w3c.dom.NamedNodeMap;
27  import org.w3c.dom.Node;
28  import org.w3c.dom.events.Event;
29  import org.w3c.dom.events.EventListener;
30  import org.w3c.dom.events.EventTarget;
31  import org.w3c.dom.events.MutationEvent;
32  import org.w3c.dom.traversal.DocumentTraversal;
33  import org.w3c.dom.traversal.NodeFilter;
34  import org.w3c.dom.traversal.NodeIterator;
35  import sk.baka.ikslibs.DOMUtils;
36  import sk.baka.ikslibs.EventClassEnum;
37  import sk.baka.ikslibs.EventTypesEnum;
38  import sk.baka.ikslibs.INodeObserver;
39  import sk.baka.ikslibs.UnremovableNodeException;
40  import sk.baka.ikslibs.modify.DOMMutils;
41  import sk.baka.xml.gene.ExportUtils;
42  import sk.uniba.euromath.config.EuromathConfig;
43  import sk.uniba.euromath.config.bind.NamespaceType;
44  import sk.uniba.euromath.document.lang.Messages;
45  /***
46   * <p>
47   * Manages prefixes in given document. Upon constructing, namespace prefixes are
48   * unified by following algorithm:
49   * </p>
50   * <ul>
51   * <li>Prefix for all namespace definitions are remembered, one for each
52   * namespace definition (by the <code>xmlns</code> attribute). First prefix
53   * definition is taken.</li>
54   * <li>If mapping defines no prefix for non-empty namespace (for example,
55   * <code>xmlns="foo:bar"</code>), then this prefix definition is not
56   * remembered. Instead, next prefix definition is remembered.</li>
57   * <li>All <code>xmlns:foo=""</code> definitions are processed as if the
58   * <code>xmlns=""</code> was encountered - empty namespaces are thus mapped to
59   * empty prefix.</li>
60   * <li>Then, all <code>xmlns</code> definitions are removed.</li>
61   * <li>All namespaces, that doesn't have prefix defined, are given new prefix,
62   * generated via <code>getPreferredPrefix()</code> function.</li>
63   * <li>Then, all element's prefixes are set, so following is true: any two
64   * elements, that have same namespace, has same prefix.</li>
65   * </ul>
66   * <p>
67   * As a result, document (except descendants of entity reference nodes) is
68   * stripped of <code>xmlns</code> attributes.
69   * </p>
70   * <p>
71   * Thread unsafe.
72   * </p>
73   * <p>
74   * Even when all elements/attributes from some namespaces are removed the
75   * mapping is not deleted automatically. It is removed when the manager is
76   * queried for all known namespaces, using {@link #getAllNamespaces()},
77   * {@link #getCounts()} or {@link #getElementNamespaces()} methods.
78   * </p>
79   * @author Martin Vysny
80   */
81  public final class NamespaceManager implements INodeObserver {
82  	/***
83  	 * Returns preferred prefix for given namespace due to the emConfig.
84  	 * @param namespaceUri return preferred prefix for this namespace.
85  	 * @return preferred prefix from configFile, or <code>null</code> if none
86  	 * had been found.
87  	 */
88  	private static String getPreferredPrefix(String namespaceUri) {
89  		NamespaceType ns = EuromathConfig.getNamespace(namespaceUri);
90  		if (ns == null) {
91  			// we have no preferred prefixes for this namespace, return null.
92  			return null;
93  		}
94  		return ns.getDefaultPrefix();
95  	}
96  	/***
97  	 * Reference to document.
98  	 */
99  	private final Document doc;
100 	/***
101 	 * Maps namespace URI to prefix name. Must be consistent with
102 	 * <code>mapPrefixToNs</code> map.
103 	 */
104 	private final Map<String, String> mapNsToPrefix = new HashMap<String, String>();
105 	/***
106 	 * Maps prefix to namespace URI. Must be consistent with
107 	 * <code>mapNsToPrefix</code> map.
108 	 */
109 	private final Map<String, String> mapPrefixToNs = new HashMap<String, String>();
110 	/***
111 	 * <p>
112 	 * Contains all namespaces present in the document. This may contain more
113 	 * namespaces than the two maps because entity reference may introduce new
114 	 * namespace and that is not prefix-normalized. It is always superset of
115 	 * <code>mapNsToPrefix.keySet()</code>.
116 	 * </p>
117 	 * <p>
118 	 * Maps namespace to a counter that counts elements and attributes with
119 	 * given namespace. The counter may be zero only when namespace is declared
120 	 * but no elements/attributes with the namespace are present in the
121 	 * document. In this case, when
122 	 */
123 	private final Map<String, NodeCount> allNamespaces = new HashMap<String, NodeCount>();
124 	/***
125 	 * Class holding elements and attributes count. Mutable.
126 	 * @author Martin Vysny
127 	 */
128 	public static final class NodeCount {
129 		/***
130 		 * Count of <code>Element</code> nodes.
131 		 */
132 		public int elements = 0;
133 		/***
134 		 * Count of <code>Attribute</code> nodes.
135 		 */
136 		public int attributes = 0;
137 		/***
138 		 * Constructs zero counter.
139 		 */
140 		public NodeCount() {
141 			super();
142 		}
143 		/***
144 		 * Copy-constructor.
145 		 * @param other copies the object.
146 		 */
147 		public NodeCount(final NodeCount other) {
148 			this();
149 			elements = other.elements;
150 			attributes = other.attributes;
151 		}
152 		/***
153 		 * Returns count of all nodes.
154 		 * @return sum of <code>elements</code> and <code>attributes</code>.
155 		 */
156 		public int getCount() {
157 			assert elements >= 0 && attributes >= 0;
158 			return elements + attributes;
159 		}
160 		/*
161 		 * (non-Javadoc)
162 		 * @see java.lang.Object#equals(java.lang.Object)
163 		 */
164 		@Override
165 		public boolean equals(Object obj) {
166 			if (this == obj)
167 				return true;
168 			if (!(obj instanceof NodeCount))
169 				return false;
170 			final NodeCount that = (NodeCount) obj;
171 			return that.attributes == attributes && that.elements == elements;
172 		}
173 		/*
174 		 * (non-Javadoc)
175 		 * @see java.lang.Object#hashCode()
176 		 */
177 		@Override
178 		public int hashCode() {
179 			return attributes * 2407 + elements;
180 		}
181 		/*
182 		 * (non-Javadoc)
183 		 * @see java.lang.Object#toString()
184 		 */
185 		@Override
186 		public String toString() {
187 			return "e#" + elements + ";a#" + attributes; //$NON-NLS-1$ //$NON-NLS-2$
188 		}
189 	}
190 	/***
191 	 * Parent namespace mananger.
192 	 */
193 	private final NamespaceManager parent;
194 	/***
195 	 * Creates an empty namespace manager.
196 	 * @param doc the document. It is not unified when using this constructor.
197 	 * @param parent parent namespace manager.
198 	 */
199 	private NamespaceManager(Document doc, NamespaceManager parent) {
200 		super();
201 		this.doc = doc;
202 		this.parent = parent;
203 	}
204 	/***
205 	 * Constructs a namespace manager, and modifies the document by unificating
206 	 * all namespace prefixes.
207 	 * @param doc document, that will be managed.
208 	 * @throws DocumentException if an unremovable xmlns attribute occured.
209 	 */
210 	NamespaceManager(Document doc) throws DocumentException {
211 		this(doc, null);
212 		// maps namespaceURI to prefix name, and serves for remembering of last
213 		// wanted prefix name.
214 		if (doc.getDocumentElement() != null) {
215 			Map<String, String> lastPrefixName = new HashMap<String, String>();
216 			try {
217 				// 1st part:
218 				// - get map of all namespaces and their prefixes
219 				// - remove all xmlns attributes
220 				mapPrefixes(lastPrefixName);
221 				// 2nd part:
222 				// - complete the prefixes sets
223 				// - unify all prefixes
224 				unifyPrefixes(lastPrefixName);
225 			} catch (UnremovableNodeException ex) {
226 				throw new DocumentException(ex);
227 			}
228 		}
229 	}
230 	/***
231 	 * Constructs a namespace manager, that is a child of given namespace
232 	 * manager. Document is not modified, changes to this namespace manager are
233 	 * not displayed in parent manager.
234 	 * @param parent parent namespace manager.
235 	 */
236 	public NamespaceManager(NamespaceManager parent) {
237 		this(parent.doc, parent);
238 	}
239 	/***
240 	 * Get map of all namespaces and their prefixes. When colliding prefix is
241 	 * encountered, it is not processed by this method. Removes all
242 	 * <code>xmlns</code> attributes also.
243 	 * @param lastPrefixName maps namespaceURI to prefix name, and serves for
244 	 * remembering of last wanted prefix name.
245 	 * @throws UnremovableNodeException if an unremovable xmlns attribute
246 	 * occured.
247 	 */
248 	private void mapPrefixes(Map<String, String> lastPrefixName)
249 			throws DocumentException {
250 		NodeIterator iterator = ((DocumentTraversal) doc)
251 				.createNodeIterator(doc.getDocumentElement(),
252 						NodeFilter.SHOW_ELEMENT, null, false);
253 		for (Element actNode = (Element) iterator.nextNode(); actNode != null; actNode = (Element) iterator
254 				.nextNode()) {
255 			checkEMReserved(actNode);
256 			final boolean inEntity = DOMUtils.isInEntity(actNode);
257 			final String namespace = StringUtils.defaultString(actNode
258 			.getNamespaceURI());
259 			final String prefix = actNode.getPrefix();
260 			if (DOMUtils.equalsURI(namespace, null)) {
261 				addNode(actNode, inEntity);
262 			} else {
263 				if (processPrefix(namespace, prefix, lastPrefixName)) {
264 					addNode(actNode, inEntity);
265 				}
266 			}
267 			// remove the xmlns* attribute
268 			DOMMutils.removeXmlns(actNode);
269 			// check namespaces of all attributes
270 			final NamedNodeMap attrs = actNode.getAttributes();
271 			if (attrs != null) {
272 				for (int i = 0; i < attrs.getLength(); i++) {
273 					final Attr a = (Attr) attrs.item(i);
274 					checkEMReserved(a);
275 					// check if the attribute has same namespace as its owner
276 					// element
277 					if (DOMUtils.equalsURI(a.getNamespaceURI(), namespace))
278 						throw new DocumentException(
279 								Messages
280 										.getString(
281 												"ELEMENT_ATTR_NS_EQUALS_ERROR", DOMUtils.printQName(actNode), DOMUtils.printQName(a))); //$NON-NLS-1$
282 					// process this prefix only if it unifiable.
283 					if (isUnifiable(a)) {
284 						if (processPrefix(StringUtils.defaultString(a
285 						.getNamespaceURI()), a.getPrefix(),
286 								lastPrefixName)) {
287 							addNode(a, inEntity);
288 						}
289 					}
290 				}
291 			}
292 		}
293 	}
294 	/***
295 	 * Process pair of namespace;prefix. If this prefix is empty, equal to
296 	 * reserved XML or <code>emp</code> prefix or already defined, then it is
297 	 * not added to <code>mapNsToPrefix</code> and <code>mapPrefixToNs</code>.
298 	 * @param namespaceUri the namespace. Must not be empty string.
299 	 * @param prefix desired prefix.
300 	 * @param lastPrefixName if prefix is already used, then it is stored here
301 	 * for later processing.
302 	 * @return <code>true</code> if the prefix must be registered immediately,
303 	 * <code>false</code> if it will be registered in second pass.
304 	 */
305 	private boolean processPrefix(String namespaceUri, String prefix,
306 			Map<String, String> lastPrefixName) {
307 		// if we don't know this namespace yet, then remember its prefix.
308 		// empty namespace must be mapped to empty prefix, so there's no
309 		// need to remember the empty namespace;anything pair.
310 		if (!DOMUtils.equalsURI(namespaceUri, null)
311 				&& !DOMUtils.equalsURI(namespaceUri, ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getNamespaceURI())
312 				&& !mapNsToPrefix.containsKey(namespaceUri)
313 				&& !isReservedPrefix(prefix)) {
314 			// this works both for elements and attributes. With elements, we
315 			// can't map non-empty namespace to empty prefix, nor reserved
316 			// emp: prefix. With attributes, empty namespace prefix selects
317 			// its parent element's namespace, that is already processed.
318 			if ((!StringUtils.isEmpty(prefix))
319 					&& (!prefix.equals(ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getPrefix()))
320 					&& !isReservedPrefix(prefix)) {
321 				if (mapPrefixToNs.containsKey(prefix)) {
322 					// oops, this prefix is already used.
323 					// just remember it as 'wanted' namespace prefix. it will
324 					// be used later.
325 					lastPrefixName.put(namespaceUri, prefix);
326 				} else {
327 					return true;
328 				}
329 			}
330 		}
331 		return false;
332 	}
333 	/***
334 	 * Unifies the prefixes. First, it completes the prefix map and set, and
335 	 * then unifies prefixes by replacing them with one prefix for each
336 	 * namespace.
337 	 * @param lastPrefixName helps picking correct prefix name, if needed.
338 	 */
339 	private void unifyPrefixes(Map<String, String> lastPrefixName) {
340 		NodeIterator iterator = ((DocumentTraversal) doc)
341 				.createNodeIterator(doc.getDocumentElement(),
342 						NodeFilter.SHOW_ELEMENT
343 								| NodeFilter.SHOW_ENTITY_REFERENCE, null, false);
344 		for (Node actNode = iterator.nextNode(); actNode != null; actNode = iterator
345 				.nextNode()) {
346 			if (actNode.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
347 				addEntityNamespaces((EntityReference) actNode);
348 				continue;
349 			}
350 			// the node is an element, we may unify its prefix
351 			unifyPrefix(actNode, lastPrefixName);
352 			NamedNodeMap attrs = actNode.getAttributes();
353 			for (int i = 0; i < attrs.getLength(); i++) {
354 				Attr attr = (Attr) attrs.item(i);
355 				unifyPrefix(attr, lastPrefixName);
356 			}
357 		}
358 		iterator.detach();
359 	}
360 	/***
361 	 * Unifies prefix, and sets it to node, if required.
362 	 * @param node node to unify
363 	 * @param lastPrefixName helps picking correct prefix name, if needed.
364 	 */
365 	private void unifyPrefix(Node node, Map<String, String> lastPrefixName) {
366 		// check if this node isn't reserved XML node. If yes, then quit with
367 		// no modifications - its prefix can't be changed.
368 		if (!isUnifiable(node))
369 			return;
370 		final String namespace = StringUtils.defaultString(node.getNamespaceURI());
371 		if (!DOMUtils.equalsURI(namespace, ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getNamespaceURI())) {
372 			if (!DOMUtils.equalsURI(namespace, null)) {
373 				// the prefix, picked for this namespace. All nodes with this
374 				// namespace must have this prefix.
375 				String prefix = mapNsToPrefix.get(namespace);
376 				// we have non-null namespace, thus prefix must not be null
377 				if (prefix == null) {
378 					prefix = getAlternativePrefix(namespace, lastPrefixName);
379 				}
380 				if (!prefix.equals(node.getPrefix()))
381 					node.setPrefix(prefix);
382 			} else {
383 				String prefix = node.getPrefix();
384 				if (!StringUtils.isEmpty(prefix))
385 					node.setPrefix(null);
386 			}
387 			// register this node.
388 			addNode(node, DOMUtils.isInEntity(node));
389 		}
390 	}
391 	/***
392 	 * Registers a node into <code>mapNsToPrefix</code>,
393 	 * <code>mapPrefixToNs</code> and <code>allNamespaces</code>. Function
394 	 * fails if a namespace is already registered to a prefix and the prefix
395 	 * does not match.
396 	 * @param node node to register, element or attribute node only. Ignores
397 	 * child nodes.
398 	 * @param inEntity if <code>true</code> then the node is not registered in
399 	 * <code>mapNsToPrefix</code> and <code>mapPrefixToNs</code> maps.
400 	 */
401 	private void addNode(final Node node, final boolean inEntity) {
402 		final short nodeType = node.getNodeType();
403 		assert (nodeType == Node.ATTRIBUTE_NODE)
404 				|| (nodeType == Node.ELEMENT_NODE) : "Not an element/attribute node"; //$NON-NLS-1$
405 		final String ns = StringUtils.defaultString(node.getNamespaceURI());
406 		addMapping(inEntity, ns, node.getPrefix());
407 		// increase count of nodes from this namespace
408 		final NodeCount cnt = allNamespaces.get(ns);
409 		if (nodeType == Node.ELEMENT_NODE)
410 			cnt.elements++;
411 		else
412 			cnt.attributes++;
413 	}
414 	/***
415 	 * Registers a mapping into <code>mapNsToPrefix</code>,
416 	 * <code>mapPrefixToNs</code> and <code>allNamespaces</code>. Function
417 	 * fails if a namespace is already registered to a prefix and the prefix
418 	 * does not match.
419 	 * @param inEntity if <code>true</code> then the node is not registered in
420 	 * <code>mapNsToPrefix</code> and <code>mapPrefixToNs</code> maps.
421 	 * @param namespace the namespace
422 	 * @param prefix prefix.
423 	 */
424 	private void addMapping(final boolean inEntity, String namespace,
425 			String prefix) {
426 		namespace = StringUtils.defaultString(namespace);
427 		prefix = StringUtils.defaultString(prefix);
428 		if (!inEntity) {
429 			final String registeredPrefix = mapNsToPrefix.get(namespace);
430 			if (registeredPrefix != null) {
431 				// this namespace is already registered. perform sanity checks
432 				if (!registeredPrefix.equals(prefix))
433 					throw new IllegalArgumentException("New prefix '" + prefix //$NON-NLS-1$
434 							+ "' does not match registered prefix '" //$NON-NLS-1$
435 							+ registeredPrefix + "' for namespace [" //$NON-NLS-1$
436 							+ namespace + "]"); //$NON-NLS-1$
437 				assert mapPrefixToNs.get(registeredPrefix) != null : "mapPrefixToNs inconsistent with mapNsToPrefix"; //$NON-NLS-1$
438 				assert mapPrefixToNs.get(registeredPrefix).equals(namespace) : "mapPrefixToNs inconsistent with mapNsToPrefix"; //$NON-NLS-1$
439 			} else {
440 				// we need to register this prefix.
441 				if (DOMUtils.equalsURI(namespace, null) && !prefix.equals("")) //$NON-NLS-1$
442 					throw new IllegalArgumentException(
443 							"Null namespace must map to null prefix."); //$NON-NLS-1$
444 				if (!DOMUtils.equalsURI(namespace, null) && prefix.equals("")) //$NON-NLS-1$
445 					throw new IllegalArgumentException(
446 							"Non-null namespace must map to non-null prefix."); //$NON-NLS-1$
447 				assert !mapPrefixToNs.containsKey(prefix) : "mapPrefixToNs inconsistent with mapNsToPrefix"; //$NON-NLS-1$
448 				mapNsToPrefix.put(namespace, prefix);
449 				mapPrefixToNs.put(prefix, namespace);
450 			}
451 		}
452 		if (!allNamespaces.containsKey(namespace))
453 			allNamespaces.put(namespace, new NodeCount());
454 	}
455 	/***
456 	 * Unregisters a node from <code>mapNsToPrefix</code>,
457 	 * <code>mapPrefixToNs</code> and <code>allNamespaces</code>, but only
458 	 * if no more nodes with same namespace exists. Function fails if a
459 	 * namespace is not registered.
460 	 * @param node node to unregister, element or attribute node only. Ignores
461 	 * child nodes.
462 	 * @param inEntity if <code>true</code> then the node is not unregistered
463 	 * from <code>mapNsToPrefix</code> and <code>mapPrefixToNs</code> maps.
464 	 */
465 	private void removeNode(final Node node, final boolean inEntity) {
466 		final short nodeType = node.getNodeType();
467 		assert (nodeType == Node.ATTRIBUTE_NODE)
468 				|| (nodeType == Node.ELEMENT_NODE) : "Not an element/attribute node"; //$NON-NLS-1$
469 		final String ns = StringUtils.defaultString(node.getNamespaceURI());
470 		// decrease count of nodes from this namespace
471 		final NodeCount cnt = allNamespaces.get(ns);
472 		if (cnt == null)
473 			throw new IllegalArgumentException("Unregistered namespace [" + ns //$NON-NLS-1$
474 					+ "]"); //$NON-NLS-1$
475 		if (nodeType == Node.ELEMENT_NODE) {
476 			cnt.elements--;
477 			if (cnt.elements < 0)
478 				throw new IllegalArgumentException(
479 						"Removing unregistered element"); //$NON-NLS-1$
480 		} else {
481 			cnt.attributes--;
482 			if (cnt.attributes < 0)
483 				throw new IllegalArgumentException(
484 						"Removing unregistered attribute"); //$NON-NLS-1$
485 		}
486 		// if no nodes with this namespace exists, remove the mapping.
487 		if (cnt.getCount() == 0) {
488 			removeMapping(ns, inEntity);
489 		}
490 	}
491 	/***
492 	 * Removes given mapping from <code>mapNsToPrefix</code>,
493 	 * <code>mapPrefixToNs</code> and <code>allNamespaces</code>. Fails if
494 	 * the mapping is not yet registered.
495 	 * @param namespace the namespace to unregister.
496 	 * @param inEntity if <code>true</code> then the node is not unregistered
497 	 * from <code>mapNsToPrefix</code> and <code>mapPrefixToNs</code> maps.
498 	 */
499 	private void removeMapping(String namespace, final boolean inEntity) {
500 		namespace = StringUtils.defaultString(namespace);
501 		// remove nodecount info.
502 		final NodeCount cnt = allNamespaces.get(namespace);
503 		if (cnt == null)
504 			throw new IllegalArgumentException("Unknown namespace: [" //$NON-NLS-1$
505 					+ namespace + "]"); //$NON-NLS-1$
506 		if (cnt.getCount() != 0)
507 			throw new IllegalStateException("There are still some nodes for [" //$NON-NLS-1$
508 					+ namespace + "]"); //$NON-NLS-1$
509 		if (!inEntity) {
510 			// remove prefix-namespace mappings if necessary
511 			final String prefix = mapNsToPrefix.get(namespace);
512 			if (prefix != null) {
513 				assert mapPrefixToNs.get(prefix).equals(namespace) : "maps are inconsistent"; //$NON-NLS-1$
514 				mapNsToPrefix.remove(namespace);
515 				mapPrefixToNs.remove(prefix);
516 			}
517 		}
518 		allNamespaces.remove(namespace);
519 	}
520 	/***
521 	 * <p>
522 	 * Finds alternative prefix for given namespace. Alternate prefix is
523 	 * returned by these rules:
524 	 * <ol>
525 	 * <li>Prefix defined in the emConfig file, if defined.</li>
526 	 * <li>lastPrefixName, if defined.</li>
527 	 * <li>Last path element from uri path.</li>
528 	 * <li>The string <code>"nsp"</code>, meaning namespace prefix.</li>
529 	 * </ol>
530 	 * </p>
531 	 * <p>
532 	 * If returned prefix is already used, then some number is added to prefix
533 	 * name, until unique prefix name is found.
534 	 * </p>
535 	 * @param namespaceUri namespace, for which the prefix will be created. Must
536 	 * not be <code>null</code> nor empty string.
537 	 * @param lastPrefixName last used prefix name for this namespace. If
538 	 * <code>null</code>, second step is skipped.
539 	 * @return unique prefix, never <code>null</code> nor empty string.
540 	 */
541 	private String getAlternativePrefix(String namespaceUri,
542 			Map<String, String> lastPrefixName) {
543 		String result = NamespaceManager.getPreferredPrefix(namespaceUri);
544 		if (!isValidPrefix(result))
545 			result = lastPrefixName == null ? null : lastPrefixName
546 					.get(namespaceUri);
547 		if (!isValidPrefix(result))
548 			result = getLastPathElement(namespaceUri);
549 		if (!isValidPrefix(result))
550 			result = "nsp"; //$NON-NLS-1$
551 		// ok, we got prefix. Now, try to get unique prefix by adding some
552 		// number, if needed.
553 		if (!mapPrefixToNs.containsKey(result))
554 			return result;
555 		int additionalNumber = 2;
556 		String newPrefix;
557 		do {
558 			newPrefix = result + Integer.toString(additionalNumber++);
559 		} while (mapPrefixToNs.containsKey(newPrefix));
560 		return newPrefix;
561 	}
562 	/***
563 	 * Checks whether the given node can be subject to prefix unification. If
564 	 * node must not be unified then it must NOT require its namespace to be
565 	 * defined. Currently these prefixes are not unifiable: xml reserved
566 	 * prefixes starting with the string <code>xml</code> and <code>emp</code>
567 	 * prefix.
568 	 * @param node node to check.
569 	 * @return true if this node prefix-namespace pair can be unified.
570 	 */
571 	private boolean isUnifiable(Node node) {
572 		if (isReserved(node))
573 			return false;
574 		if (DOMUtils.equalsURI(node.getNamespaceURI(), ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getNamespaceURI()))
575 			return false;
576 		return true;
577 	}
578 	/***
579 	 * Checks, if given prefix name may be placed safely into the xml file. It
580 	 * doesn't check if this prefix is already used, it just check that it isn't
581 	 * reserved <code>emp:</code> prefix, and it doesn't start with reserved
582 	 * <code>xml</code> prefix.
583 	 * @param prefix prefix to check.
584 	 * @return true if this prefix is assignable to a normal node.
585 	 */
586 	private boolean isValidPrefix(String prefix) {
587 		if (prefix == null)
588 			return false;
589 		if (prefix.equals(ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getPrefix()))
590 			return false;
591 		return !isReservedPrefix(prefix);
592 	}
593 	/***
594 	 * Checks, if given prefix name is reserved for XML use.
595 	 * @param prefix prefix to check.
596 	 * @return true if prefix is reserved.
597 	 */
598 	private boolean isReservedPrefix(String prefix) {
599 		if (prefix == null)
600 			return false;
601 		if (prefix.length() < 3)
602 			return false;
603 		if (prefix.substring(0, 3).equalsIgnoreCase(XMLConstants.XML_NS_PREFIX))
604 			return true;
605 		return false;
606 	}
607 	/***
608 	 * Checks, if given node is reserved for XML use.
609 	 * @param node node to check.
610 	 * @return true if this prefix is syntactically placeable into xml.
611 	 */
612 	private boolean isReserved(Node node) {
613 		// only attributes forms a reserved node.
614 		if (node.getNodeType() != Node.ATTRIBUTE_NODE)
615 			return false;
616 		return isReservedPrefix(node.getPrefix())
617 				|| DOMUtils.isXmlns((Attr)node);
618 	}
619 	/***
620 	 * Checks if given node namespace is reserved by EuroMath2.
621 	 * @param node node to check.
622 	 * @return <code>true</code> if namespace is reserved and can not be used
623 	 * by users.
624 	 */
625 	private boolean isEMReserved(Node node) {
626 		if (node.getNamespaceURI() == null)
627 			return false;
628 		return DOMUtils.equalsURI(node.getNamespaceURI(),ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getNamespaceURI());
629 	}
630 	/***
631 	 * Checks if given node namespace is reserved by EuroMath2.
632 	 * @param node node to check
633 	 * @throws DocumentException if reserved node is found. <code>emp:id</code>
634 	 * is ignored - no exception is thrown.
635 	 */
636 	private void checkEMReserved(Node node) throws DocumentException {
637 		if (DOMUtils.equalsNodeQName(node, ExportUtils.GENE_ID_ATTRIBUTE_QNAME))
638 			return;
639 		if (isEMReserved(node)) {
640 			throw new DocumentException(
641 					Messages
642 							.getString(
643 									"EM_RESERVED_NAMESPACE_FOUND", node.getNodeName(), node.getNamespaceURI())); //$NON-NLS-1$
644 		}
645 	}
646 	/***
647 	 * Returns last longest path element, that doesn't contain whitespace, nor
648 	 * one of the following: <code>: / &gt; &lt; ;</code>.
649 	 * @param uri uri to parse.
650 	 * @return last path element, or <code>null</code> if it doesn't exist.
651 	 */
652 	private String getLastPathElement(String uri) {
653 		StringTokenizer i = new StringTokenizer(uri, "\t\n\r\f/:<>;"); //$NON-NLS-1$
654 		String result = null;
655 		while (i.hasMoreTokens()) {
656 			result = i.nextToken();
657 		}
658 		return result;
659 	}
660 	/***
661 	 * For each used namespace, except empty namespace, creates xmlns mapping.
662 	 * Erases all other xmlns definitions.
663 	 * @param e element, where the <code>xmlns</code> attributes will be
664 	 * re-created.
665 	 */
666 	public void createXmlnsAttributes(Element e) {
667 		DOMMutils.removeXmlns(e);
668 		createXmlnsAttributes(e, mapNsToPrefix.keySet());
669 	}
670 	/***
671 	 * Adds and/or replaces xmlns mapping for all given namespaces in given
672 	 * element.
673 	 * @param e the element.
674 	 * @param namespaces Set of Strings. All these namespaces' mappings are
675 	 * added to element.
676 	 */
677 	public void createXmlnsAttributes(Element e,
678 			Set< ? extends String> namespaces) {
679 		for (String namespace : namespaces) {
680 			Attr attr = createXmlnsAttribute(e, namespace);
681 			if (attr != null)
682 				e.setAttributeNodeNS(attr);
683 		}
684 	}
685 	/***
686 	 * <p>
687 	 * Returns all namespaces known to this manager. Returned set cannot be
688 	 * modified in any way. <code>null</code> namespace is represented as
689 	 * empty string (<code>""</code>), and always maps to empty (
690 	 * <code>""</code>) namespace. It never contains the EuroMath namespace,
691 	 * xmlns namespace nor xml namespace.
692 	 * </p>
693 	 * <p>
694 	 * All mappings of namespaces no longer present in the document are removed,
695 	 * but only for root manager.
696 	 * </p>
697 	 * @return all namespaces in document.
698 	 */
699 	public Set<String> getAllNamespaces() {
700 		if (parent == null)
701 			cleanMappings();
702 		return getAllNamespacesInt();
703 	}
704 	/***
705 	 * Returns namespaces of all elements present in the document.Returned set
706 	 * cannot be modified in any way. <code>null</code> namespace is
707 	 * represented as empty string (<code>""</code>), and always maps to
708 	 * empty ( <code>""</code>) namespace. It never contains the EuroMath
709 	 * namespace, xmlns namespace nor xml namespace.
710 	 * </p>
711 	 * <p>
712 	 * All mappings of namespaces no longer present in the document are removed.
713 	 * </p>
714 	 * @return set of all namespaces present in elements.
715 	 */
716 	public Set<String> getElementNamespaces() {
717 		cleanMappings();
718 		final Set<String> result = new HashSet<String>();
719 		if (parent != null)
720 			result.addAll(parent.getElementNamespaces());
721 		// walk through all namespaces and add only those present in elements.
722 		for (final String namespace : allNamespaces.keySet()) {
723 			final NodeCount nc = allNamespaces.get(namespace);
724 			if (nc.elements != 0)
725 				result.add(namespace);
726 		}
727 		return result;
728 	}
729 	/***
730 	 * Returns all namespaces known to this manager. Returned set cannot be
731 	 * modified in any way. <code>null</code> namespace is represented as
732 	 * empty string (<code>""</code>), and always maps to empty (
733 	 * <code>""</code>) namespace. It never contains the EuroMath namespace,
734 	 * null namespace, xmlns namespace nor xml namespace.
735 	 * @return all namespaces in document.
736 	 */
737 	private Set<String> getAllNamespacesInt() {
738 		if (parent == null)
739 			return Collections.unmodifiableSet(allNamespaces.keySet());
740 		final Set<String> result = new HashSet<String>(parent
741 				.getAllNamespaces());
742 		result.addAll(allNamespaces.keySet());
743 		return Collections.unmodifiableSet(result);
744 	}
745 	/***
746 	 * <p>
747 	 * Returns counts of nodes for each namespace. Clones internal map,
748 	 * therefore the result may be cached when used multiple times.
749 	 * </p>
750 	 * <p>
751 	 * All mappings of namespaces no longer present in the document are removed.
752 	 * </p>
753 	 * @return maps namespace URI to a count of nodes with given namespace.
754 	 * <code>null</code> namespace is represented as an empty string.
755 	 */
756 	public Map<String, NodeCount> getCounts() {
757 		cleanMappings();
758 		final Map<String, NodeCount> result;
759 		if (parent == null) {
760 			result = new HashMap<String, NodeCount>(allNamespaces.size());
761 		} else {
762 			result = parent.getCounts();
763 		}
764 		// clone all NodeCount, just to be sure.
765 		for (final String ns : allNamespaces.keySet()) {
766 			if (!result.containsKey(ns)) {
767 				final NodeCount cnt = allNamespaces.get(ns);
768 				result.put(ns, new NodeCount(cnt));
769 			}
770 		}
771 		return result;
772 	}
773 	/***
774 	 * Removes all mappings of namespaces not present in the document. Cleans
775 	 * only mappings registered in this instance of manager, it does not clean
776 	 * parent manager.
777 	 */
778 	public void cleanMappings() {
779 		// get the obsolete mappings
780 		final Set<String> obsolete = new HashSet<String>();
781 		for (final String ns : allNamespaces.keySet()) {
782 			final NodeCount cnt = allNamespaces.get(ns);
783 			if (cnt.getCount() == 0)
784 				obsolete.add(ns);
785 		}
786 		// remove all obsolete mappings
787 		for (final String ns : obsolete) {
788 			removeMapping(ns, false);
789 		}
790 	}
791 	/***
792 	 * <p>
793 	 * When creating new element/attribute from another namespace as their
794 	 * parent, we can specify new prefix. This function returns the best
795 	 * suggestion of new prefix.
796 	 * </p>
797 	 * <p>
798 	 * However, you can ask for every element/attribute's prefix, but there are
799 	 * cases, when this prefix is not changeable.
800 	 * </p>
801 	 * <p>
802 	 * Warning: when creating attributes with same namespace as their owner
803 	 * element (local namespaces), their prefix AND namespace MUST be null.
804 	 * </p>
805 	 * @param namespaceUri namespace of new nametree being created.
806 	 * @return best prefix name. If no prefix is the best choice, then
807 	 * <code>null</code> is returned.
808 	 */
809 	public String getBestPrefix(String namespaceUri) {
810 		if (StringUtils.isEmpty(namespaceUri))
811 			return null;
812 		if (DOMUtils.equalsURI(namespaceUri, XMLConstants.XML_NS_URI))
813 			return XMLConstants.XML_NS_PREFIX;
814 		String result = getPrefix(namespaceUri);
815 		if (result != null)
816 			return result;
817 		return getAlternativePrefix(namespaceUri, null);
818 	}
819 	/***
820 	 * Checks, if the prefix, returned by the <code>getBestPrefix()</code>
821 	 * function, is only the best choice for new prefix, or if returned prefix
822 	 * MUST be used.
823 	 * @param namespaceUri namespace to check.
824 	 * @return false, if user can choose virtually any prefix, that is accepted
825 	 * by the <code>acceptsNewPrefix()</code> function. True, if only one
826 	 * prefix can be used.
827 	 */
828 	public boolean isBestPrefixRequired(String namespaceUri) {
829 		if (StringUtils.isEmpty(namespaceUri))
830 			return true;
831 		if (DOMUtils.equalsURI(namespaceUri, XMLConstants.XML_NS_URI))
832 			return true;
833 		return getPrefix(namespaceUri) != null;
834 	}
835 	/***
836 	 * When creating new element/attribute, check, whether chosen prefix can be
837 	 * used.
838 	 * @param namespaceUri namespace.
839 	 * @param newPrefix desired prefix. When no prefix is desired,
840 	 * <code>null</code> must be specified. Only <code>null</code> namespace
841 	 * may have <code>null</code> prefix.
842 	 * @return true, if this prefix can be set to new element/attribute, false
843 	 * if not. The function will not check if the prefix contains illegal
844 	 * characters, it merely checks if there is no other registered prefix with
845 	 * same name.
846 	 */
847 	public boolean acceptsNewPrefix(String namespaceUri, String newPrefix) {
848 		if (StringUtils.isEmpty(namespaceUri))
849 			return StringUtils.isEmpty(newPrefix);
850 		if (StringUtils.isEmpty(newPrefix))
851 			return false;
852 		if (DOMUtils.equalsURI(namespaceUri, ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getNamespaceURI()))
853 			return false;
854 		if (DOMUtils.equalsURI(namespaceUri, XMLConstants.XML_NS_URI))
855 			return XMLConstants.XML_NS_PREFIX.equals(newPrefix);
856 		// check if this namespace already exists. If yes then we must use the
857 		// prefix already defined.
858 		String prefix = getPrefix(namespaceUri);
859 		if (prefix != null)
860 			return prefix.equals(newPrefix);
861 		// check for presence of given prefix. If a prefix is already bound to
862 		// some namespace, it cannot be used.
863 		String ns = getNamespace(newPrefix);
864 		if (ns != null)
865 			return false;
866 		return true;
867 	}
868 	/***
869 	 * Creates an <code>xmlns</code> attribute, that maps known namespace to
870 	 * registered prefix. EuroMath namespace is always mapped to
871 	 * <code>emp:</code> prefix. When an empty namespace is given, function
872 	 * returns <code>null</code>: empty namespace is always mapped to empty
873 	 * prefix, and thus need not to be declared by the <code>xmlns:</code>
874 	 * attribute. When an XML namespace is given then <code>null</code> is
875 	 * returned aswell.
876 	 * @param e element, where to create element.
877 	 * @param namespaceUri creates xmlns mapping for this namespace.
878 	 * @return xmlns attribute node, or <code>null</code> if no prefix is
879 	 * currently mapped to given namespace.
880 	 */
881 	public Attr createXmlnsAttribute(Element e, String namespaceUri) {
882 		if (StringUtils.isEmpty(namespaceUri))
883 			return null;
884 		if (DOMUtils.equalsURI(namespaceUri, XMLConstants.XMLNS_ATTRIBUTE_NS_URI))
885 			return null;
886 		if (DOMUtils.equalsURI(namespaceUri, XMLConstants.XML_NS_URI))
887 			return null;
888 		final String prefix = getPrefix(namespaceUri);
889 		if (prefix == null)
890 			return null;
891 		final Attr result = DOMMutils.createXmlnsAttribute(e,namespaceUri,prefix);
892 		return result;
893 	}
894 	/***
895 	 * Returns prefix, that is currently mapped to given namespace. If no prefix
896 	 * is currently mapped, function returns <code>null</code>. GENE-ID
897 	 * namespace is always mapped to <code>gene:</code> prefix. Empty or
898 	 * <code>null</code> namespace ("") is always mapped to empty prefix ("").
899 	 * XML namespace is always mapped to <code>xml</code> prefix.
900 	 * @param namespaceUri namespace.
901 	 * @return prefix, that maps to given namespace. If no prefix is currently
902 	 * known for given namespace, it return <code>null</code>.
903 	 */
904 	public String getPrefix(String namespaceUri) {
905 		if (DOMUtils.equalsURI(ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getNamespaceURI(), namespaceUri) )
906 			return ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getPrefix();
907 		if (StringUtils.isEmpty(namespaceUri))
908 			return ""; //$NON-NLS-1$
909 		if (DOMUtils.equalsURI(namespaceUri, XMLConstants.XML_NS_URI))
910 			return XMLConstants.XML_NS_PREFIX;
911 		if (parent != null) {
912 			final String result = parent.getPrefix(namespaceUri);
913 			if (result != null)
914 				return result;
915 		}
916 		return mapNsToPrefix.get(namespaceUri);
917 	}
918 	/***
919 	 * Returns namespace, that is currently mapped to given prefix. If no
920 	 * namespace is currently mapped, function returns <code>null</code>.
921 	 * EuroMath namespace is always mapped to <code>emp:</code> prefix. Empty
922 	 * or <code>null</code> prefix is always mapped to empty namespace ("").
923 	 * @param prefix the prefix.
924 	 * @return namespace, that maps to given prefix. If no namespace is
925 	 * currently known for given prefix, it returns <code>null</code>.
926 	 */
927 	public String getNamespace(String prefix) {
928 		if (StringUtils.isEmpty(prefix))
929 			return ""; //$NON-NLS-1$
930 		if (prefix.equals(ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getPrefix()))
931 			return ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getNamespaceURI();
932 		if (parent != null) {
933 			final String result = parent.getNamespace(prefix);
934 			if (result != null)
935 				return result;
936 		}
937 		return mapPrefixToNs.get(StringUtils.defaultString(prefix));
938 	}
939 	/***
940 	 * Checks, if given namespace is <strong>defined</strong> in given element.
941 	 * For empty namespace always returns true.
942 	 * @param namespaceUri namespace
943 	 * @param e element
944 	 * @return <code>true</code> if this namespace is defined in given element
945 	 * with the <code>xmlns:</code> attribute, or if a prefix is registered.
946 	 */
947 	public boolean isDefined(String namespaceUri, Element e) {
948 		if (StringUtils.isEmpty(namespaceUri))
949 			return true;
950 		final String prefix = getPrefix(namespaceUri);
951 		if (prefix == null)
952 			return false;
953 		return DOMUtils.getXmlnsNode(prefix, e)!=null;
954 	}
955 	/***
956 	 * Declares the namespace-prefix pair. This operation is not applicable to a
957 	 * default namespace manager - it is updated automatically when the document
958 	 * is modified. Neither the namespace, nor the prefix can exist in a parent.
959 	 * @param namespaceUri the namespace.
960 	 * @param prefix the prefix.
961 	 * @throws IllegalArgumentException if such prefix-namespace pair cannot be
962 	 * registered.
963 	 */
964 	public void declare(String namespaceUri, String prefix) {
965 		if (parent == null)
966 			throw new IllegalStateException(
967 					"This operation is not applicable to default namespace manager."); //$NON-NLS-1$
968 		if (StringUtils.isEmpty(namespaceUri)) {
969 			if (!StringUtils.isEmpty(prefix))
970 				throw new IllegalArgumentException(
971 						"Prefix name must be null when namespace is null."); //$NON-NLS-1$
972 			addMapping(false, namespaceUri, prefix);
973 		}
974 		if (StringUtils.isEmpty(prefix))
975 			throw new IllegalArgumentException("Invalid prefix value."); //$NON-NLS-1$
976 		if (parent != null) {
977 			if (parent.getPrefix(namespaceUri) != null)
978 				throw new IllegalArgumentException(
979 						"Namespace is already declared in parent."); //$NON-NLS-1$
980 			if (parent.getNamespace(prefix) != null)
981 				throw new IllegalArgumentException(
982 						"Prefix is already declared in parent."); //$NON-NLS-1$
983 		}
984 		addMapping(false, namespaceUri, prefix);
985 	}
986 	/***
987 	 * Undeclares given namespace. If the namespace is not declared in this
988 	 * manager then the function will fail - we cannot modify parent.
989 	 * @param namespaceUri the namespace part of pair.
990 	 */
991 	public void undeclareNamespace(String namespaceUri) {
992 		if (parent == null)
993 			throw new IllegalStateException(
994 					"This operation is not applicable to default namespace manager."); //$NON-NLS-1$
995 		if (StringUtils.isEmpty(namespaceUri))
996 			throw new IllegalArgumentException(
997 					"Null namespace cannot be undeclared."); //$NON-NLS-1$
998 		String prefix = mapNsToPrefix.get(namespaceUri);
999 		if (prefix == null)
1000 			throw new IllegalArgumentException(
1001 					"The namespace is not defined in this manager."); //$NON-NLS-1$
1002 		removeMapping(namespaceUri, false);
1003 	}
1004 	/*
1005 	 * (non-Javadoc)
1006 	 * @see java.lang.Object#toString()
1007 	 */
1008 	@Override
1009 	public String toString() {
1010 		final StringBuilder builder = new StringBuilder();
1011 		for (String ns : getAllNamespacesInt()) {
1012 			final String prefix = getPrefix(ns);
1013 			builder.append("[" + prefix + "=" + ns + "] "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1014 		}
1015 		return builder.toString();
1016 	}
1017 	/***
1018 	 * Listens for changes in the document.
1019 	 * @author Martin Vysny
1020 	 */
1021 	private final class ModifierListener implements EventListener {
1022 		
1023 		/* (non-Javadoc)
1024 		 * @see org.w3c.dom.events.EventListener#handleEvent(org.w3c.dom.events.Event)
1025 		 */
1026 		public void handleEvent(Event evt) {
1027 			final EventTypesEnum eventType = EventTypesEnum.fromEvent(evt);
1028 			if (eventType.getEventClass() != EventClassEnum.Mutation)
1029 				return;
1030 			final MutationEvent e = (MutationEvent) evt;
1031 			if (EventClassEnum.Mutation.isAddition(e)) {
1032 				nodeAdded(EventClassEnum.Mutation.getMutatedNode(e));
1033 				return;
1034 			}
1035 			if (EventClassEnum.Mutation.isModification(e)) {
1036 				nodeModified(e);
1037 				return;
1038 			}
1039 			if (EventClassEnum.Mutation.isRemoval(e)) {
1040 				nodeDelete(EventClassEnum.Mutation.getMutatedNode(e));
1041 				return;
1042 			}
1043 		}
1044 		private void nodeAdded(Node node) {
1045 			// This function must not be called directly!
1046 			if (parent != null)
1047 				throw new IllegalStateException(
1048 						"This method can be called on main manager only"); //$NON-NLS-1$
1049 			if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
1050 				final boolean inEntity = DOMUtils.isInEntity(((Attr) node)
1051 						.getOwnerElement());
1052 				attributeAdded((Attr) node, inEntity);
1053 				return;
1054 			}
1055 			if ((node.getNodeType() != Node.ELEMENT_NODE)
1056 					&& (node.getNodeType() != Node.ENTITY_REFERENCE_NODE))
1057 				return;
1058 			// walk through the node and its descendants and search for new
1059 			// namespaces
1060 			NodeIterator iterator = ((DocumentTraversal) doc)
1061 					.createNodeIterator(node, NodeFilter.SHOW_ELEMENT
1062 							| NodeFilter.SHOW_ENTITY_REFERENCE, null, false);
1063 			for (Node e = iterator.nextNode(); e != null; e = iterator
1064 					.nextNode()) {
1065 				if (e.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
1066 					addEntityNamespaces((EntityReference) e);
1067 					continue;
1068 				}
1069 				final boolean inEntity = DOMUtils.isInEntity(e);
1070 				final String ns = StringUtils.defaultString(e.getNamespaceURI());
1071 				final String validPrefix = mapNsToPrefix.get(ns);
1072 				if (validPrefix != null) {
1073 					// namespace is known. check the prefix
1074 					if (!validPrefix.equals(e.getPrefix()))
1075 						throw new IllegalArgumentException("Element " + e //$NON-NLS-1$
1076 								+ " must have prefix " + validPrefix); //$NON-NLS-1$
1077 				} else {
1078 					// namespace is unknown, register it, but check it first.
1079 					if (isBestPrefixRequired(ns)) {
1080 						String forcedPrefix = getBestPrefix(ns);
1081 						if (!forcedPrefix.equals(e.getPrefix()))
1082 							throw new IllegalArgumentException("Element " + e //$NON-NLS-1$
1083 									+ " must have prefix " + forcedPrefix); //$NON-NLS-1$
1084 					}
1085 					addNode(e, inEntity);
1086 				}
1087 				// check attributes
1088 				NamedNodeMap attrs = e.getAttributes();
1089 				if (attrs != null) {
1090 					for (int i = 0; i < attrs.getLength(); i++) {
1091 						Attr attr = (Attr) attrs.item(i);
1092 						attributeAdded(attr, inEntity);
1093 					}
1094 				}
1095 			}
1096 			iterator.detach();
1097 		}
1098 		/***
1099 		 * Informs that the attribute was added.
1100 		 * @param attr the attribute that was added.
1101 		 * @param inEntity if <code>true</code> then owner element resides in
1102 		 * an entity.
1103 		 */
1104 		private void attributeAdded(final Attr attr, final boolean inEntity) {
1105 			// em2 reserved attributes are not managed
1106 			if (DOMUtils.equalsURI(attr.getNamespaceURI(), ExportUtils.GENE_ID_ATTRIBUTE_QNAME.getNamespaceURI())
1107 					|| isReserved(attr))
1108 				return;
1109 			addNode(attr, inEntity);
1110 		}
1111 		private void nodeDelete(Node node) {
1112 			if (parent != null)
1113 				throw new IllegalStateException(
1114 						"This method can be called on main manager only"); //$NON-NLS-1$
1115 			// update node count.
1116 			nodeDeleteInt(node, DOMUtils.isInEntity(node));
1117 		}
1118 		private void nodeModified(MutationEvent e) {
1119 			if (parent != null)
1120 				throw new IllegalStateException(
1121 						"This method can be called on main manager only"); //$NON-NLS-1$
1122 			// do nothing - node modification cannot change namespace
1123 		}
1124 	}
1125 	/***
1126 	 * The modeifier listener instance.
1127 	 */
1128 	final EventListener modifierListener = new ModifierListener();
1129 	/***
1130 	 * Walks through the descendants of the entity and adds all namespaces found
1131 	 * in the entity. The entity is not modified - prefixes are not unified.
1132 	 * @param eRef the entity to check.
1133 	 */
1134 	private void addEntityNamespaces(EntityReference eRef) {
1135 		for (Node child = eRef.getFirstChild(); child != null; child = child
1136 				.getNextSibling()) {
1137 			if (child.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
1138 				addEntityNamespaces((EntityReference) child);
1139 				continue;
1140 			}
1141 			if (child.getNodeType() != Node.ELEMENT_NODE)
1142 				continue;
1143 			// the node is an element
1144 			// check the element itself
1145 			NodeIterator iterator = ((DocumentTraversal) doc)
1146 					.createNodeIterator(child, NodeFilter.SHOW_ELEMENT
1147 							| NodeFilter.SHOW_ENTITY_REFERENCE, null, false);
1148 			for (Node e = iterator.nextNode(); e != null; e = iterator
1149 					.nextNode()) {
1150 				if (e.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
1151 					addEntityNamespaces((EntityReference) e);
1152 					continue;
1153 				}
1154 				// the node is an element
1155 				if (isUnifiable(e)) {
1156 					addNode(e, true);
1157 				}
1158 				// check attributes
1159 				NamedNodeMap attrs = e.getAttributes();
1160 				if (attrs != null) {
1161 					for (int i = 0; i < attrs.getLength(); i++) {
1162 						Attr attr = (Attr) attrs.item(i);
1163 						if (!StringUtils.isEmpty(attr.getNamespaceURI())
1164 								&& isUnifiable(attr)) {
1165 							addNode(attr, true);
1166 						}
1167 					}
1168 				}
1169 			}
1170 			iterator.detach();
1171 		}
1172 	}
1173 	/***
1174 	 * Calls {@link #removeNode(Node, boolean)} on each descendant node,
1175 	 * including given node.
1176 	 * @param node node to unmap
1177 	 * @param inEntity if <code>true</code> then the node resides in an
1178 	 * entity.
1179 	 */
1180 	private void nodeDeleteInt(final Node node, final boolean inEntity) {
1181 		switch (node.getNodeType()) {
1182 			case Node.ENTITY_REFERENCE_NODE: {
1183 				for (Node child = node.getFirstChild(); child != null; child = child
1184 						.getNextSibling()) {
1185 					nodeDeleteInt(child, true);
1186 				}
1187 			}
1188 				break;
1189 			case Node.ATTRIBUTE_NODE: {
1190 				if (isUnifiable(node))
1191 					removeNode(node, inEntity);
1192 			}
1193 				break;
1194 			case Node.ELEMENT_NODE: {
1195 				if (isUnifiable(node))
1196 					removeNode(node, inEntity);
1197 				for (Node child = node.getFirstChild(); child != null; child = child
1198 						.getNextSibling()) {
1199 					nodeDeleteInt(child, inEntity);
1200 				}
1201 				final NamedNodeMap atts = node.getAttributes();
1202 				if (atts != null) {
1203 					for (int i = 0; i < atts.getLength(); i++) {
1204 						nodeDeleteInt(atts.item(i), inEntity);
1205 					}
1206 				}
1207 			}
1208 				break;
1209 		}
1210 	}
1211 	/* (non-Javadoc)
1212 	 * @see sk.baka.ikslibs.INodeObserver#observe(org.w3c.dom.Node)
1213 	 */
1214 	public void observe(Node node) {
1215 		final EventTarget et = (EventTarget) node;
1216 		EventTypesEnum.addEventListenerTo(et, modifierListener, true, EnumSet.of(
1217 				EventTypesEnum.MAttrModified,
1218 				EventTypesEnum.MCharacterDataModified,
1219 				EventTypesEnum.MNodeInserted, EventTypesEnum.MNodeRemoved));
1220 	}
1221 	/* (non-Javadoc)
1222 	 * @see sk.baka.ikslibs.INodeObserver#stopObservation(org.w3c.dom.Node)
1223 	 */
1224 	public void stopObservation(Node node) {
1225 		final EventTarget et = (EventTarget) node;
1226 		EventTypesEnum.removeEventListenerFrom(et, modifierListener, true, EnumSet.of(
1227 				EventTypesEnum.MAttrModified,
1228 				EventTypesEnum.MCharacterDataModified,
1229 				EventTypesEnum.MNodeInserted, EventTypesEnum.MNodeRemoved));
1230 	}
1231 	
1232 }