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