1
2
3
4
5
6
7
8
9
10
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
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
154
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
167
168
169 @Override
170 public int hashCode() {
171 return attributes * 2407 + elements;
172 }
173
174
175
176
177 @Override
178 public String toString() {
179 return "e#" + elements + ";a#" + attributes;
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
205
206 if (doc.getDocument().getDocumentElement() != null) {
207 Map<String, String> lastPrefixName = new HashMap<String, String>();
208 try {
209
210
211
212 mapPrefixes(lastPrefixName);
213
214
215
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
260 DOMMutils.removeXmlns(actNode);
261
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
268
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)));
274
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
300
301
302 if (!DOMUtils.equalsURI(namespaceUri, null)
303 && !DOMUtils.equalsURI(namespaceUri, Const.EM_URI)
304 && !mapNsToPrefix.containsKey(namespaceUri)
305 && !isReservedPrefix(prefix)) {
306
307
308
309
310 if ((StringTools.nullStr(prefix) != null)
311 && (!prefix.equals(Const.EM_URI_PREFIX))
312 && !isReservedPrefix(prefix)) {
313 if (mapPrefixToNs.containsKey(prefix)) {
314
315
316
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
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
359
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
366
367 String prefix = mapNsToPrefix.get(namespace);
368
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
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";
397 final String ns = StringTools.nonNullStr(node.getNamespaceURI());
398 addMapping(inEntity, ns, node.getPrefix());
399
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
424 if (!registeredPrefix.equals(prefix))
425 throw new IllegalArgumentException("New prefix '" + prefix
426 + "' does not match registered prefix '"
427 + registeredPrefix + "' for namespace ["
428 + namespace + "]");
429 assert mapPrefixToNs.get(registeredPrefix) != null : "mapPrefixToNs inconsistent with mapNsToPrefix";
430 assert mapPrefixToNs.get(registeredPrefix).equals(namespace) : "mapPrefixToNs inconsistent with mapNsToPrefix";
431 } else {
432
433 if (DOMUtils.equalsURI(namespace, null) && !prefix.equals(""))
434 throw new IllegalArgumentException(
435 "Null namespace must map to null prefix.");
436 if (!DOMUtils.equalsURI(namespace, null) && prefix.equals(""))
437 throw new IllegalArgumentException(
438 "Non-null namespace must map to non-null prefix.");
439 assert !mapPrefixToNs.containsKey(prefix) : "mapPrefixToNs inconsistent with mapNsToPrefix";
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";
461 final String ns = StringTools.nonNullStr(node.getNamespaceURI());
462
463 final NodeCount cnt = allNamespaces.get(ns);
464 if (cnt == null)
465 throw new IllegalArgumentException("Unregistered namespace [" + ns
466 + "]");
467 if (nodeType == Node.ELEMENT_NODE) {
468 cnt.elements--;
469 if (cnt.elements < 0)
470 throw new IllegalArgumentException(
471 "Removing unregistered element");
472 } else {
473 cnt.attributes--;
474 if (cnt.attributes < 0)
475 throw new IllegalArgumentException(
476 "Removing unregistered attribute");
477 }
478
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
494 final NodeCount cnt = allNamespaces.get(namespace);
495 if (cnt == null)
496 throw new IllegalArgumentException("Unknown namespace: ["
497 + namespace + "]");
498 if (cnt.getCount() != 0)
499 throw new IllegalStateException("There are still some nodes for ["
500 + namespace + "]");
501 if (!inEntity) {
502
503 final String prefix = mapNsToPrefix.get(namespace);
504 if (prefix != null) {
505 assert mapPrefixToNs.get(prefix).equals(namespace) : "maps are inconsistent";
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";
543
544
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
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()));
636 }
637 }
638 /***
639 * Returns last longest path element, that doesn't contain whitespace, nor
640 * one of the following: <code>: / > < ;</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/:<>;");
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
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
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
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
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
849
850 String prefix = getPrefix(namespaceUri);
851 if (prefix != null)
852 return prefix.equals(newPrefix);
853
854
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 "";
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 "";
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.");
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.");
964 addMapping(false, namespaceUri, prefix);
965 }
966 if (StringTools.nullStr(prefix) == null)
967 throw new IllegalArgumentException("Invalid prefix value.");
968 if (parent != null) {
969 if (parent.getPrefix(namespaceUri) != null)
970 throw new IllegalArgumentException(
971 "Namespace is already declared in parent.");
972 if (parent.getNamespace(prefix) != null)
973 throw new IllegalArgumentException(
974 "Prefix is already declared in parent.");
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.");
987 if (StringTools.nullStr(namespaceUri) == null)
988 throw new IllegalArgumentException(
989 "Null namespace cannot be undeclared.");
990 String prefix = mapNsToPrefix.get(namespaceUri);
991 if (prefix == null)
992 throw new IllegalArgumentException(
993 "The namespace is not defined in this manager.");
994 removeMapping(namespaceUri, false);
995 }
996
997
998
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 + "] ");
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
1016
1017
1018
1019 public void nodeAdded(Node node, boolean changesIdLevel) {
1020
1021 if (parent != null)
1022 throw new IllegalStateException(
1023 "This method can be called on main manager only");
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
1034
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
1049 if (!validPrefix.equals(e.getPrefix()))
1050 throw new IllegalArgumentException("Element " + e
1051 + " must have prefix " + validPrefix);
1052 } else {
1053
1054 if (isBestPrefixRequired(ns)) {
1055 String forcedPrefix = getBestPrefix(ns);
1056 if (!forcedPrefix.equals(e.getPrefix()))
1057 throw new IllegalArgumentException("Element " + e
1058 + " must have prefix " + forcedPrefix);
1059 }
1060 addNode(e, inEntity);
1061 }
1062
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
1081 if (DOMUtils.equalsURI(attr.getNamespaceURI(), Const.EM_URI)
1082 || isReserved(attr))
1083 return;
1084 addNode(attr, inEntity);
1085 }
1086
1087
1088
1089
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");
1095
1096 nodeDeleteInt(node, DOMUtils.isInEntity(node));
1097 }
1098
1099
1100
1101
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");
1107
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
1129
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
1140 if (isUnifiable(e)) {
1141 addNode(e, true);
1142 }
1143
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 }