KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > xml > NamespacesTable


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.xml;
17
18 import org.xml.sax.ContentHandler JavaDoc;
19 import org.xml.sax.SAXException JavaDoc;
20
21 /**
22  * Keeps track of namespaces declarations and resolve namespaces names.
23  * <p>
24  * This class also provides a very convenient and safe way of handling
25  * namespace declarations in SAX pipes. It also allows to filter duplicate namespace
26  * declarations that too often clutter up XML documents that went through
27  * several transformations, and avoid useless namespace declarations that aren't followed
28  * by element events.
29  * <p>
30  * Usage example in a SAX pipe:
31  * <pre>
32  * NamespacesTable namespaces = new NamespacesTable();
33  * ContentHandler nextHandler;
34  *
35  * public void startPrefixMapping(String prefix, String uri) throws SAXException {
36  * namespaces.addDeclaration(prefix, uri);
37  * }
38  *
39  * public void startElement(...) throws SAXException {
40  * // automatically start mappings for this scope
41  * namespaces.enterScope(nextHandler);
42  * nextHandler.startElement(...);
43  * }
44  *
45  * public void endElement(...) throws SAXException {
46  * nextHandler.endElement(...);
47  * // automatically end mappings for this scope
48  * namespaces.leaveScope(nextHandler);
49  * }
50  *
51  * public void endPrefixMapping(String prefix) throws SAXException {
52  * // Ignore, it is handled by leaveScope()
53  * }
54  * </pre>
55  *
56  * @version $Id: NamespacesTable.java 232969 2005-08-16 09:22:46Z sylvain $
57  */

