001: /*
002: * Copyright 1999-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: NamespaceSupport2.java,v 1.9 2004/02/17 04:21:14 minchau Exp $
018: */
019: package org.apache.xml.utils;
020:
021: import java.util.EmptyStackException;
022: import java.util.Enumeration;
023: import java.util.Hashtable;
024: import java.util.Vector;
025:
026: /**
027: * Encapsulate Namespace tracking logic for use by SAX drivers.
028: *
029: * <p>This class is an attempt to rewrite the SAX NamespaceSupport
030: * "helper" class for improved efficiency. It can be used to track the
031: * namespace declarations currently in scope, providing lookup
032: * routines to map prefixes to URIs and vice versa.</p>
033: *
034: * <p>ISSUE: For testing purposes, I've extended NamespaceSupport even
035: * though I'm completely reasserting all behaviors and fields.
036: * Wasteful.... But SAX did not put an interface under that object and
037: * we seem to have written that SAX class into our APIs... and I don't
038: * want to argue with it right now. </p>
039: *
040: * @see org.xml.sax.helpers.NamespaceSupport
041: * */
042: public class NamespaceSupport2 extends
043: org.xml.sax.helpers.NamespaceSupport {
044: ////////////////////////////////////////////////////////////////////
045: // Internal state.
046: ////////////////////////////////////////////////////////////////////
047:
048: private Context2 currentContext; // Current point on the double-linked stack
049:
050: ////////////////////////////////////////////////////////////////////
051: // Constants.
052: ////////////////////////////////////////////////////////////////////
053:
054: /**
055: * The XML Namespace as a constant.
056: *
057: * <p>This is the Namespace URI that is automatically mapped
058: * to the "xml" prefix.</p>
059: */
060: public final static String XMLNS = "http://www.w3.org/XML/1998/namespace";
061:
062: ////////////////////////////////////////////////////////////////////
063: // Constructor.
064: ////////////////////////////////////////////////////////////////////
065:
066: /**
067: * Create a new Namespace support object.
068: */
069: public NamespaceSupport2() {
070: reset();
071: }
072:
073: ////////////////////////////////////////////////////////////////////
074: // Context management.
075: ////////////////////////////////////////////////////////////////////
076:
077: /**
078: * Reset this Namespace support object for reuse.
079: *
080: * <p>It is necessary to invoke this method before reusing the
081: * Namespace support object for a new session.</p>
082: */
083: public void reset() {
084: // Discarding the whole stack doesn't save us a lot versus
085: // creating a new NamespaceSupport. Do we care, or should we
086: // change this to just reset the root context?
087: currentContext = new Context2(null);
088: currentContext.declarePrefix("xml", XMLNS);
089: }
090:
091: /**
092: * Start a new Namespace context.
093: *
094: * <p>Normally, you should push a new context at the beginning
095: * of each XML element: the new context will automatically inherit
096: * the declarations of its parent context, but it will also keep
097: * track of which declarations were made within this context.</p>
098: *
099: * <p>The Namespace support object always starts with a base context
100: * already in force: in this context, only the "xml" prefix is
101: * declared.</p>
102: *
103: * @see #popContext
104: */
105: public void pushContext() {
106: // JJK: Context has a parent pointer.
107: // That means we don't need a stack to pop.
108: // We may want to retain for reuse, but that can be done via
109: // a child pointer.
110:
111: Context2 parentContext = currentContext;
112: currentContext = parentContext.getChild();
113: if (currentContext == null) {
114: currentContext = new Context2(parentContext);
115: } else {
116: // JJK: This will wipe out any leftover data
117: // if we're reusing a previously allocated Context.
118: currentContext.setParent(parentContext);
119: }
120: }
121:
122: /**
123: * Revert to the previous Namespace context.
124: *
125: * <p>Normally, you should pop the context at the end of each
126: * XML element. After popping the context, all Namespace prefix
127: * mappings that were previously in force are restored.</p>
128: *
129: * <p>You must not attempt to declare additional Namespace
130: * prefixes after popping a context, unless you push another
131: * context first.</p>
132: *
133: * @see #pushContext
134: */
135: public void popContext() {
136: Context2 parentContext = currentContext.getParent();
137: if (parentContext == null)
138: throw new EmptyStackException();
139: else
140: currentContext = parentContext;
141: }
142:
143: ////////////////////////////////////////////////////////////////////
144: // Operations within a context.
145: ////////////////////////////////////////////////////////////////////
146:
147: /**
148: * Declare a Namespace prefix.
149: *
150: * <p>This method declares a prefix in the current Namespace
151: * context; the prefix will remain in force until this context
152: * is popped, unless it is shadowed in a descendant context.</p>
153: *
154: * <p>To declare a default Namespace, use the empty string. The
155: * prefix must not be "xml" or "xmlns".</p>
156: *
157: * <p>Note that you must <em>not</em> declare a prefix after
158: * you've pushed and popped another Namespace.</p>
159: *
160: * <p>Note that there is an asymmetry in this library: while {@link
161: * #getPrefix getPrefix} will not return the default "" prefix,
162: * even if you have declared one; to check for a default prefix,
163: * you have to look it up explicitly using {@link #getURI getURI}.
164: * This asymmetry exists to make it easier to look up prefixes
165: * for attribute names, where the default prefix is not allowed.</p>
166: *
167: * @param prefix The prefix to declare, or null for the empty
168: * string.
169: * @param uri The Namespace URI to associate with the prefix.
170: * @return true if the prefix was legal, false otherwise
171: * @see #processName
172: * @see #getURI
173: * @see #getPrefix
174: */
175: public boolean declarePrefix(String prefix, String uri) {
176: if (prefix.equals("xml") || prefix.equals("xmlns")) {
177: return false;
178: } else {
179: currentContext.declarePrefix(prefix, uri);
180: return true;
181: }
182: }
183:
184: /**
185: * Process a raw XML 1.0 name.
186: *
187: * <p>This method processes a raw XML 1.0 name in the current
188: * context by removing the prefix and looking it up among the
189: * prefixes currently declared. The return value will be the
190: * array supplied by the caller, filled in as follows:</p>
191: *
192: * <dl>
193: * <dt>parts[0]</dt>
194: * <dd>The Namespace URI, or an empty string if none is
195: * in use.</dd>
196: * <dt>parts[1]</dt>
197: * <dd>The local name (without prefix).</dd>
198: * <dt>parts[2]</dt>
199: * <dd>The original raw name.</dd>
200: * </dl>
201: *
202: * <p>All of the strings in the array will be internalized. If
203: * the raw name has a prefix that has not been declared, then
204: * the return value will be null.</p>
205: *
206: * <p>Note that attribute names are processed differently than
207: * element names: an unprefixed element name will received the
208: * default Namespace (if any), while an unprefixed element name
209: * will not.</p>
210: *
211: * @param qName The raw XML 1.0 name to be processed.
212: * @param parts A string array supplied by the caller, capable of
213: * holding at least three members.
214: * @param isAttribute A flag indicating whether this is an
215: * attribute name (true) or an element name (false).
216: * @return The supplied array holding three internalized strings
217: * representing the Namespace URI (or empty string), the
218: * local name, and the raw XML 1.0 name; or null if there
219: * is an undeclared prefix.
220: * @see #declarePrefix
221: * @see java.lang.String#intern */
222: public String[] processName(String qName, String[] parts,
223: boolean isAttribute) {
224: String[] name = currentContext.processName(qName, isAttribute);
225: if (name == null)
226: return null;
227:
228: // JJK: This recopying is required because processName may return
229: // a cached result. I Don't Like It. *****
230: System.arraycopy(name, 0, parts, 0, 3);
231: return parts;
232: }
233:
234: /**
235: * Look up a prefix and get the currently-mapped Namespace URI.
236: *
237: * <p>This method looks up the prefix in the current context.
238: * Use the empty string ("") for the default Namespace.</p>
239: *
240: * @param prefix The prefix to look up.
241: * @return The associated Namespace URI, or null if the prefix
242: * is undeclared in this context.
243: * @see #getPrefix
244: * @see #getPrefixes
245: */
246: public String getURI(String prefix) {
247: return currentContext.getURI(prefix);
248: }
249:
250: /**
251: * Return an enumeration of all prefixes currently declared.
252: *
253: * <p><strong>Note:</strong> if there is a default prefix, it will not be
254: * returned in this enumeration; check for the default prefix
255: * using the {@link #getURI getURI} with an argument of "".</p>
256: *
257: * @return An enumeration of all prefixes declared in the
258: * current context except for the empty (default)
259: * prefix.
260: * @see #getDeclaredPrefixes
261: * @see #getURI
262: */
263: public Enumeration getPrefixes() {
264: return currentContext.getPrefixes();
265: }
266:
267: /**
268: * Return one of the prefixes mapped to a Namespace URI.
269: *
270: * <p>If more than one prefix is currently mapped to the same
271: * URI, this method will make an arbitrary selection; if you
272: * want all of the prefixes, use the {@link #getPrefixes}
273: * method instead.</p>
274: *
275: * <p><strong>Note:</strong> this will never return the empty
276: * (default) prefix; to check for a default prefix, use the {@link
277: * #getURI getURI} method with an argument of "".</p>
278: *
279: * @param uri The Namespace URI.
280: * @return One of the prefixes currently mapped to the URI supplied,
281: * or null if none is mapped or if the URI is assigned to
282: * the default Namespace.
283: * @see #getPrefixes(java.lang.String)
284: * @see #getURI */
285: public String getPrefix(String uri) {
286: return currentContext.getPrefix(uri);
287: }
288:
289: /**
290: * Return an enumeration of all prefixes currently declared for a URI.
291: *
292: * <p>This method returns prefixes mapped to a specific Namespace
293: * URI. The xml: prefix will be included. If you want only one
294: * prefix that's mapped to the Namespace URI, and you don't care
295: * which one you get, use the {@link #getPrefix getPrefix}
296: * method instead.</p>
297: *
298: * <p><strong>Note:</strong> the empty (default) prefix is
299: * <em>never</em> included in this enumeration; to check for the
300: * presence of a default Namespace, use the {@link #getURI getURI}
301: * method with an argument of "".</p>
302: *
303: * @param uri The Namespace URI.
304: * @return An enumeration of all prefixes declared in the
305: * current context.
306: * @see #getPrefix
307: * @see #getDeclaredPrefixes
308: * @see #getURI */
309: public Enumeration getPrefixes(String uri) {
310: // JJK: The old code involved creating a vector, filling it
311: // with all the matching prefixes, and then getting its
312: // elements enumerator. Wastes storage, wastes cycles if we
313: // don't actually need them all. Better to either implement
314: // a specific enumerator for these prefixes... or a filter
315: // around the all-prefixes enumerator, which comes out to
316: // roughly the same thing.
317: //
318: // **** Currently a filter. That may not be most efficient
319: // when I'm done restructuring storage!
320: return new PrefixForUriEnumerator(this , uri, getPrefixes());
321: }
322:
323: /**
324: * Return an enumeration of all prefixes declared in this context.
325: *
326: * <p>The empty (default) prefix will be included in this
327: * enumeration; note that this behaviour differs from that of
328: * {@link #getPrefix} and {@link #getPrefixes}.</p>
329: *
330: * @return An enumeration of all prefixes declared in this
331: * context.
332: * @see #getPrefixes
333: * @see #getURI
334: */
335: public Enumeration getDeclaredPrefixes() {
336: return currentContext.getDeclaredPrefixes();
337: }
338:
339: }
340:
341: ////////////////////////////////////////////////////////////////////
342: // Local classes.
343: // These were _internal_ classes... but in fact they don't have to be,
344: // and may be more efficient if they aren't.
345: ////////////////////////////////////////////////////////////////////
346:
347: /**
348: * Implementation of Enumeration filter, wrapped
349: * aroung the get-all-prefixes version of the operation. This is NOT
350: * necessarily the most efficient approach; finding the URI and then asking
351: * what prefixes apply to it might make much more sense.
352: */
353: class PrefixForUriEnumerator implements Enumeration {
354: private Enumeration allPrefixes;
355: private String uri;
356: private String lookahead = null;
357: private NamespaceSupport2 nsup;
358:
359: // Kluge: Since one can't do a constructor on an
360: // anonymous class (as far as I know)...
361: PrefixForUriEnumerator(NamespaceSupport2 nsup, String uri,
362: Enumeration allPrefixes) {
363: this .nsup = nsup;
364: this .uri = uri;
365: this .allPrefixes = allPrefixes;
366: }
367:
368: public boolean hasMoreElements() {
369: if (lookahead != null)
370: return true;
371:
372: while (allPrefixes.hasMoreElements()) {
373: String prefix = (String) allPrefixes.nextElement();
374: if (uri.equals(nsup.getURI(prefix))) {
375: lookahead = prefix;
376: return true;
377: }
378: }
379: return false;
380: }
381:
382: public Object nextElement() {
383: if (hasMoreElements()) {
384: String tmp = lookahead;
385: lookahead = null;
386: return tmp;
387: } else
388: throw new java.util.NoSuchElementException();
389: }
390: }
391:
392: /**
393: * Internal class for a single Namespace context.
394: *
395: * <p>This module caches and reuses Namespace contexts, so the number allocated
396: * will be equal to the element depth of the document, not to the total
397: * number of elements (i.e. 5-10 rather than tens of thousands).</p>
398: */
399: final class Context2 {
400:
401: ////////////////////////////////////////////////////////////////
402: // Manefest Constants
403: ////////////////////////////////////////////////////////////////
404:
405: /**
406: * An empty enumeration.
407: */
408: private final static Enumeration EMPTY_ENUMERATION = new Vector()
409: .elements();
410:
411: ////////////////////////////////////////////////////////////////
412: // Protected state.
413: ////////////////////////////////////////////////////////////////
414:
415: Hashtable prefixTable;
416: Hashtable uriTable;
417: Hashtable elementNameTable;
418: Hashtable attributeNameTable;
419: String defaultNS = null;
420:
421: ////////////////////////////////////////////////////////////////
422: // Internal state.
423: ////////////////////////////////////////////////////////////////
424:
425: private Vector declarations = null;
426: private boolean tablesDirty = false;
427: private Context2 parent = null;
428: private Context2 child = null;
429:
430: /**
431: * Create a new Namespace context.
432: */
433: Context2(Context2 parent) {
434: if (parent == null) {
435: prefixTable = new Hashtable();
436: uriTable = new Hashtable();
437: elementNameTable = null;
438: attributeNameTable = null;
439: } else
440: setParent(parent);
441: }
442:
443: /**
444: * @returns The child Namespace context object, or null if this
445: * is the last currently on the chain.
446: */
447: Context2 getChild() {
448: return child;
449: }
450:
451: /**
452: * @returns The parent Namespace context object, or null if this
453: * is the root.
454: */
455: Context2 getParent() {
456: return parent;
457: }
458:
459: /**
460: * (Re)set the parent of this Namespace context.
461: * This is separate from the c'tor because it's re-applied
462: * when a Context2 is reused by push-after-pop.
463: *
464: * @param context The parent Namespace context object.
465: */
466: void setParent(Context2 parent) {
467: this .parent = parent;
468: parent.child = this ; // JJK: Doubly-linked
469: declarations = null;
470: prefixTable = parent.prefixTable;
471: uriTable = parent.uriTable;
472: elementNameTable = parent.elementNameTable;
473: attributeNameTable = parent.attributeNameTable;
474: defaultNS = parent.defaultNS;
475: tablesDirty = false;
476: }
477:
478: /**
479: * Declare a Namespace prefix for this context.
480: *
481: * @param prefix The prefix to declare.
482: * @param uri The associated Namespace URI.
483: * @see org.xml.sax.helpers.NamespaceSupport2#declarePrefix
484: */
485: void declarePrefix(String prefix, String uri) {
486: // Lazy processing...
487: if (!tablesDirty) {
488: copyTables();
489: }
490: if (declarations == null) {
491: declarations = new Vector();
492: }
493:
494: prefix = prefix.intern();
495: uri = uri.intern();
496: if ("".equals(prefix)) {
497: if ("".equals(uri)) {
498: defaultNS = null;
499: } else {
500: defaultNS = uri;
501: }
502: } else {
503: prefixTable.put(prefix, uri);
504: uriTable.put(uri, prefix); // may wipe out another prefix
505: }
506: declarations.addElement(prefix);
507: }
508:
509: /**
510: * Process a raw XML 1.0 name in this context.
511: *
512: * @param qName The raw XML 1.0 name.
513: * @param isAttribute true if this is an attribute name.
514: * @return An array of three strings containing the
515: * URI part (or empty string), the local part,
516: * and the raw name, all internalized, or null
517: * if there is an undeclared prefix.
518: * @see org.xml.sax.helpers.NamespaceSupport2#processName
519: */
520: String[] processName(String qName, boolean isAttribute) {
521: String name[];
522: Hashtable table;
523:
524: // Select the appropriate table.
525: if (isAttribute) {
526: if (elementNameTable == null)
527: elementNameTable = new Hashtable();
528: table = elementNameTable;
529: } else {
530: if (attributeNameTable == null)
531: attributeNameTable = new Hashtable();
532: table = attributeNameTable;
533: }
534:
535: // Start by looking in the cache, and
536: // return immediately if the name
537: // is already known in this content
538: name = (String[]) table.get(qName);
539: if (name != null) {
540: return name;
541: }
542:
543: // We haven't seen this name in this
544: // context before.
545: name = new String[3];
546: int index = qName.indexOf(':');
547:
548: // No prefix.
549: if (index == -1) {
550: if (isAttribute || defaultNS == null) {
551: name[0] = "";
552: } else {
553: name[0] = defaultNS;
554: }
555: name[1] = qName.intern();
556: name[2] = name[1];
557: }
558:
559: // Prefix
560: else {
561: String prefix = qName.substring(0, index);
562: String local = qName.substring(index + 1);
563: String uri;
564: if ("".equals(prefix)) {
565: uri = defaultNS;
566: } else {
567: uri = (String) prefixTable.get(prefix);
568: }
569: if (uri == null) {
570: return null;
571: }
572: name[0] = uri;
573: name[1] = local.intern();
574: name[2] = qName.intern();
575: }
576:
577: // Save in the cache for future use.
578: table.put(name[2], name);
579: tablesDirty = true;
580: return name;
581: }
582:
583: /**
584: * Look up the URI associated with a prefix in this context.
585: *
586: * @param prefix The prefix to look up.
587: * @return The associated Namespace URI, or null if none is
588: * declared.
589: * @see org.xml.sax.helpers.NamespaceSupport2#getURI
590: */
591: String getURI(String prefix) {
592: if ("".equals(prefix)) {
593: return defaultNS;
594: } else if (prefixTable == null) {
595: return null;
596: } else {
597: return (String) prefixTable.get(prefix);
598: }
599: }
600:
601: /**
602: * Look up one of the prefixes associated with a URI in this context.
603: *
604: * <p>Since many prefixes may be mapped to the same URI,
605: * the return value may be unreliable.</p>
606: *
607: * @param uri The URI to look up.
608: * @return The associated prefix, or null if none is declared.
609: * @see org.xml.sax.helpers.NamespaceSupport2#getPrefix
610: */
611: String getPrefix(String uri) {
612: if (uriTable == null) {
613: return null;
614: } else {
615: return (String) uriTable.get(uri);
616: }
617: }
618:
619: /**
620: * Return an enumeration of prefixes declared in this context.
621: *
622: * @return An enumeration of prefixes (possibly empty).
623: * @see org.xml.sax.helpers.NamespaceSupport2#getDeclaredPrefixes
624: */
625: Enumeration getDeclaredPrefixes() {
626: if (declarations == null) {
627: return EMPTY_ENUMERATION;
628: } else {
629: return declarations.elements();
630: }
631: }
632:
633: /**
634: * Return an enumeration of all prefixes currently in force.
635: *
636: * <p>The default prefix, if in force, is <em>not</em>
637: * returned, and will have to be checked for separately.</p>
638: *
639: * @return An enumeration of prefixes (never empty).
640: * @see org.xml.sax.helpers.NamespaceSupport2#getPrefixes
641: */
642: Enumeration getPrefixes() {
643: if (prefixTable == null) {
644: return EMPTY_ENUMERATION;
645: } else {
646: return prefixTable.keys();
647: }
648: }
649:
650: ////////////////////////////////////////////////////////////////
651: // Internal methods.
652: ////////////////////////////////////////////////////////////////
653:
654: /**
655: * Copy on write for the internal tables in this context.
656: *
657: * <p>This class is optimized for the normal case where most
658: * elements do not contain Namespace declarations. In that case,
659: * the Context2 will share data structures with its parent.
660: * New tables are obtained only when new declarations are issued,
661: * so they can be popped off the stack.</p>
662: *
663: * <p> JJK: **** Alternative: each Context2 might declare
664: * _only_ its local bindings, and delegate upward if not found.</p>
665: */
666: private void copyTables() {
667: // Start by copying our parent's bindings
668: prefixTable = (Hashtable) prefixTable.clone();
669: uriTable = (Hashtable) uriTable.clone();
670:
671: // Replace the caches with empty ones, rather than
672: // trying to determine which bindings should be flushed.
673: // As far as I can tell, these caches are never actually
674: // used in Xalan... More efficient to remove the whole
675: // cache system? ****
676: if (elementNameTable != null)
677: elementNameTable = new Hashtable();
678: if (attributeNameTable != null)
679: attributeNameTable = new Hashtable();
680: tablesDirty = true;
681: }
682:
683: }
684:
685: // end of NamespaceSupport2.java
|