001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.xml;
018:
019: import org.xml.sax.ContentHandler;
020: import org.xml.sax.SAXException;
021:
022: /**
023: * Keeps track of namespaces declarations and resolve namespaces names.
024: * <p>
025: * This class also provides a very convenient and safe way of handling
026: * namespace declarations in SAX pipes. It also allows to filter duplicate namespace
027: * declarations that too often clutter up XML documents that went through
028: * several transformations, and avoid useless namespace declarations that aren't followed
029: * by element events.
030: * <p>
031: * Usage example in a SAX pipe:
032: * <pre>
033: * NamespacesTable namespaces = new NamespacesTable();
034: * ContentHandler nextHandler;
035: *
036: * public void startPrefixMapping(String prefix, String uri) throws SAXException {
037: * namespaces.addDeclaration(prefix, uri);
038: * }
039: *
040: * public void startElement(...) throws SAXException {
041: * // automatically start mappings for this scope
042: * namespaces.enterScope(nextHandler);
043: * nextHandler.startElement(...);
044: * }
045: *
046: * public void endElement(...) throws SAXException {
047: * nextHandler.endElement(...);
048: * // automatically end mappings for this scope
049: * namespaces.leaveScope(nextHandler);
050: * }
051: *
052: * public void endPrefixMapping(String prefix) throws SAXException {
053: * // Ignore, it is handled by leaveScope()
054: * }
055: * </pre>
056: *
057: * @version $Id: NamespacesTable.java 433543 2006-08-22 06:22:54Z crossley $
058: */
059: public class NamespacesTable {
060: /** The last namespace declaration. */
061: private Entry lastEntry;
062:
063: /** The entry that start the prefix mappings for the scope that's about to be entered
064: * or was just left.
065: */
066: private Entry lastDeclaredEntry;
067:
068: private boolean usesScopes = false;
069:
070: /**
071: * Construct a new <code>NamespacesTable</code> instance.
072: */
073: public NamespacesTable() {
074: clear();
075: }
076:
077: /**
078: * Clear and reinitialize this namespace table before reuse.
079: *
080: * @since 2.1.8
081: */
082: public void clear() {
083: this .lastEntry = Entry.create("", "");
084: this .addDeclaration("xml",
085: "http://www.w3.org/XML/1998/namespace");
086: // Lock this scope
087: this .lastEntry.closedScopes = 1;
088: }
089:
090: /**
091: * Declare a new namespace prefix-uri mapping.
092: *
093: * @return The newly added <code>Declaration</code>.
094: */
095: public Declaration addDeclaration(String prefix, String uri) {
096: // Find a previous declaration of the same prefix
097: Entry dup = this .lastEntry;
098: while (dup != null && !dup.prefix.equals(prefix)) {
099: dup = dup.previous;
100: }
101:
102: if (dup != null) {
103: if (usesScopes && dup.uri.equals(uri)) {
104: return dup;
105: }
106: dup.overriden = true;
107: }
108:
109: Entry e = Entry.create(prefix, uri);
110: e.previous = this .lastEntry;
111: e.overrides = dup;
112: this .lastEntry = e;
113: // this always starts the declared prefix chain
114: this .lastDeclaredEntry = e;
115: return e;
116: }
117:
118: /**
119: * Undeclare a namespace prefix-uri mapping. If the prefix was previously declared
120: * mapping another URI, its value is restored.
121: * <p>
122: * When using {@link #enterScope()}/{@link #leaveScope()}, this method does nothing and always
123: * returns <code>null</code>, as declaration removal is handled in {@link #leaveScope()}.
124: *
125: * @return the removed <code>Declaration</code> or <b>null</b>.
126: */
127: public Declaration removeDeclaration(String prefix) {
128: if (usesScopes) {
129: // Automatically handled in leaveScope
130: return null; // or throw and IllegalStateException if enterScope(handler) was used?
131: }
132:
133: Entry current = this .lastEntry;
134: Entry afterCurrent = null;
135: while (current != null) {
136: if (current.closedScopes > 0) {
137: // Don't undeclare mappings not declared in this scope
138: return null;
139: }
140:
141: if (current.prefix.equals(prefix)) {
142: // Got it
143: // Remove it from the chain
144: if (afterCurrent != null) {
145: afterCurrent.previous = current.previous;
146: }
147: // And report closed scopes on the previous entry
148: current.previous.closedScopes += current.closedScopes;
149: Entry overrides = current.overrides;
150: if (overrides != null) {
151: // No more overriden
152: overrides.overriden = false;
153: }
154:
155: if (this .lastDeclaredEntry == current) {
156: if (current.previous.closedScopes == 0) {
157: this .lastDeclaredEntry = current.previous;
158: } else {
159: this .lastDeclaredEntry = null;
160: }
161: }
162:
163: if (this .lastEntry == current) {
164: this .lastEntry = current.previous;
165: }
166:
167: return current;
168: }
169:
170: afterCurrent = current;
171: current = current.previous;
172: }
173:
174: // Not found
175: return null;
176: }
177:
178: /**
179: * Enter a new scope. This starts a new, empty list of declarations for the new scope.
180: * <p>
181: * Typically called in a SAX handler <em>before</em> sending a <code>startElement()</code>
182: * event.
183: *
184: * @since 2.1.8
185: */
186: public void enterScope() {
187: this .usesScopes = true;
188: this .lastEntry.closedScopes++;
189: this .lastDeclaredEntry = null;
190: }
191:
192: /**
193: * Start all declared mappings of the current scope and enter a new scope. This starts a new,
194: * empty list of declarations for the new scope.
195: * <p>
196: * Typically called in a SAX handler <em>before</em> sending a <code>startElement()</code>
197: * event.
198: *
199: * @param handler the handler that will receive startPrefixMapping events.
200: * @throws SAXException
201: * @since 2.1.8
202: */
203: public void enterScope(ContentHandler handler) throws SAXException {
204: this .usesScopes = true;
205: Entry current = this .lastEntry;
206: while (current != null && current.closedScopes == 0) {
207: handler.startPrefixMapping(current.prefix, current.uri);
208: current = current.previous;
209: }
210: this .lastEntry.closedScopes++;
211: this .lastDeclaredEntry = null;
212: }
213:
214: /**
215: * Leave a scope. The namespace declarations that occured before the corresponding
216: * <code>enterScope()</code> are no more visible using the resolution methods, but
217: * still available using {@link #getCurrentScopeDeclarations()} until the next call
218: * to {@link #addDeclaration(String, String)} or {@link #enterScope()}.
219: * <p>
220: * Typically called in a SAX handler <em>after</em> sending a <code>endElement()</code>
221: * event.
222: *
223: * @since 2.1.8
224: */
225: public void leaveScope() {
226: Entry current = this .lastEntry;
227:
228: // Purge declarations that were added but not included in a scope
229: while (current.closedScopes == 0) {
230: current = current.previous;
231: }
232:
233: current.closedScopes--;
234:
235: if (current.closedScopes == 0) {
236: this .lastDeclaredEntry = current;
237: } else {
238: // More than one scope closed here: no local declarations
239: this .lastDeclaredEntry = null;
240: }
241:
242: while (current != null && current.closedScopes == 0) {
243: Entry overrides = current.overrides;
244: if (overrides != null) {
245: // No more overriden
246: overrides.overriden = false;
247: }
248: current = current.previous;
249: }
250: this .lastEntry = current;
251: }
252:
253: /**
254: * Leave a scope. The namespace declarations that occured before the corresponding
255: * <code>enterScope()</code> are no more visible using the resolution methods, but
256: * still available using {@link #getCurrentScopeDeclarations()} until the next call
257: * to {@link #addDeclaration(String, String)} or {@link #enterScope()}.
258: * <p>
259: * Typically called in a SAX handler <em>after</em> sending a <code>endElement()</code>
260: * event.
261: *
262: * @param handler the handler that will receive endPrefixMapping events.
263: * @throws SAXException
264: * @since 2.1.8
265: */
266: public void leaveScope(ContentHandler handler) throws SAXException {
267: Entry current = this .lastEntry;
268:
269: // Purge declarations that were added but not included in a scope
270: while (current.closedScopes == 0) {
271: current = current.previous;
272: }
273:
274: current.closedScopes--;
275:
276: if (current.closedScopes == 0) {
277: this .lastDeclaredEntry = current;
278: } else {
279: // More than one scope closed here: no local declarations
280: this .lastDeclaredEntry = null;
281: }
282:
283: while (current != null && current.closedScopes == 0) {
284: handler.endPrefixMapping(current.prefix);
285: Entry overrides = current.overrides;
286: if (overrides != null) {
287: // No more overriden
288: overrides.overriden = false;
289: }
290: current = current.previous;
291: }
292:
293: this .lastEntry = current;
294: }
295:
296: private static final Declaration[] NO_DECLS = new Declaration[0];
297:
298: /**
299: * Get the declarations that were declared within the current scope.
300: *
301: * @return the declarations (possibly empty, but never null)
302: * @since 2.1.8
303: */
304: public Declaration[] getCurrentScopeDeclarations() {
305: int count = 0;
306: Entry current = this .lastDeclaredEntry;
307: while (current != null && current.closedScopes == 0) {
308: count++;
309: current = current.previous;
310: }
311:
312: if (count == 0)
313: return NO_DECLS;
314:
315: Declaration[] decls = new Declaration[count];
316: count = 0;
317: current = this .lastDeclaredEntry;
318: while (current != null && current.closedScopes == 0) {
319: decls[count++] = current;
320: current = current.previous;
321: }
322: return decls;
323: }
324:
325: /**
326: * Return the URI associated with the given prefix or <b>null</b> if the
327: * prefix was not mapped.
328: */
329: public String getUri(String prefix) {
330: Entry current = this .lastEntry;
331: while (current != null) {
332: if (current.prefix.equals(prefix)) {
333: return current.uri;
334: }
335: current = current.previous;
336: }
337:
338: // Not found
339: return null;
340: }
341:
342: /**
343: * Return an array with all prefixes currently mapped to the specified URI.
344: * <br>
345: * The array length might be <b>zero</b> if no prefixes are associated with
346: * the specified uri.
347: *
348: * @return A <b>non-null</b> <code>String</code> array.
349: */
350: public String[] getPrefixes(String uri) {
351:
352: Entry current = this .lastEntry;
353: int count = 0;
354: while (current != null) {
355: if (!current.overriden && current.uri.equals(uri))
356: count++;
357: current = current.previous;
358: }
359: if (count == 0)
360: return (new String[0]);
361:
362: String prefixes[] = new String[count];
363: count = 0;
364: current = this .lastEntry;
365: while (current != null) {
366: if (!current.overriden && current.uri.equals(uri))
367: prefixes[count++] = current.prefix;
368: current = current.previous;
369: }
370: return prefixes;
371: }
372:
373: /**
374: * Return one of the prefixes currently mapped to the specified URI or
375: * <b>null</b>.
376: */
377: public String getPrefix(String uri) {
378: Entry current = this .lastEntry;
379: while (current != null) {
380: if (!current.overriden && current.uri.equals(uri))
381: return current.prefix;
382: current = current.previous;
383: }
384: return null;
385: }
386:
387: /**
388: * Resolve a namespace-aware name against the current namespaces
389: * declarations.
390: *
391: * @param uri The namespace URI or <b>null</b> if not known.
392: * @param raw The raw (complete) name or <b>null</b> if not known.
393: * @param prefix The namespace prefix or <b>null</b> if not known.
394: * @param local The local name or <b>null</b> if not known.
395: * @return A <b>non-null</b> <code>Name</code>.
396: * @exception SAXException If the name cannot be resolved.
397: */
398: public Name resolve(String uri, String raw, String prefix,
399: String local) throws SAXException {
400: if (uri == null)
401: uri = "";
402: if (raw == null)
403: raw = "";
404: if (prefix == null)
405: prefix = "";
406: if (local == null)
407: local = "";
408: // Start examining the URI
409: if (raw.length() > 0) {
410: // The raw name was specified
411: int pos = raw.indexOf(':');
412: if (pos > 0) {
413: // We have a namespace prefix:local separator
414: String pre = raw.substring(0, pos);
415: String loc = raw.substring(pos + 1);
416: if (prefix.length() == 0)
417: prefix = pre;
418: else if (!prefix.equals(pre))
419: throw new SAXException("Raw/Prefix mismatch");
420: if (local.length() == 0)
421: local = loc;
422: else if (!local.equals(loc))
423: throw new SAXException("Raw/Local Name mismatch");
424: } else {
425: // We don't have a prefix:local separator
426: if (prefix.length() > 0)
427: throw new SAXException("Raw Name/Prefix mismatch");
428: if (local.length() == 0)
429: local = raw;
430: else if (!local.equals(raw))
431: throw new SAXException(
432: "Raw Name/Local Name mismatch");
433: }
434: } else {
435: // The raw name was not specified
436: if (local.length() == 0)
437: throw new SAXException("No Raw/Local Name");
438: if (prefix.length() == 0)
439: raw = local;
440: else
441: raw = prefix + ':' + local;
442: }
443: // We have resolved and checked data between the raw, local, and
444: // prefix... We have to doublecheck the namespaces.
445: if (uri.length() > 0) {
446: // We have a URI and a prefix, check them
447: if ((prefix.length() > 0)
448: && (!uri.equals(this .getUri(prefix)))) {
449: throw new SAXException("URI/Prefix mismatch [" + prefix
450: + "," + uri + "]");
451: } else {
452: String temp = this .getPrefix(uri);
453: if (temp == null)
454: throw new SAXException("URI not declared");
455: else if (temp.length() > 0) {
456: prefix = temp;
457: raw = prefix + ':' + local;
458: }
459: }
460: } else {
461: // We don't have a URI, check if we can find one from the prefix.
462: String temp = this .getUri(prefix);
463: if (temp == null)
464: throw new SAXException("Prefix not declared");
465: else
466: uri = temp;
467: }
468: NameImpl name = new NameImpl();
469: if (uri.length() > 0)
470: name.uri = uri;
471: else
472: name.uri = null;
473: name.raw = raw;
474: name.prefix = prefix;
475: name.local = local;
476: return (name);
477: }
478:
479: /** The internal entry structure for this table. */
480: private static class Entry implements Declaration {
481: /** The URI string. */
482: protected String uri = "";
483: /** The prefix string. */
484: protected String prefix = "";
485: /** The previous declaration. */
486: protected Entry previous;
487: protected Entry overrides;
488: protected int closedScopes = 0;
489: protected boolean overriden = false;
490:
491: /** Create a new namespace declaration. */
492: protected static Entry create(String prefix, String uri) {
493: // Create a new entry
494: Entry e = new Entry();
495: // Set the prefix string.
496: if (prefix != null)
497: e.prefix = prefix;
498: // Set the uri string.
499: if (uri != null)
500: e.uri = uri;
501: // Return the entry
502: return e;
503: }
504:
505: /** Return the namespace URI. */
506: public String getUri() {
507: return this .uri;
508: }
509:
510: /** Return the namespace prefix. */
511: public String getPrefix() {
512: return this .prefix;
513: }
514: }
515:
516: /** The default namespace-aware name declaration implementation */
517: private static class NameImpl implements Name {
518: /** The namespace URI. */
519: protected String uri;
520: /** The namespace prefix. */
521: protected String prefix;
522: /** The namespace local name. */
523: protected String local;
524: /** The namespace raw name. */
525: protected String raw;
526:
527: /** Return the namespace URI. */
528: public String getUri() {
529: return this .uri;
530: }
531:
532: /** Return the namespace prefix. */
533: public String getPrefix() {
534: return this .prefix;
535: }
536:
537: /** Return the namespace local name. */
538: public String getLocalName() {
539: return this .local;
540: }
541:
542: /** Return the namespace raw name. */
543: public String getQName() {
544: return this .raw;
545: }
546: }
547:
548: /**
549: * A namespace-aware name. (This interface is used in conjunction
550: * with <code>NamespacesTable</code>).
551: */
552: public interface Name {
553: /** Return the namespace URI. */
554: String getUri();
555:
556: /** Return the namespace prefix. */
557: String getPrefix();
558:
559: /** Return the namespace local name. */
560: String getLocalName();
561:
562: /** Return the namespace raw name. */
563: String getQName();
564: }
565:
566: /**
567: * A namespace declaration. (This interface is used in conjunction
568: * with <code>NamespacesTable</code>).
569: */
570: public interface Declaration {
571: /** Return the namespace URI. */
572: String getUri();
573:
574: /** Return the namespace prefix. */
575: String getPrefix();
576: }
577: }
|