58 public class NamespacesTable {
59     /** The last namespace declaration. */
60     private Entry lastEntry;
61     
62     /** The entry that start the prefix mappings for the scope that's about to be entered
63      * or was just left.
64      */

65     private Entry lastDeclaredEntry;
66
67     private boolean usesScopes = false;
68
69     /**
70      * Construct a new <code>NamespacesTable</code> instance.
71      */

72     public NamespacesTable() {
73         clear();
74     }
75
76     /**
77      * Clear and reinitialize this namespace table before reuse.
78      *
79      * @since 2.1.8
80      */

81     public void clear() {
82         this.lastEntry = Entry.create("","");
83         this.addDeclaration("xml", "http://www.w3.org/XML/1998/namespace");
84         // Lock this scope
85
this.lastEntry.closedScopes = 1;
86     }
87
88     /**
89      * Declare a new namespace prefix-uri mapping.
90      *
91      * @return The newly added <code>Declaration</code>.
92      */

93     public Declaration addDeclaration(String JavaDoc prefix, String JavaDoc uri) {
94         // Find a previous declaration of the same prefix
95
Entry dup = this.lastEntry;
96         while (dup != null && !dup.prefix.equals(prefix)) {
97             dup = dup.previous;
98         }
99
100         if (dup != null) {
101             if (usesScopes && dup.uri.equals(uri)) {
102                 return dup;
103             }
104             dup.overriden = true;
105         }
106
107         Entry e = Entry.create(prefix, uri);
108         e.previous = this.lastEntry;
109         e.overrides = dup;
110         this.lastEntry = e;
111         // this always starts the declared prefix chain
112
this.lastDeclaredEntry = e;
113         return e;
114     }
115
116     /**
117      * Undeclare a namespace prefix-uri mapping. If the prefix was previously declared
118      * mapping another URI, its value is restored.
119      * <p>
120      * When using {@link #enterScope()}/{@link #leaveScope()}, this method does nothing and always
121      * returns <code>null</code>, as declaration removal is handled in {@link #leaveScope()}.
122      *
123      * @return the removed <code>Declaration</code> or <b>null</b>.
124      */

125     public Declaration removeDeclaration(String JavaDoc prefix) {
126         if (usesScopes) {
127             // Automatically handled in leaveScope
128
return null; // or throw and IllegalStateException if enterScope(handler) was used?
129
}
130
131         Entry current = this.lastEntry;
132         Entry afterCurrent = null;
133         while(current != null) {
134             if (current.closedScopes > 0) {
135                 // Don't undeclare mappings not declared in this scope
136
return null;
137             }
138
139             if (current.prefix.equals(prefix)) {
140                 // Got it
141
// Remove it from the chain
142
if (afterCurrent != null) {
143                     afterCurrent.previous = current.previous;
144                 }
145                 // And report closed scopes on the previous entry
146
current.previous.closedScopes += current.closedScopes;
147                 Entry overrides = current.overrides;
148                 if (overrides != null) {
149                     // No more overriden
150
overrides.overriden = false;
151                 }
152
153                 if (this.lastDeclaredEntry == current) {
154                     if (current.previous.closedScopes == 0) {
155                         this.lastDeclaredEntry = current.previous;
156                     } else {
157                         this.lastDeclaredEntry = null;
158                     }
159                 }
160
161                 if (this.lastEntry == current) {
162                     this.lastEntry = current.previous;
163                 }
164
165                 return current;
166             }
167
168             afterCurrent = current;
169             current = current.previous;
170         }
171
172         // Not found
173
return null;
174     }
175
176     /**
177      * Enter a new scope. This starts a new, empty list of declarations for the new scope.
178      * <p>
179      * Typically called in a SAX handler <em>before</em> sending a <code>startElement()</code>
180      * event.
181      *
182      * @since 2.1.8
183      */

184     public void enterScope() {
185         this.usesScopes = true;
186         this.lastEntry.closedScopes++;
187         this.lastDeclaredEntry = null;
188     }
189
190     /**
191      * Start all declared mappings of the current scope and enter a new scope. This starts a new,
192      * empty list of declarations for the new scope.
193      * <p>
194      * Typically called in a SAX handler <em>before</em> sending a <code>startElement()</code>
195      * event.
196      *
197      * @param handler the handler that will receive startPrefixMapping events.
198      * @throws SAXException
199      * @since 2.1.8
200      */

201     public void enterScope(ContentHandler JavaDoc handler) throws SAXException JavaDoc {
202         this.usesScopes = true;
203         Entry current = this.lastEntry;
204         while (current != null && current.closedScopes == 0) {
205             handler.startPrefixMapping(current.prefix, current.uri);
206             current = current.previous;
207         }
208         this.lastEntry.closedScopes++;
209         this.lastDeclaredEntry = null;
210     }
211
212     /**
213      * Leave a scope. The namespace declarations that occured before the corresponding
214      * <code>enterScope()</code> are no more visible using the resolution methods, but
215      * still available using {@link #getCurrentScopeDeclarations()} until the next call
216      * to {@link #addDeclaration(String, String)} or {@link #enterScope()}.
217      * <p>
218      * Typically called in a SAX handler <em>after</em> sending a <code>endElement()</code>
219      * event.
220      *
221      * @since 2.1.8
222      */

223     public void leaveScope() {
224         Entry current = this.lastEntry;
225
226         // Purge declarations that were added but not included in a scope
227
while (current.closedScopes == 0) {
228             current = current.previous;
229         }
230
231         current.closedScopes--;
232
233         if (current.closedScopes == 0) {
234             this.lastDeclaredEntry = current;
235         } else {
236             // More than one scope closed here: no local declarations
237
this.lastDeclaredEntry = null;
238         }
239
240         while (current != null && current.closedScopes == 0) {
241             Entry overrides = current.overrides;
242             if (overrides != null) {
243                 // No more overriden
244
overrides.overriden = false;
245             }
246             current = current.previous;
247         }
248         this.lastEntry = current;
249     }
250
251     /**
252      * Leave a scope. The namespace declarations that occured before the corresponding
253      * <code>enterScope()</code> are no more visible using the resolution methods, but
254      * still available using {@link #getCurrentScopeDeclarations()} until the next call
255      * to {@link #addDeclaration(String, String)} or {@link #enterScope()}.
256      * <p>
257      * Typically called in a SAX handler <em>after</em> sending a <code>endElement()</code>
258      * event.
259      *
260      * @param handler the handler that will receive endPrefixMapping events.
261      * @throws SAXException
262      * @since 2.1.8
263      */

264     public void leaveScope(ContentHandler JavaDoc handler) throws SAXException JavaDoc {
265         Entry current = this.lastEntry;
266         
267         // Purge declarations that were added but not included in a scope
268
while (current.closedScopes == 0) {
269             current = current.previous;
270         }
271
272         current.closedScopes--;
273
274         if (current.closedScopes == 0) {
275             this.lastDeclaredEntry = current;
276         } else {
277             // More than one scope closed here: no local declarations
278
this.lastDeclaredEntry = null;
279         }
280
281         while (current != null && current.closedScopes == 0) {
282             handler.endPrefixMapping(current.prefix);
283             Entry overrides = current.overrides;
284             if (overrides != null) {
285                 // No more overriden
286
overrides.overriden = false;
287             }
288             current = current.previous;
289         }
290
291         this.lastEntry = current;
292     }
293
294     private static final Declaration[] NO_DECLS = new Declaration[0];
295
296     /**
297      * Get the declarations that were declared within the current scope.
298      *
299      * @return the declarations (possibly empty, but never null)
300      * @since 2.1.8
301      */

302     public Declaration[] getCurrentScopeDeclarations() {
303         int count = 0;
304         Entry current = this.lastDeclaredEntry;
305         while (current != null && current.closedScopes == 0) {
306             count++;
307             current = current.previous;
308         }
309
310         if (count == 0) return NO_DECLS;
311
312         Declaration[] decls = new Declaration[count];
313         count = 0;
314         current = this.lastDeclaredEntry;
315         while (current != null && current.closedScopes == 0) {
316             decls[count++] = current;
317             current = current.previous;
318         }
319         return decls;
320     }
321
322     /**
323      * Return the URI associated with the given prefix or <b>null</b> if the
324      * prefix was not mapped.
325      */

326     public String JavaDoc getUri(String JavaDoc prefix) {
327         Entry current = this.lastEntry;
328         while (current != null) {
329             if (current.prefix.equals(prefix)) {
330                 return current.uri;
331             }
332             current = current.previous;
333         }
334
335         // Not found
336
return null;
337     }
338
339     /**
340      * Return an array with all prefixes currently mapped to the specified URI.
341      * <br>
342      * The array length might be <b>zero</b> if no prefixes are associated with
343      * the specified uri.
344      *
345      * @return A <b>non-null</b> <code>String</code> array.
346      */

347     public String JavaDoc[] getPrefixes(String JavaDoc uri) {
348
349         Entry current=this.lastEntry;
350         int count=0;
351         while (current!=null) {
352             if(!current.overriden && current.uri.equals(uri))
353                 count++;
354             current=current.previous;
355         }
356         if (count==0) return(new String JavaDoc[0]);
357
358         String JavaDoc prefixes[]=new String JavaDoc[count];
359         count=0;
360         current = this.lastEntry;
361         while (current!=null) {
362             if(!current.overriden && current.uri.equals(uri))
363                 prefixes[count++] = current.prefix;
364             current = current.previous;
365         }
366         return prefixes;
367     }
368
369
370     /**
371      * Return one of the prefixes currently mapped to the specified URI or
372      * <b>null</b>.
373      */

374     public String JavaDoc getPrefix(String JavaDoc uri) {
375         Entry current = this.lastEntry;
376         while (current != null) {
377             if(!current.overriden && current.uri.equals(uri))
378                 return current.prefix;
379             current = current.previous;
380         }
381         return null;
382     }
383
384     /**
385      * Resolve a namespace-aware name against the current namespaces
386      * declarations.
387      *
388      * @param uri The namespace URI or <b>null</b> if not known.
389      * @param raw The raw (complete) name or <b>null</b> if not known.
390      * @param prefix The namespace prefix or <b>null</b> if not known.
391      * @param local The local name or <b>null</b> if not known.
392      * @return A <b>non-null</b> <code>Name</code>.
393      * @exception SAXException If the name cannot be resolved.
394      */

395     public Name resolve(String JavaDoc uri, String JavaDoc raw, String JavaDoc prefix, String JavaDoc local)
396     throws SAXException JavaDoc {
397         if (uri==null) uri="";
398         if (raw==null) raw="";
399         if (prefix==null) prefix="";
400         if (local==null) local="";
401         // Start examining the URI
402
if (raw.length()>0) {
403             // The raw name was specified
404
int pos=raw.indexOf(':');
405             if (pos>0) {
406                 // We have a namespace prefix:local separator
407
String JavaDoc pre=raw.substring(0,pos);
408                 String JavaDoc loc=raw.substring(pos+1);
409                 if (prefix.length()==0) prefix=pre;
410                 else if (!prefix.equals(pre))
411                     throw new SAXException JavaDoc("Raw/Prefix mismatch");
412                 if (local.length()==0) local=loc;
413                 else if (!local.equals(loc))
414                     throw new SAXException JavaDoc("Raw/Local Name mismatch");
415             } else {
416                 // We don't have a prefix:local separator
417
if (prefix.length()>0)
418                     throw new SAXException JavaDoc("Raw Name/Prefix mismatch");
419                 if (local.length()==0) local=raw;
420                 else if (!local.equals(raw))
421                     throw new SAXException JavaDoc("Raw Name/Local Name mismatch");
422             }
423         } else {
424             // The raw name was not specified
425
if (local.length()==0) throw new SAXException JavaDoc("No Raw/Local Name");
426             if (prefix.length()==0) raw=local;
427             else raw=prefix+':'+local;
428         }
429         // We have resolved and checked data between the raw, local, and
430
// prefix... We have to doublecheck the namespaces.
431
if (uri.length()>0) {
432             // We have a URI and a prefix, check them
433
if ((prefix.length()>0) && (!uri.equals(this.getUri(prefix)))) {
434                 throw new SAXException JavaDoc("URI/Prefix mismatch [" + prefix + "," + uri + "]");
435             } else {
436                 String JavaDoc temp=this.getPrefix(uri);
437                 if (temp==null) throw new SAXException JavaDoc("URI not declared");
438                 else if (temp.length()>0) {
439                     prefix=temp;
440                     raw=prefix+':'+local;
441                 }
442             }
443         } else {
444             // We don't have a URI, check if we can find one from the prefix.
445
String JavaDoc temp=this.getUri(prefix);
446             if (temp==null) throw new SAXException JavaDoc("Prefix not declared");
447             else uri=temp;
448         }
449         NameImpl name=new NameImpl();
450         if (uri.length() > 0) name.uri=uri;
451         else name.uri=null;
452         name.raw=raw;
453         name.prefix=prefix;
454         name.local=local;
455         return(name);
456     }
457
458     /** The internal entry structure for this table. */
459     private static class Entry implements Declaration {
460         /** The URI string. */
461         protected String JavaDoc uri="";
462         /** The prefix string. */
463         protected String JavaDoc prefix="";
464         /** The previous declaration. */
465         protected Entry previous;
466         protected Entry overrides;
467         protected int closedScopes = 0;
468         protected boolean overriden = false;
469
470         /** Create a new namespace declaration. */
471         protected static Entry create(String JavaDoc prefix, String JavaDoc uri) {
472             // Create a new entry
473
Entry e = new Entry();
474             // Set the prefix string.
475
if (prefix != null) e.prefix=prefix;
476             // Set the uri string.
477
if (uri != null) e.uri=uri;
478             // Return the entry
479
return e;
480         }
481
482         /** Return the namespace URI. */
483         public String JavaDoc getUri() { return this.uri; }
484         /** Return the namespace prefix. */
485         public String JavaDoc getPrefix() { return this.prefix; }
486     }
487
488     /** The default namespace-aware name declaration implementation */
489     private static class NameImpl implements Name {
490         /** The namespace URI. */
491         protected String JavaDoc uri;
492         /** The namespace prefix. */
493         protected String JavaDoc prefix;
494         /** The namespace local name. */
495         protected String JavaDoc local;
496         /** The namespace raw name. */
497         protected String JavaDoc raw;
498
499         /** Return the namespace URI. */
500         public String JavaDoc getUri() { return this.uri; }
501         /** Return the namespace prefix. */
502         public String JavaDoc getPrefix() { return this.prefix; }
503         /** Return the namespace local name. */
504         public String JavaDoc getLocalName() { return this.local; }
505         /** Return the namespace raw name. */
506         public String JavaDoc getQName() { return this.raw; }
507     }
508
509     /**
510      * A namespace-aware name. (This interface is used in conjunction
511      * with <code>NamespacesTable</code>).
512      */

513     public interface Name {
514         /** Return the namespace URI. */
515         String JavaDoc getUri();
516         /** Return the namespace prefix. */
517         String JavaDoc getPrefix();
518         /** Return the namespace local name. */
519         String JavaDoc getLocalName();
520         /** Return the namespace raw name. */
521         String JavaDoc getQName();
522     }
523
524     /**
525      * A namespace declaration. (This interface is used in conjunction
526      * with <code>NamespacesTable</code>).
527      */

528     public interface Declaration {
529         /** Return the namespace URI. */
530         String JavaDoc getUri();
531         /** Return the namespace prefix. */
532         String JavaDoc getPrefix();
533     }
534 }
535
Popular Tags