1 /* SAXDriver.java --
2    Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version.
37 
38 Portions derived from code which carried the following notice:
39 
40   Copyright (c) 1997, 1998 by Microstar Software Ltd.
41 
42   AElfred is free for both commercial and non-commercial use and
43   redistribution, provided that Microstar's copyright and disclaimer are
44   retained intact.  You are free to modify AElfred for your own use and
45   to redistribute AElfred with your modifications, provided that the
46   modifications are clearly documented.
47 
48   This program is distributed in the hope that it will be useful, but
49   WITHOUT ANY WARRANTY; without even the implied warranty of
50   merchantability or fitness for a particular purpose.  Please use it AT
51   YOUR OWN RISK.
52 */
53 
54 package gnu.xml.aelfred2;
55 
56 import java.io.*;
57 
58 import java.net.MalformedURLException;
59 import java.net.URL;
60 import java.util.Locale;
61 import java.util.Stack;
62 
63 import java.util.ArrayList;
64 import java.util.Collections;
65 import java.util.Enumeration;
66 import java.util.Iterator;
67 import java.util.List;
68 
69 import org.xml.sax.*;
70 import org.xml.sax.ext.*;
71 import org.xml.sax.helpers.NamespaceSupport;
72 
73 
74 /**
75  * An enhanced SAX2 version of Microstar's Ælfred XML parser.
76  * The enhancements primarily relate to significant improvements in
77  * conformance to the XML specification, and SAX2 support.  Performance
78  * has been improved.  See the package level documentation for more
79  * information.
80  *
81  * <table border="1" width='100%' cellpadding='3' cellspacing='0'>
82  * <tr bgcolor='#ccccff'>
83  *      <th><font size='+1'>Name</font></th>
84  *      <th><font size='+1'>Notes</font></th></tr>
85  *
86  * <tr><td colspan=2><center><em>Features ... URL prefix is
87  * <b>http://xml.org/sax/features/</b></em></center></td></tr>
88  *
89  * <tr><td>(URL)/external-general-entities</td>
90  *      <td>Value defaults to <em>true</em></td></tr>
91  * <tr><td>(URL)/external-parameter-entities</td>
92  *      <td>Value defaults to <em>true</em></td></tr>
93  * <tr><td>(URL)/is-standalone</td>
94  *      <td>(PRELIMINARY) Returns true iff the document's parsing
95  *      has started (some non-error event after <em>startDocument()</em>
96  *      was reported) and the document's standalone flag is set.</td></tr>
97  * <tr><td>(URL)/namespace-prefixes</td>
98  *      <td>Value defaults to <em>false</em> (but XML 1.0 names are
99  *              always reported)</td></tr>
100  * <tr><td>(URL)/lexical-handler/parameter-entities</td>
101  *      <td>Value is fixed at <em>true</em></td></tr>
102  * <tr><td>(URL)/namespaces</td>
103  *      <td>Value defaults to <em>true</em></td></tr>
104  * <tr><td>(URL)/resolve-dtd-uris</td>
105  *      <td>(PRELIMINARY) Value defaults to <em>true</em></td></tr>
106  * <tr><td>(URL)/string-interning</td>
107  *      <td>Value is fixed at <em>true</em></td></tr>
108  * <tr><td>(URL)/use-attributes2</td>
109  *      <td>(PRELIMINARY) Value is fixed at <em>true</em></td></tr>
110  * <tr><td>(URL)/use-entity-resolver2</td>
111  *      <td>(PRELIMINARY) Value defaults to <em>true</em></td></tr>
112  * <tr><td>(URL)/validation</td>
113  *      <td>Value is fixed at <em>false</em></td></tr>
114  *
115  * <tr><td colspan=2><center><em>Handler Properties ... URL prefix is
116  * <b>http://xml.org/sax/properties/</b></em></center></td></tr>
117  *
118  * <tr><td>(URL)/declaration-handler</td>
119  *      <td>A declaration handler may be provided.  </td></tr>
120  * <tr><td>(URL)/lexical-handler</td>
121  *      <td>A lexical handler may be provided.  </td></tr>
122  * </table>
123  *
124  * <p>This parser currently implements the SAX1 Parser API, but
125  * it may not continue to do so in the future.
126  *
127  * @author Written by David Megginson (version 1.2a from Microstar)
128  * @author Updated by David Brownell &lt;dbrownell@users.sourceforge.net&gt;
129  * @see org.xml.sax.Parser
130  */
131 final public class SAXDriver
132   implements Locator, Attributes2, XMLReader, Parser, AttributeList
133 {
134 
135   private final DefaultHandler2 base = new DefaultHandler2();
136   private XmlParser parser;
137 
138   private EntityResolver entityResolver = base;
139   private EntityResolver2 resolver2 = null;
140   private ContentHandler contentHandler = base;
141   private DTDHandler dtdHandler = base;
142   private ErrorHandler errorHandler = base;
143   private DeclHandler declHandler = base;
144   private LexicalHandler lexicalHandler = base;
145 
146   private String elementName;
147   private Stack entityStack;
148 
149   // one vector (of object/struct): faster, smaller
150   private List attributesList;
151 
152   private boolean namespaces = true;
153   private boolean xmlNames = false;
154   private boolean extGE = true;
155   private boolean extPE = true;
156   private boolean resolveAll = true;
157   private boolean useResolver2 = true;
158 
159   // package private to allow (read-only) access in XmlParser
160   boolean stringInterning = true;
161 
162   private int attributeCount;
163   private boolean attributes;
164   private String[] nsTemp;
165   private NamespaceSupport prefixStack;
166 
167   //
168   // Constructor.
169   //
170 
171   /**
172    * Constructs a SAX Parser.
173    */
SAXDriver()174   public SAXDriver()
175   {
176     reset();
177   }
178 
reset()179   private void reset()
180   {
181     elementName = null;
182     entityStack = new Stack();
183     attributesList = Collections.synchronizedList(new ArrayList());
184     attributeCount = 0;
185     attributes = false;
186     nsTemp = new String[3];
187     prefixStack = null;
188   }
189 
190 
191   //
192   // Implementation of org.xml.sax.Parser.
193   //
194 
195   /**
196    * <b>SAX1</b>: Sets the locale used for diagnostics; currently,
197    * only locales using the English language are supported.
198    * @param locale The locale for which diagnostics will be generated
199    */
setLocale(Locale locale)200   public void setLocale(Locale locale)
201     throws SAXException
202   {
203     if ("en".equals(locale.getLanguage()))
204       {
205         return;
206       }
207     throw new SAXException ("AElfred2 only supports English locales.");
208   }
209 
210   /**
211    * <b>SAX2</b>: Returns the object used when resolving external
212    * entities during parsing (both general and parameter entities).
213    */
getEntityResolver()214   public EntityResolver getEntityResolver()
215   {
216     return (entityResolver == base) ? null : entityResolver;
217   }
218 
219   /**
220    * <b>SAX1, SAX2</b>: Set the entity resolver for this parser.
221    * @param handler The object to receive entity events.
222    */
setEntityResolver(EntityResolver resolver)223   public void setEntityResolver(EntityResolver resolver)
224   {
225     if (resolver instanceof EntityResolver2)
226       {
227         resolver2 = (EntityResolver2) resolver;
228       }
229     else
230       {
231         resolver2 = null;
232       }
233     if (resolver == null)
234       {
235         resolver = base;
236       }
237     entityResolver = resolver;
238   }
239 
240   /**
241    * <b>SAX2</b>: Returns the object used to process declarations related
242    * to notations and unparsed entities.
243    */
getDTDHandler()244   public DTDHandler getDTDHandler()
245   {
246     return (dtdHandler == base) ? null : dtdHandler;
247   }
248 
249   /**
250    * <b>SAX1, SAX2</b>: Set the DTD handler for this parser.
251    * @param handler The object to receive DTD events.
252    */
setDTDHandler(DTDHandler handler)253   public void setDTDHandler(DTDHandler handler)
254   {
255     if (handler == null)
256       {
257         handler = base;
258       }
259     this.dtdHandler = handler;
260   }
261 
262 
263   /**
264    * <b>SAX1</b>: Set the document handler for this parser.  If a
265    * content handler was set, this document handler will supplant it.
266    * The parser is set to report all XML 1.0 names rather than to
267    * filter out "xmlns" attributes (the "namespace-prefixes" feature
268    * is set to true).
269    *
270    * @deprecated SAX2 programs should use the XMLReader interface
271    *  and a ContentHandler.
272    *
273    * @param handler The object to receive document events.
274    */
setDocumentHandler(DocumentHandler handler)275   public void setDocumentHandler(DocumentHandler handler)
276   {
277     contentHandler = new Adapter(handler);
278     xmlNames = true;
279   }
280 
281   /**
282    * <b>SAX2</b>: Returns the object used to report the logical
283    * content of an XML document.
284    */
getContentHandler()285   public ContentHandler getContentHandler()
286   {
287     return (contentHandler == base) ? null : contentHandler;
288   }
289 
290   /**
291    * <b>SAX2</b>: Assigns the object used to report the logical
292    * content of an XML document.  If a document handler was set,
293    * this content handler will supplant it (but XML 1.0 style name
294    * reporting may remain enabled).
295    */
setContentHandler(ContentHandler handler)296   public void setContentHandler(ContentHandler handler)
297   {
298     if (handler == null)
299       {
300         handler = base;
301       }
302     contentHandler = handler;
303   }
304 
305   /**
306    * <b>SAX1, SAX2</b>: Set the error handler for this parser.
307    * @param handler The object to receive error events.
308    */
setErrorHandler(ErrorHandler handler)309   public void setErrorHandler(ErrorHandler handler)
310   {
311     if (handler == null)
312       {
313         handler = base;
314       }
315     this.errorHandler = handler;
316   }
317 
318   /**
319    * <b>SAX2</b>: Returns the object used to receive callbacks for XML
320    * errors of all levels (fatal, nonfatal, warning); this is never null;
321    */
getErrorHandler()322   public ErrorHandler getErrorHandler()
323   {
324     return (errorHandler == base) ? null : errorHandler;
325   }
326 
327   /**
328    * <b>SAX1, SAX2</b>: Auxiliary API to parse an XML document, used mostly
329    * when no URI is available.
330    * If you want anything useful to happen, you should set
331    * at least one type of handler.
332    * @param source The XML input source.  Don't set 'encoding' unless
333    *  you know for a fact that it's correct.
334    * @see #setEntityResolver
335    * @see #setDTDHandler
336    * @see #setContentHandler
337    * @see #setErrorHandler
338    * @exception SAXException The handlers may throw any SAXException,
339    *  and the parser normally throws SAXParseException objects.
340    * @exception IOException IOExceptions are normally through through
341    *  the parser if there are problems reading the source document.
342    */
parse(InputSource source)343   public void parse(InputSource source)
344     throws SAXException, IOException
345   {
346     synchronized (base)
347       {
348         parser = new XmlParser();
349         if (namespaces)
350           {
351             prefixStack = new NamespaceSupport();
352           }
353         else if (!xmlNames)
354           {
355             throw new IllegalStateException();
356           }
357         parser.setHandler(this);
358 
359         try
360           {
361             Reader r = source.getCharacterStream();
362             InputStream in = source.getByteStream();
363 
364             parser.doParse(source.getSystemId(),
365                            source.getPublicId(),
366                            r,
367                            in,
368                            source.getEncoding());
369           }
370         catch (SAXException e)
371           {
372             throw e;
373           }
374         catch (IOException e)
375           {
376             throw e;
377           }
378         catch (RuntimeException e)
379           {
380             throw e;
381           }
382         catch (Exception e)
383           {
384             throw new SAXParseException(e.getMessage(), this, e);
385           }
386         finally
387           {
388             contentHandler.endDocument();
389             reset();
390           }
391       }
392   }
393 
394   /**
395    * <b>SAX1, SAX2</b>: Preferred API to parse an XML document, using a
396    * system identifier (URI).
397    */
parse(String systemId)398   public void parse(String systemId)
399     throws SAXException, IOException
400   {
401     parse(new InputSource(systemId));
402   }
403 
404   //
405   // Implementation of SAX2 "XMLReader" interface
406   //
407   static final String FEATURE = "http://xml.org/sax/features/";
408   static final String PROPERTY = "http://xml.org/sax/properties/";
409 
410   /**
411    * <b>SAX2</b>: Tells the value of the specified feature flag.
412    *
413    * @exception SAXNotRecognizedException thrown if the feature flag
414    *  is neither built in, nor yet assigned.
415    */
getFeature(String featureId)416   public boolean getFeature(String featureId)
417     throws SAXNotRecognizedException, SAXNotSupportedException
418   {
419     if ((FEATURE + "validation").equals(featureId))
420       {
421         return false;
422       }
423 
424     // external entities (both types) are optionally included
425     if ((FEATURE + "external-general-entities").equals(featureId))
426       {
427         return extGE;
428       }
429     if ((FEATURE + "external-parameter-entities").equals(featureId))
430       {
431         return extPE;
432       }
433 
434     // element/attribute names are as written in document; no mangling
435     if ((FEATURE + "namespace-prefixes").equals(featureId))
436       {
437         return xmlNames;
438       }
439 
440     // report element/attribute namespaces?
441     if ((FEATURE + "namespaces").equals(featureId))
442       {
443         return namespaces;
444       }
445 
446     // all PEs and GEs are reported
447     if ((FEATURE + "lexical-handler/parameter-entities").equals(featureId))
448       {
449         return true;
450       }
451 
452     // default is true
453     if ((FEATURE + "string-interning").equals(featureId))
454       {
455         return stringInterning;
456       }
457 
458     // EXTENSIONS 1.1
459 
460     // always returns isSpecified info
461     if ((FEATURE + "use-attributes2").equals(featureId))
462       {
463         return true;
464       }
465 
466     // meaningful between startDocument/endDocument
467     if ((FEATURE + "is-standalone").equals(featureId))
468       {
469         if (parser == null)
470           {
471             throw new SAXNotSupportedException(featureId);
472           }
473         return parser.isStandalone();
474       }
475 
476     // optionally don't absolutize URIs in declarations
477     if ((FEATURE + "resolve-dtd-uris").equals(featureId))
478       {
479         return resolveAll;
480       }
481 
482     // optionally use resolver2 interface methods, if possible
483     if ((FEATURE + "use-entity-resolver2").equals(featureId))
484       {
485         return useResolver2;
486       }
487 
488     throw new SAXNotRecognizedException(featureId);
489   }
490 
491   // package private
getDeclHandler()492   DeclHandler getDeclHandler()
493   {
494     return declHandler;
495   }
496 
497   // package private
resolveURIs()498   boolean resolveURIs()
499   {
500     return resolveAll;
501   }
502 
503   /**
504    * <b>SAX2</b>:  Returns the specified property.
505    *
506    * @exception SAXNotRecognizedException thrown if the property value
507    *  is neither built in, nor yet stored.
508    */
getProperty(String propertyId)509   public Object getProperty(String propertyId)
510     throws SAXNotRecognizedException
511   {
512     if ((PROPERTY + "declaration-handler").equals(propertyId))
513       {
514         return (declHandler == base) ? null : declHandler;
515       }
516 
517     if ((PROPERTY + "lexical-handler").equals(propertyId))
518       {
519         return (lexicalHandler == base) ? null : lexicalHandler;
520       }
521 
522     // unknown properties
523     throw new SAXNotRecognizedException(propertyId);
524   }
525 
526   /**
527    * <b>SAX2</b>:  Sets the state of feature flags in this parser.  Some
528    * built-in feature flags are mutable.
529    */
setFeature(String featureId, boolean value)530   public void setFeature(String featureId, boolean value)
531     throws SAXNotRecognizedException, SAXNotSupportedException
532   {
533     boolean state;
534 
535     // Features with a defined value, we just change it if we can.
536     state = getFeature (featureId);
537 
538     if (state == value)
539       {
540         return;
541       }
542     if (parser != null)
543       {
544         throw new SAXNotSupportedException("not while parsing");
545       }
546 
547     if ((FEATURE + "namespace-prefixes").equals(featureId))
548       {
549         // in this implementation, this only affects xmlns reporting
550         xmlNames = value;
551         // forcibly prevent illegal parser state
552         if (!xmlNames)
553           {
554             namespaces = true;
555           }
556         return;
557       }
558 
559     if ((FEATURE + "namespaces").equals(featureId))
560       {
561         namespaces = value;
562         // forcibly prevent illegal parser state
563         if (!namespaces)
564           {
565             xmlNames = true;
566           }
567         return;
568       }
569 
570     if ((FEATURE + "external-general-entities").equals(featureId))
571       {
572         extGE = value;
573         return;
574       }
575     if ((FEATURE + "external-parameter-entities").equals(featureId))
576       {
577         extPE = value;
578         return;
579       }
580     if ((FEATURE + "resolve-dtd-uris").equals(featureId))
581       {
582         resolveAll = value;
583         return;
584       }
585 
586     if ((FEATURE + "use-entity-resolver2").equals(featureId))
587       {
588         useResolver2 = value;
589         return;
590       }
591 
592     throw new SAXNotRecognizedException(featureId);
593   }
594 
595   /**
596    * <b>SAX2</b>:  Assigns the specified property.  Like SAX1 handlers,
597    * these may be changed at any time.
598    */
setProperty(String propertyId, Object value)599   public void setProperty(String propertyId, Object value)
600     throws SAXNotRecognizedException, SAXNotSupportedException
601   {
602     // see if the property is recognized
603     getProperty(propertyId);
604 
605     // Properties with a defined value, we just change it if we can.
606 
607     if ((PROPERTY + "declaration-handler").equals(propertyId))
608       {
609         if (value == null)
610           {
611             declHandler = base;
612           }
613         else if (!(value instanceof DeclHandler))
614           {
615             throw new SAXNotSupportedException(propertyId);
616           }
617         else
618           {
619             declHandler = (DeclHandler) value;
620           }
621         return ;
622       }
623 
624     if ((PROPERTY + "lexical-handler").equals(propertyId))
625       {
626         if (value == null)
627           {
628             lexicalHandler = base;
629           }
630         else if (!(value instanceof LexicalHandler))
631           {
632             throw new SAXNotSupportedException(propertyId);
633           }
634         else
635           {
636             lexicalHandler = (LexicalHandler) value;
637           }
638         return;
639       }
640 
641     throw new SAXNotSupportedException(propertyId);
642   }
643 
644   //
645   // This is where the driver receives XmlParser callbacks and translates
646   // them into SAX callbacks.  Some more callbacks have been added for
647   // SAX2 support.
648   //
649 
startDocument()650   void startDocument()
651     throws SAXException
652   {
653     contentHandler.setDocumentLocator(this);
654     contentHandler.startDocument();
655     attributesList.clear();
656   }
657 
skippedEntity(String name)658   void skippedEntity(String name)
659     throws SAXException
660   {
661     contentHandler.skippedEntity(name);
662   }
663 
getExternalSubset(String name, String baseURI)664   InputSource getExternalSubset(String name, String baseURI)
665     throws SAXException, IOException
666   {
667     if (resolver2 == null || !useResolver2 || !extPE)
668       {
669         return null;
670       }
671     return resolver2.getExternalSubset(name, baseURI);
672   }
673 
resolveEntity(boolean isPE, String name, InputSource in, String baseURI)674   InputSource resolveEntity(boolean isPE, String name,
675                             InputSource in, String baseURI)
676     throws SAXException, IOException
677   {
678     InputSource  source;
679 
680     // external entities might be skipped
681     if (isPE && !extPE)
682       {
683         return null;
684       }
685     if (!isPE && !extGE)
686       {
687         return null;
688       }
689 
690     // ... or not
691     lexicalHandler.startEntity(name);
692     if (resolver2 != null && useResolver2)
693       {
694         source = resolver2.resolveEntity(name, in.getPublicId(),
695                                          baseURI, in.getSystemId());
696         if (source == null)
697           {
698             in.setSystemId(absolutize(baseURI,
699                                       in.getSystemId(), false));
700             source = in;
701           }
702       }
703     else
704       {
705         in.setSystemId(absolutize(baseURI,
706                                   in.getSystemId(),
707                                   entityResolver != base));
708         source = entityResolver.resolveEntity(in.getPublicId(),
709                                               in.getSystemId());
710         if (source == null)
711           {
712             source = in;
713           }
714       }
715     startExternalEntity(name, source.getSystemId(), true);
716     return source;
717   }
718 
719   // absolutize a system ID relative to the specified base URI
720   // (temporarily) package-visible for external entity decls
absolutize(String baseURI, String systemId, boolean nice)721   String absolutize(String baseURI, String systemId, boolean nice)
722     throws MalformedURLException, SAXException
723   {
724     // FIXME normalize system IDs -- when?
725     // - Convert to UTF-8
726     // - Map reserved and non-ASCII characters to %HH
727 
728     try
729       {
730         if (baseURI == null)
731           {
732             if (XmlParser.uriWarnings)
733               {
734                 warn ("No base URI; hope this SYSTEM id is absolute: "
735                       + systemId);
736               }
737             return new URL(systemId).toString();
738           }
739         else
740           {
741             return new URL(new URL(baseURI), systemId).toString();
742           }
743       }
744     catch (MalformedURLException e)
745       {
746         // Let unknown URI schemes pass through unless we need
747         // the JVM to map them to i/o streams for us...
748         if (!nice)
749           {
750             throw e;
751           }
752 
753         // sometimes sysids for notations or unparsed entities
754         // aren't really URIs...
755         warn("Can't absolutize SYSTEM id: " + e.getMessage());
756         return systemId;
757       }
758   }
759 
startExternalEntity(String name, String systemId, boolean stackOnly)760   void startExternalEntity(String name, String systemId, boolean stackOnly)
761     throws SAXException
762   {
763     // The following warning was deleted because the application has the
764     // option of not setting systemId. Sun's JAXP or Xerces seems to
765     // ignore this case.
766     /*
767        if (systemId == null)
768        warn ("URI was not reported to parser for entity " + name);
769      */
770     if (!stackOnly)  // spliced [dtd] needs startEntity
771       {
772         lexicalHandler.startEntity(name);
773       }
774     entityStack.push(systemId);
775   }
776 
endExternalEntity(String name)777   void endExternalEntity(String name)
778     throws SAXException
779   {
780     if (!"[document]".equals(name))
781       {
782         lexicalHandler.endEntity(name);
783       }
784     entityStack.pop();
785   }
786 
startInternalEntity(String name)787   void startInternalEntity(String name)
788     throws SAXException
789   {
790     lexicalHandler.startEntity(name);
791   }
792 
endInternalEntity(String name)793   void endInternalEntity(String name)
794     throws SAXException
795   {
796     lexicalHandler.endEntity(name);
797   }
798 
doctypeDecl(String name, String publicId, String systemId)799   void doctypeDecl(String name, String publicId, String systemId)
800     throws SAXException
801   {
802     lexicalHandler.startDTD(name, publicId, systemId);
803 
804     // ... the "name" is a declaration and should be given
805     // to the DeclHandler (but sax2 doesn't).
806 
807     // the IDs for the external subset are lexical details,
808     // as are the contents of the internal subset; but sax2
809     // doesn't provide the internal subset "pre-parse"
810   }
811 
notationDecl(String name, String publicId, String systemId, String baseUri)812   void notationDecl(String name, String publicId, String systemId,
813                     String baseUri)
814     throws SAXException
815   {
816     try
817       {
818         dtdHandler.notationDecl(name, publicId,
819                                 (resolveAll && systemId != null)
820                                 ? absolutize(baseUri, systemId, true)
821                                 : systemId);
822       }
823     catch (IOException e)
824       {
825         // "can't happen"
826         throw new SAXParseException(e.getMessage(), this, e);
827       }
828   }
829 
unparsedEntityDecl(String name, String publicId, String systemId, String baseUri, String notation)830   void unparsedEntityDecl(String name, String publicId, String systemId,
831                           String baseUri, String notation)
832     throws SAXException
833   {
834     try
835       {
836         dtdHandler.unparsedEntityDecl(name, publicId,
837                                       resolveAll
838                                       ? absolutize(baseUri, systemId, true)
839                                       : systemId,
840                                       notation);
841       }
842     catch (IOException e)
843       {
844         // "can't happen"
845         throw new SAXParseException(e.getMessage(), this, e);
846       }
847   }
848 
endDoctype()849   void endDoctype()
850     throws SAXException
851   {
852     lexicalHandler.endDTD();
853   }
854 
declarePrefix(String prefix, String uri)855   private void declarePrefix(String prefix, String uri)
856     throws SAXException
857   {
858     int index = uri.indexOf(':');
859 
860     // many versions of nwalsh docbook stylesheets
861     // have bogus URLs; so this can't be an error...
862     if (index < 1 && uri.length() != 0)
863       {
864         warn("relative URI for namespace: " + uri);
865       }
866 
867     // FIXME:  char [0] must be ascii alpha; chars [1..index]
868     // must be ascii alphanumeric or in "+-." [RFC 2396]
869 
870     //Namespace Constraints
871     //name for xml prefix must be http://www.w3.org/XML/1998/namespace
872     boolean prefixEquality = prefix.equals("xml");
873     boolean uriEquality = uri.equals("http://www.w3.org/XML/1998/namespace");
874     if ((prefixEquality || uriEquality) && !(prefixEquality && uriEquality))
875       {
876         fatal("xml is by definition bound to the namespace name " +
877               "http://www.w3.org/XML/1998/namespace");
878       }
879 
880     //xmlns prefix declaration is illegal but xml prefix declaration is llegal...
881     if (prefixEquality && uriEquality)
882       {
883         return;
884       }
885 
886     //name for xmlns prefix must be http://www.w3.org/2000/xmlns/
887     prefixEquality = prefix.equals("xmlns");
888     uriEquality = uri.equals("http://www.w3.org/2000/xmlns/");
889     if ((prefixEquality || uriEquality) && !(prefixEquality && uriEquality))
890       {
891         fatal("http://www.w3.org/2000/xmlns/ is by definition bound" +
892               " to prefix xmlns");
893       }
894 
895     //even if the uri is http://www.w3.org/2000/xmlns/
896     // it is illegal to declare it
897     if (prefixEquality && uriEquality)
898       {
899         fatal ("declaring the xmlns prefix is illegal");
900       }
901 
902     uri = uri.intern();
903     prefixStack.declarePrefix(prefix, uri);
904     contentHandler.startPrefixMapping(prefix, uri);
905   }
906 
attribute(String qname, String value, boolean isSpecified)907   void attribute(String qname, String value, boolean isSpecified)
908     throws SAXException
909   {
910     if (!attributes)
911       {
912         attributes = true;
913         if (namespaces)
914           {
915             prefixStack.pushContext();
916           }
917       }
918 
919     // process namespace decls immediately;
920     // then maybe forget this as an attribute
921     if (namespaces)
922       {
923         int index;
924 
925         // default NS declaration?
926         if (stringInterning)
927           {
928             if ("xmlns" == qname)
929               {
930                 declarePrefix("", value);
931                 if (!xmlNames)
932                   {
933                     return;
934                   }
935               }
936             // NS prefix declaration?
937             else if ((index = qname.indexOf(':')) == 5
938                      && qname.startsWith("xmlns"))
939               {
940                 String prefix = qname.substring(6);
941 
942                 if (prefix.equals(""))
943                   {
944                     fatal("missing prefix " +
945                           "in namespace declaration attribute");
946                   }
947                 if (value.length() == 0)
948                   {
949                     verror("missing URI in namespace declaration attribute: "
950                            + qname);
951                   }
952                 else
953                   {
954                     declarePrefix(prefix, value);
955                   }
956                 if (!xmlNames)
957                   {
958                     return;
959                   }
960               }
961           }
962         else
963           {
964             if ("xmlns".equals(qname))
965               {
966                 declarePrefix("", value);
967                 if (!xmlNames)
968                   {
969                     return;
970                   }
971               }
972             // NS prefix declaration?
973             else if ((index = qname.indexOf(':')) == 5
974                      && qname.startsWith("xmlns"))
975               {
976                 String prefix = qname.substring(6);
977 
978                 if (value.length() == 0)
979                   {
980                     verror("missing URI in namespace decl attribute: "
981                            + qname);
982                   }
983                 else
984                   {
985                     declarePrefix(prefix, value);
986                   }
987                 if (!xmlNames)
988                   {
989                     return;
990                   }
991               }
992           }
993       }
994     // remember this attribute ...
995     attributeCount++;
996 
997     // attribute type comes from querying parser's DTD records
998     attributesList.add(new Attribute(qname, value, isSpecified));
999 
1000   }
1001 
startElement(String elname)1002   void startElement(String elname)
1003     throws SAXException
1004   {
1005     ContentHandler handler = contentHandler;
1006 
1007     //
1008     // NOTE:  this implementation of namespace support adds something
1009     // like six percent to parsing CPU time, in a large (~50 MB)
1010     // document that doesn't use namespaces at all.  (Measured by PC
1011     // sampling, with a bug where endElement processing was omitted.)
1012     // [Measurement referred to older implementation, older JVM ...]
1013     //
1014     // It ought to become notably faster in such cases.  Most
1015     // costs are the prefix stack calling Hashtable.get() (2%),
1016     // String.hashCode() (1.5%) and about 1.3% each for pushing
1017     // the context, and two chunks of name processing.
1018     //
1019 
1020     if (!attributes)
1021       {
1022         if (namespaces)
1023           {
1024             prefixStack.pushContext();
1025           }
1026       }
1027     else if (namespaces)
1028       {
1029 
1030         // now we can patch up namespace refs; we saw all the
1031         // declarations, so now we'll do the Right Thing
1032         Iterator itt = attributesList.iterator();
1033         while (itt.hasNext())
1034           {
1035             Attribute attribute = (Attribute) itt.next();
1036             String qname = attribute.name;
1037             int index;
1038 
1039             // default NS declaration?
1040             if (stringInterning)
1041               {
1042                 if ("xmlns" == qname)
1043                   {
1044                     continue;
1045                   }
1046               }
1047             else
1048               {
1049                 if ("xmlns".equals(qname))
1050                   {
1051                     continue;
1052                   }
1053               }
1054             //Illegal in the new Namespaces Draft
1055             //should it be only in 1.1 docs??
1056             if (qname.equals (":"))
1057               {
1058                 fatal("namespace names consisting of a single colon " +
1059                       "character are invalid");
1060               }
1061             index = qname.indexOf(':');
1062 
1063             // NS prefix declaration?
1064             if (index == 5 && qname.startsWith("xmlns"))
1065               {
1066                 continue;
1067               }
1068 
1069             // it's not a NS decl; patch namespace info items
1070             if (prefixStack.processName(qname, nsTemp, true) == null)
1071               {
1072                 fatal("undeclared attribute prefix in: " + qname);
1073               }
1074             else
1075               {
1076                 attribute.nameSpace = nsTemp[0];
1077                 attribute.localName = nsTemp[1];
1078               }
1079           }
1080       }
1081 
1082     // save element name so attribute callbacks work
1083     elementName = elname;
1084     if (namespaces)
1085       {
1086         if (prefixStack.processName(elname, nsTemp, false) == null)
1087           {
1088             fatal("undeclared element prefix in: " + elname);
1089             nsTemp[0] = nsTemp[1] = "";
1090           }
1091         handler.startElement(nsTemp[0], nsTemp[1], elname, this);
1092       }
1093     else
1094       {
1095         handler.startElement("", "", elname, this);
1096       }
1097     // elementName = null;
1098 
1099     // elements with no attributes are pretty common!
1100     if (attributes)
1101       {
1102         attributesList.clear();
1103         attributeCount = 0;
1104         attributes = false;
1105       }
1106   }
1107 
endElement(String elname)1108   void endElement(String elname)
1109     throws SAXException
1110   {
1111     ContentHandler handler = contentHandler;
1112 
1113     if (!namespaces)
1114       {
1115         handler.endElement("", "", elname);
1116         return;
1117       }
1118     prefixStack.processName(elname, nsTemp, false);
1119     handler.endElement(nsTemp[0], nsTemp[1], elname);
1120 
1121     Enumeration prefixes = prefixStack.getDeclaredPrefixes();
1122 
1123     while (prefixes.hasMoreElements())
1124       {
1125         handler.endPrefixMapping((String) prefixes.nextElement());
1126       }
1127     prefixStack.popContext();
1128   }
1129 
startCDATA()1130   void startCDATA()
1131     throws SAXException
1132   {
1133     lexicalHandler.startCDATA();
1134   }
1135 
charData(char[] ch, int start, int length)1136   void charData(char[] ch, int start, int length)
1137     throws SAXException
1138   {
1139     contentHandler.characters(ch, start, length);
1140   }
1141 
endCDATA()1142   void endCDATA()
1143     throws SAXException
1144   {
1145     lexicalHandler.endCDATA();
1146   }
1147 
ignorableWhitespace(char[] ch, int start, int length)1148   void ignorableWhitespace(char[] ch, int start, int length)
1149     throws SAXException
1150   {
1151     contentHandler.ignorableWhitespace(ch, start, length);
1152   }
1153 
processingInstruction(String target, String data)1154   void processingInstruction(String target, String data)
1155     throws SAXException
1156   {
1157     contentHandler.processingInstruction(target, data);
1158   }
1159 
comment(char[] ch, int start, int length)1160   void comment(char[] ch, int start, int length)
1161     throws SAXException
1162   {
1163     if (lexicalHandler != base)
1164       {
1165         lexicalHandler.comment(ch, start, length);
1166       }
1167   }
1168 
fatal(String message)1169   void fatal(String message)
1170     throws SAXException
1171   {
1172     SAXParseException fatal;
1173 
1174     fatal = new SAXParseException(message, this);
1175     errorHandler.fatalError(fatal);
1176 
1177     // Even if the application can continue ... we can't!
1178     throw fatal;
1179   }
1180 
1181   // We can safely report a few validity errors that
1182   // make layered SAX2 DTD validation more conformant
verror(String message)1183   void verror(String message)
1184     throws SAXException
1185   {
1186     SAXParseException err;
1187 
1188     err = new SAXParseException(message, this);
1189     errorHandler.error(err);
1190   }
1191 
warn(String message)1192   void warn(String message)
1193     throws SAXException
1194   {
1195     SAXParseException err;
1196 
1197     err = new SAXParseException(message, this);
1198     errorHandler.warning(err);
1199   }
1200 
1201   //
1202   // Implementation of org.xml.sax.Attributes.
1203   //
1204 
1205   /**
1206    * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1207    * (don't invoke on parser);
1208    */
getLength()1209   public int getLength()
1210   {
1211     return attributesList.size();
1212   }
1213 
1214   /**
1215    * <b>SAX2 Attributes</b> method (don't invoke on parser);
1216    */
getURI(int index)1217   public String getURI(int index)
1218   {
1219     if (index < 0 || index >= attributesList.size())
1220       {
1221         return null;
1222       }
1223     return ((Attribute) attributesList.get(index)).nameSpace;
1224   }
1225 
1226   /**
1227    * <b>SAX2 Attributes</b> method (don't invoke on parser);
1228    */
getLocalName(int index)1229   public String getLocalName(int index)
1230   {
1231     if (index < 0 || index >= attributesList.size())
1232       {
1233         return null;
1234       }
1235     Attribute attr = (Attribute) attributesList.get(index);
1236     // FIXME attr.localName is sometimes null, why?
1237     if (namespaces && attr.localName == null)
1238       {
1239         // XXX fix this here for now
1240         int ci = attr.name.indexOf(':');
1241         attr.localName = (ci == -1) ? attr.name :
1242           attr.name.substring(ci + 1);
1243       }
1244     return (attr.localName == null) ? "" : attr.localName;
1245   }
1246 
1247   /**
1248    * <b>SAX2 Attributes</b> method (don't invoke on parser);
1249    */
getQName(int index)1250   public String getQName(int index)
1251   {
1252     if (index < 0 || index >= attributesList.size())
1253       {
1254       return null;
1255       }
1256     Attribute attr = (Attribute) attributesList.get(index);
1257     return (attr.name == null) ? "" : attr.name;
1258   }
1259 
1260   /**
1261    * <b>SAX1 AttributeList</b> method (don't invoke on parser);
1262    */
getName(int index)1263   public String getName(int index)
1264   {
1265     return getQName(index);
1266   }
1267 
1268   /**
1269    * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1270    * (don't invoke on parser);
1271    */
getType(int index)1272   public String getType(int index)
1273   {
1274     if (index < 0 || index >= attributesList.size())
1275       {
1276         return null;
1277       }
1278     String type = parser.getAttributeType(elementName, getQName(index));
1279     if (type == null)
1280       {
1281         return "CDATA";
1282       }
1283     // ... use DeclHandler.attributeDecl to see enumerations
1284     if (type == "ENUMERATION")
1285       {
1286         return "NMTOKEN";
1287       }
1288     return type;
1289   }
1290 
1291   /**
1292    * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1293    * (don't invoke on parser);
1294    */
getValue(int index)1295   public String getValue(int index)
1296   {
1297     if (index < 0 || index >= attributesList.size())
1298       {
1299         return null;
1300       }
1301     return ((Attribute) attributesList.get(index)).value;
1302   }
1303 
1304   /**
1305    * <b>SAX2 Attributes</b> method (don't invoke on parser);
1306    */
getIndex(String uri, String local)1307   public int getIndex(String uri, String local)
1308     {
1309       int length = getLength();
1310 
1311       for (int i = 0; i < length; i++)
1312         {
1313           if (!getURI(i).equals(uri))
1314             {
1315               continue;
1316             }
1317           if (getLocalName(i).equals(local))
1318             {
1319               return i;
1320             }
1321         }
1322       return -1;
1323   }
1324 
1325   /**
1326    * <b>SAX2 Attributes</b> method (don't invoke on parser);
1327    */
getIndex(String xmlName)1328   public int getIndex(String xmlName)
1329   {
1330     int length = getLength();
1331 
1332     for (int i = 0; i < length; i++)
1333       {
1334         if (getQName(i).equals(xmlName))
1335           {
1336             return i;
1337           }
1338       }
1339     return -1;
1340   }
1341 
1342   /**
1343    * <b>SAX2 Attributes</b> method (don't invoke on parser);
1344    */
getType(String uri, String local)1345   public String getType(String uri, String local)
1346   {
1347     int index = getIndex(uri, local);
1348 
1349     if (index < 0)
1350       {
1351         return null;
1352       }
1353     return getType(index);
1354   }
1355 
1356   /**
1357    * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1358    * (don't invoke on parser);
1359    */
getType(String xmlName)1360   public String getType(String xmlName)
1361   {
1362     int index = getIndex(xmlName);
1363 
1364     if (index < 0)
1365       {
1366         return null;
1367       }
1368     return getType(index);
1369   }
1370 
1371   /**
1372    * <b>SAX Attributes</b> method (don't invoke on parser);
1373    */
getValue(String uri, String local)1374   public String getValue(String uri, String local)
1375   {
1376     int index = getIndex(uri, local);
1377 
1378     if (index < 0)
1379       {
1380         return null;
1381       }
1382     return getValue(index);
1383   }
1384 
1385   /**
1386    * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1387    * (don't invoke on parser);
1388    */
getValue(String xmlName)1389   public String getValue(String xmlName)
1390   {
1391     int index = getIndex(xmlName);
1392 
1393     if (index < 0)
1394       {
1395         return null;
1396       }
1397     return getValue(index);
1398   }
1399 
1400   //
1401   // Implementation of org.xml.sax.ext.Attributes2
1402   //
1403 
1404   /** @return false unless the attribute was declared in the DTD.
1405    * @throws java.lang.ArrayIndexOutOfBoundsException
1406    *   When the supplied index does not identify an attribute.
1407    */
isDeclared(int index)1408   public boolean isDeclared(int index)
1409   {
1410     if (index < 0 || index >= attributeCount)
1411       {
1412         throw new ArrayIndexOutOfBoundsException();
1413       }
1414     String type = parser.getAttributeType(elementName, getQName(index));
1415     return (type != null);
1416   }
1417 
1418   /** @return false unless the attribute was declared in the DTD.
1419    * @throws java.lang.IllegalArgumentException
1420    *   When the supplied names do not identify an attribute.
1421    */
isDeclared(String qName)1422   public boolean isDeclared(String qName)
1423   {
1424     int index = getIndex(qName);
1425     if (index < 0)
1426       {
1427         throw new IllegalArgumentException();
1428       }
1429     String type = parser.getAttributeType(elementName, qName);
1430     return (type != null);
1431   }
1432 
1433   /** @return false unless the attribute was declared in the DTD.
1434    * @throws java.lang.IllegalArgumentException
1435    *   When the supplied names do not identify an attribute.
1436    */
isDeclared(String uri, String localName)1437   public boolean isDeclared(String uri, String localName)
1438   {
1439     int index = getIndex(uri, localName);
1440     return isDeclared(index);
1441   }
1442 
1443   /**
1444    * <b>SAX-ext Attributes2</b> method (don't invoke on parser);
1445    */
isSpecified(int index)1446   public boolean isSpecified(int index)
1447   {
1448     return ((Attribute) attributesList.get(index)).specified;
1449   }
1450 
1451   /**
1452    * <b>SAX-ext Attributes2</b> method (don't invoke on parser);
1453    */
isSpecified(String uri, String local)1454   public boolean isSpecified(String uri, String local)
1455   {
1456     int index = getIndex (uri, local);
1457     return isSpecified(index);
1458   }
1459 
1460   /**
1461    * <b>SAX-ext Attributes2</b> method (don't invoke on parser);
1462    */
isSpecified(String xmlName)1463   public boolean isSpecified(String xmlName)
1464   {
1465     int index = getIndex (xmlName);
1466     return isSpecified(index);
1467   }
1468 
1469   //
1470   // Implementation of org.xml.sax.Locator.
1471   //
1472 
1473   /**
1474    * <b>SAX Locator</b> method (don't invoke on parser);
1475    */
getPublicId()1476   public String getPublicId()
1477   {
1478     return null;   // FIXME track public IDs too
1479   }
1480 
1481   /**
1482    * <b>SAX Locator</b> method (don't invoke on parser);
1483    */
getSystemId()1484   public String getSystemId()
1485   {
1486     if (entityStack.empty())
1487       {
1488         return null;
1489       }
1490     else
1491       {
1492         return (String) entityStack.peek();
1493       }
1494   }
1495 
1496   /**
1497    * <b>SAX Locator</b> method (don't invoke on parser);
1498    */
getLineNumber()1499   public int getLineNumber()
1500   {
1501     return parser.getLineNumber();
1502   }
1503 
1504   /**
1505    * <b>SAX Locator</b> method (don't invoke on parser);
1506    */
getColumnNumber()1507   public int getColumnNumber()
1508   {
1509     return parser.getColumnNumber();
1510   }
1511 
1512   // adapter between SAX2 content handler and SAX1 document handler callbacks
1513   private static class Adapter
1514     implements ContentHandler
1515   {
1516 
1517     private DocumentHandler docHandler;
1518 
Adapter(DocumentHandler dh)1519     Adapter(DocumentHandler dh)
1520     {
1521       docHandler = dh;
1522     }
1523 
setDocumentLocator(Locator l)1524     public void setDocumentLocator(Locator l)
1525     {
1526       docHandler.setDocumentLocator(l);
1527     }
1528 
startDocument()1529     public void startDocument()
1530       throws SAXException
1531     {
1532       docHandler.startDocument();
1533     }
1534 
processingInstruction(String target, String data)1535     public void processingInstruction(String target, String data)
1536       throws SAXException
1537     {
1538       docHandler.processingInstruction(target, data);
1539     }
1540 
startPrefixMapping(String prefix, String uri)1541     public void startPrefixMapping(String prefix, String uri)
1542     {
1543       /* ignored */
1544     }
1545 
startElement(String namespace, String local, String name, Attributes attrs)1546     public void startElement(String namespace,
1547                              String local,
1548                              String name,
1549                              Attributes attrs)
1550       throws SAXException
1551     {
1552       docHandler.startElement(name, (AttributeList) attrs);
1553     }
1554 
characters(char[] buf, int offset, int len)1555     public void characters(char[] buf, int offset, int len)
1556       throws SAXException
1557     {
1558       docHandler.characters(buf, offset, len);
1559     }
1560 
ignorableWhitespace(char[] buf, int offset, int len)1561     public void ignorableWhitespace(char[] buf, int offset, int len)
1562       throws SAXException
1563     {
1564       docHandler.ignorableWhitespace(buf, offset, len);
1565     }
1566 
skippedEntity(String name)1567     public void skippedEntity(String name)
1568     {
1569       /* ignored */
1570     }
1571 
endElement(String u, String l, String name)1572     public void endElement(String u, String l, String name)
1573       throws SAXException
1574     {
1575       docHandler.endElement(name);
1576     }
1577 
endPrefixMapping(String prefix)1578     public void endPrefixMapping(String prefix)
1579     {
1580       /* ignored */
1581     }
1582 
endDocument()1583     public void endDocument()
1584       throws SAXException
1585     {
1586       docHandler.endDocument();
1587     }
1588   }
1589 
1590   private static class Attribute
1591   {
1592 
1593     String name;
1594     String value;
1595     String nameSpace;
1596     String localName;
1597     boolean specified;
1598 
Attribute(String name, String value, boolean specified)1599     Attribute(String name, String value, boolean specified)
1600     {
1601       this.name = name;
1602       this.value = value;
1603       this.nameSpace = "";
1604       this.specified = specified;
1605     }
1606 
1607   }
1608 
1609 }
1610