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