001/*
002 * Copyright (c) 1999-2000 by David Brownell.  All Rights Reserved.
003 *
004 * This program is open source software; you may use, copy, modify, and
005 * redistribute it under the terms of the LICENSE with which it was
006 * originally distributed.
007 *
008 * This program is distributed in the hope that it will be useful,
009 * but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
011 * LICENSE for more details.
012 */
013
014//
015// Copyright (c) 1998 by Microstar Software Ltd.
016// From Microstar's README (the entire original license):
017//
018// AElfred is free for both commercial and non-commercial use and
019// redistribution, provided that Microstar's copyright and disclaimer are
020// retained intact.  You are free to modify AElfred for your own use and
021// to redistribute AElfred with your modifications, provided that the
022// modifications are clearly documented.
023//
024// This program is distributed in the hope that it will be useful, but
025// WITHOUT ANY WARRANTY; without even the implied warranty of
026// merchantability or fitness for a particular purpose.  Please use it AT
027// YOUR OWN RISK.
028//
029
030
031package org.dom4j.io.aelfred;
032
033import java.io.InputStreamReader;
034import java.io.IOException;
035import java.io.Reader;
036import java.util.ArrayList;
037import java.util.Enumeration;
038import java.util.HashMap;
039import java.util.Iterator;
040import java.util.List;
041import java.util.Locale;
042
043import org.xml.sax.*;
044import org.xml.sax.ext.*;
045import org.xml.sax.helpers.NamespaceSupport;
046
047//import org.brownell.xml.DefaultHandler;
048import org.dom4j.io.aelfred.DefaultHandler;
049
050
051// $Id: SAXDriver.java,v 1.4 2002/02/01 10:55:25 jstrachan Exp $
052
053/**
054 * An enhanced SAX2 version of Microstar's Ælfred XML parser.
055 * The enhancements primarily relate to significant improvements in
056 * conformance to the XML specification, and SAX2 support.  Performance
057 * has been improved.  However, the Ælfred proprietary APIs are
058 * no longer public.  See the package level documentation for more
059 * information.
060 *
061 * <table border="1" width='100%' cellpadding='3' cellspacing='0'>
062 * <tr bgcolor='#ccccff'>
063 *      <th><font size='+1'>Name</font></th>
064 *      <th><font size='+1'>Notes</font></th></tr>
065 *
066 * <tr><td colspan=2><center><em>Features ... URL prefix is
067 * <b>http://xml.org/sax/features/</b></em></center></td></tr>
068 *
069 * <tr><td>(URL)/external-general-entities</td>
070 *      <td>Value is fixed at <em>true</em></td></tr>
071 * <tr><td>(URL)/external-parameter-entities</td>
072 *      <td>Value is fixed at <em>true</em></td></tr>
073 * <tr><td>(URL)/namespace-prefixes</td>
074 *      <td>Value defaults to <em>false</em> (but XML 1.0 names are
075 *              always reported)</td></tr>
076 * <tr><td>(URL)/namespaces</td>
077 *      <td>Value defaults to <em>true</em></td></tr>
078 * <tr><td>(URL)/string-interning</td>
079 *      <td>Value is fixed at <em>true</em></td></tr>
080 * <tr><td>(URL)/validation</td>
081 *      <td>Value is fixed at <em>false</em></td></tr>
082 *
083 * <tr><td colspan=2><center><em>Handler Properties ... URL prefix is
084 * <b>http://xml.org/sax/properties/</b></em></center></td></tr>
085 *
086 * <tr><td>(URL)/declaration-handler</td>
087 *      <td>A declaration handler may be provided.  Declaration of general
088 *      entities is exposed, but not parameter entities; none of the entity
089 *      names reported here will begin with "%". </td></tr>
090 * <tr><td>(URL)/lexical-handler</td>
091 *      <td>A lexical handler may be provided.  Entity boundaries and
092 *      comments are not exposed; only CDATA sections and the start/end of
093 *      the DTD (the internal subset is not detectible). </td></tr>
094 * </table>
095 *
096 * <p> Note that the declaration handler doesn't suffice for showing all
097 * the logical structure
098 * of the DTD; it doesn't expose the name of the root element, or the values
099 * that are permitted in a NOTATIONS attribute.  (The former is exposed as
100 * lexical data, and SAX2 beta doesn't expose the latter.)
101 *
102 * <p> Although support for several features and properties is "built in"
103 * to this parser, it support all others by storing the assigned values
104 * and returning them.
105 *
106 * <p>This parser currently implements the SAX1 Parser API, but
107 * it may not continue to do so in the future.
108 *
109 * @author Written by David Megginson &lt;dmeggins@microstar.com&gt;
110 *      (version 1.2a from Microstar)
111 * @author Updated by David Brownell &lt;david-b@pacbell.net&gt;
112 * @version $Date: 2002/02/01 10:55:25 $
113 * @see org.xml.sax.Parser
114 */
115final public class SAXDriver
116    implements Locator, Attributes, XMLReader, Parser, AttributeList
117{
118    private final DefaultHandler        base = new DefaultHandler ();
119    private XmlParser                   parser;
120
121    private EntityResolver              entityResolver = base;
122    private ContentHandler              contentHandler = base;
123    private DTDHandler                  dtdHandler = base;
124    private ErrorHandler                errorHandler = base;
125    private DeclHandler                 declHandler = base;
126    private LexicalHandler              lexicalHandler = base;
127
128    private String                      elementName = null;
129    private ArrayList                   entityStack = new ArrayList ();
130
131    private ArrayList                   attributeNames = new ArrayList ();
132    private ArrayList                   attributeNamespaces = new ArrayList ();
133    private ArrayList                   attributeLocalNames = new ArrayList ();
134    private ArrayList                   attributeValues = new ArrayList ();
135
136    private boolean                     namespaces = true;
137    private boolean                     xmlNames = false;
138    private boolean         nspending = false; // indicates an attribute was read before its
139                                               // namespace declaration
140
141    private int                         attributeCount = 0;
142    private String                      nsTemp [] = new String [3];
143    private NamespaceSupport            prefixStack = new NamespaceSupport ();
144
145    private HashMap                     features;
146    private HashMap                     properties;
147
148
149    //
150    // Constructor.
151    //
152
153    /** Constructs a SAX Parser.  */
154    public SAXDriver () {}
155
156
157    //
158    // Implementation of org.xml.sax.Parser.
159    //
160
161    /**
162     * <b>SAX1</b>: Sets the locale used for diagnostics; currently,
163     * only locales using the English language are supported.
164     * @param locale The locale for which diagnostics will be generated
165     */
166    public void setLocale (Locale locale)
167    throws SAXException
168    {
169        if ("en".equals (locale.getLanguage ()))
170            return ;
171
172        throw new SAXException ("AElfred only supports English locales.");
173    }
174
175
176    /**
177     * <b>SAX2</b>: Returns the object used when resolving external
178     * entities during parsing (both general and parameter entities).
179     */
180    public EntityResolver getEntityResolver ()
181    {
182        return entityResolver;
183    }
184
185    /**
186     * <b>SAX1, SAX2</b>: Set the entity resolver for this parser.
187     * @param handler The object to receive entity events.
188     */
189    public void setEntityResolver (EntityResolver resolver)
190    {
191        if (resolver == null)
192            resolver = base;
193        this.entityResolver = resolver;
194    }
195
196
197    /**
198     * <b>SAX2</b>: Returns the object used to process declarations related
199     * to notations and unparsed entities.
200     */
201    public DTDHandler getDTDHandler ()
202    {
203        return dtdHandler;
204    }
205
206    /**
207     * <b>SAX1, SAX2</b>: Set the DTD handler for this parser.
208     * @param handler The object to receive DTD events.
209     */
210    public void setDTDHandler (DTDHandler handler)
211    {
212        if (handler == null)
213            handler = base;
214        this.dtdHandler = handler;
215    }
216
217
218    /**
219     * <b>SAX1</b>: Set the document handler for this parser.  If a
220     * content handler was set, this document handler will supplant it.
221     * The parser is set to report all XML 1.0 names rather than to
222     * filter out "xmlns" attributes (the "namespace-prefixes" feature
223     * is set to true).
224     *
225     * @deprecated SAX2 programs should use the XMLReader interface
226     *  and a ContentHandler.
227     *
228     * @param handler The object to receive document events.
229     */
230    public void setDocumentHandler (DocumentHandler handler)
231    {
232        contentHandler = new Adapter (handler);
233        xmlNames = true;
234    }
235
236    /**
237     * <b>SAX2</b>: Returns the object used to report the logical
238     * content of an XML document.
239     */
240    public ContentHandler getContentHandler ()
241    {
242        return contentHandler;
243    }
244
245    /**
246     * <b>SAX2</b>: Assigns the object used to report the logical
247     * content of an XML document.  If a document handler was set,
248     * this content handler will supplant it (but XML 1.0 style name
249     * reporting may remain enabled).
250     */
251    public void setContentHandler (ContentHandler handler)
252    {
253        if (handler == null)
254            handler = base;
255        contentHandler = handler;
256    }
257
258    /**
259     * <b>SAX1, SAX2</b>: Set the error handler for this parser.
260     * @param handler The object to receive error events.
261     */
262    public void setErrorHandler (ErrorHandler handler)
263    {
264        if (handler == null)
265            handler = base;
266        this.errorHandler = handler;
267    }
268
269    /**
270     * <b>SAX2</b>: Returns the object used to receive callbacks for XML
271     * errors of all levels (fatal, nonfatal, warning); this is never null;
272     */
273    public ErrorHandler getErrorHandler ()
274        {
275            return errorHandler;
276    }
277
278
279    /**
280     * <b>SAX1, SAX2</b>: Auxiliary API to parse an XML document, used mostly
281     * when no URI is available.
282     * If you want anything useful to happen, you should set
283     * at least one type of handler.
284     * @param source The XML input source.  Don't set 'encoding' unless
285     *  you know for a fact that it's correct.
286     * @see #setEntityResolver
287     * @see #setDTDHandler
288     * @see #setContentHandler
289     * @see #setErrorHandler
290     * @exception SAXException The handlers may throw any SAXException,
291     *  and the parser normally throws SAXParseException objects.
292     * @exception IOException IOExceptions are normally through through
293     *  the parser if there are problems reading the source document.
294     */
295     
296    public void parse (InputSource source) throws SAXException, IOException
297    {
298        synchronized (base) {
299            parser = new XmlParser ();
300            parser.setHandler (this);
301
302            try {
303                        String  systemId = source.getSystemId ();
304
305                        // duplicate first entry, in case startDocument handler
306                        // needs to use Locator.getSystemId(), before entities
307                        // start to get reported by the parser
308
309                        if (systemId != null)
310                            entityStack.add (systemId);
311                        else
312                            entityStack.add ("illegal:unknown system ID");
313
314                        parser.doParse (systemId,
315                                      source.getPublicId (),
316                                      source.getCharacterStream (),
317                                      source.getByteStream (),
318                                      source.getEncoding ());
319            } catch (SAXException e) {
320                    throw e;
321            } catch (IOException e) {
322                    throw e;
323            } catch (RuntimeException e) {
324                    throw e;
325            } catch (Exception e) {
326                    throw new SAXException (e.getMessage (), e);
327            } finally {
328                    contentHandler.endDocument ();
329                    entityStack.clear ();
330            }
331        }
332    }
333
334
335    /**
336     * <b>SAX1, SAX2</b>: Preferred API to parse an XML document, using a
337     * system identifier (URI).
338     */
339     
340    public void parse (String systemId) throws SAXException, IOException
341    {
342            parse (new InputSource (systemId));
343    }
344
345    //
346    // Implementation of SAX2 "XMLReader" interface
347    //
348    static final String FEATURE = "http://xml.org/sax/features/";
349    static final String HANDLER = "http://xml.org/sax/properties/";
350
351    /**
352     * <b>SAX2</b>: Tells the value of the specified feature flag.
353     *
354     * @exception SAXNotRecognizedException thrown if the feature flag
355     *  is neither built in, nor yet assigned.
356     */
357    public boolean getFeature (String featureId)
358    throws SAXNotRecognizedException
359    {
360        if ((FEATURE + "validation").equals (featureId))
361            return false;
362
363        // external entities (both types) are currently always included
364        if ((FEATURE + "external-general-entities").equals (featureId)
365                || (FEATURE + "external-parameter-entities")
366                .equals (featureId))
367            return true;
368
369        // element/attribute names are as written in document; no mangling
370        if ((FEATURE + "namespace-prefixes").equals (featureId))
371            return xmlNames;
372
373        // report element/attribute namespaces?
374        if ((FEATURE + "namespaces").equals (featureId))
375            return namespaces;
376
377        // XXX always provides a locator ... removed in beta
378
379        // always interns
380        if ((FEATURE + "string-interning").equals (featureId))
381            return true;
382
383        if (features != null && features.containsKey (featureId))
384            return ((Boolean)features.get (featureId)).booleanValue ();
385
386        throw new SAXNotRecognizedException (featureId);
387    }
388
389    /**
390     * <b>SAX2</b>:  Returns the specified property.
391     *
392     * @exception SAXNotRecognizedException thrown if the property value
393     *  is neither built in, nor yet stored.
394     */
395    public Object getProperty (String propertyId)
396    throws SAXNotRecognizedException
397    {
398        if ((HANDLER + "declaration-handler").equals (propertyId))
399            return declHandler;
400
401        if ((HANDLER + "lexical-handler").equals (propertyId))
402            return lexicalHandler;
403        
404        if (properties != null && properties.containsKey (propertyId))
405            return properties.get (propertyId);
406
407        // unknown properties
408        throw new SAXNotRecognizedException (propertyId);
409    }
410
411    /**
412     * <b>SAX2</b>:  Sets the state of feature flags in this parser.  Some
413     * built-in feature flags are mutable; all flags not built-in are
414     * motable.
415     */
416    public void setFeature (String featureId, boolean state)
417    throws SAXNotRecognizedException, SAXNotSupportedException
418    {
419        boolean value;
420        
421        try {
422            // Features with a defined value, we just change it if we can.
423            value = getFeature (featureId);
424
425            if (state == value)
426                return;
427
428            if ((FEATURE + "namespace-prefixes").equals (featureId)) {
429                // in this implementation, this only affects xmlns reporting
430                xmlNames = state;
431                return;
432            }
433
434            if ((FEATURE + "namespaces").equals (featureId)) {
435                // XXX if not currently parsing ...
436                if (true) {
437                    namespaces = state;
438                    return;
439                }
440                // if in mid-parse, critical info hasn't been computed/saved
441            }
442
443            // can't change builtins
444            if (features == null || !features.containsKey (featureId))
445                throw new SAXNotSupportedException (featureId);
446
447        } catch (SAXNotRecognizedException e) {
448            // as-yet unknown features
449            if (features == null)
450                features = new HashMap (5);
451        }
452
453        // record first value, or modify existing one
454        features.put (featureId, 
455            state
456                ? Boolean.TRUE
457                : Boolean.FALSE);
458    }
459
460    /**
461     * <b>SAX2</b>:  Assigns the specified property.  Like SAX1 handlers,
462     * these may be changed at any time.
463     */
464    public void setProperty (String propertyId, Object property)
465    throws SAXNotRecognizedException, SAXNotSupportedException
466    {
467        Object  value;
468        
469        try {
470            // Properties with a defined value, we just change it if we can.
471            value = getProperty (propertyId);
472
473            if ((HANDLER + "declaration-handler").equals (propertyId)) {
474                if (property == null)
475                    declHandler = base;
476                else if (! (property instanceof DeclHandler))
477                    throw new SAXNotSupportedException (propertyId);
478                else
479                    declHandler = (DeclHandler) property;
480                    return ;
481            }
482
483            if ((HANDLER + "lexical-handler").equals (propertyId)) {
484                if (property == null)
485                    lexicalHandler = base;
486                else if (! (property instanceof LexicalHandler))
487                    throw new SAXNotSupportedException (propertyId);
488                else
489                    lexicalHandler = (LexicalHandler) property;
490                return ;
491            }
492
493            // can't change builtins
494            if (properties == null || !properties.containsKey (propertyId))
495                throw new SAXNotSupportedException (propertyId);
496
497        } catch (SAXNotRecognizedException e) {
498            // as-yet unknown properties
499            if (properties == null)
500                properties = new HashMap (5);
501        }
502
503        // record first value, or modify existing one
504        properties.put (propertyId, property);
505    }
506
507
508    //
509    // This is where the driver receives AElfred callbacks and translates
510    // them into SAX callbacks.  Some more callbacks have been added for
511    // SAX2 support.
512    //
513
514    // NOTE:  in some cases, local copies of handlers are
515    // created and used, to work around codegen bugs in at
516    // least one snapshot version of GCJ.
517
518    void startDocument () throws SAXException
519    {
520        contentHandler.setDocumentLocator (this);
521        contentHandler.startDocument ();
522        attributeNames.clear ();
523        attributeValues.clear ();
524    }
525
526    void endDocument () throws SAXException
527    {
528        // SAX says endDocument _must_ be called (handy to close
529        // files etc) so it's in a "finally" clause
530    }
531
532    Object resolveEntity (String publicId, String systemId)
533    throws SAXException, IOException
534    {
535        InputSource source = entityResolver.resolveEntity (publicId,
536                             systemId);
537
538        if (source == null) {
539            return null;
540        } else if (source.getCharacterStream () != null) {
541            return source.getCharacterStream ();
542        } else if (source.getByteStream () != null) {
543            if (source.getEncoding () == null)
544                return source.getByteStream ();
545            else try {
546                return new InputStreamReader (
547                    source.getByteStream (),
548                    source.getEncoding ()
549                    );
550            } catch (IOException e) {
551                return source.getByteStream ();
552            }
553        } else {
554            return source.getSystemId ();
555        }
556        // XXX no way to tell AElfred about new public
557        // or system ids ... so relative URL resolution
558        // through that entity could be less than reliable.
559    }
560
561
562    void startExternalEntity (String systemId)
563    throws SAXException
564    {
565            entityStack.add (systemId);
566    }
567
568    void endExternalEntity (String systemId)
569    throws SAXException
570    {
571            entityStack.remove ( entityStack.size() - 1 );
572    }
573
574    void doctypeDecl (String name, String publicId, String systemId)
575    throws SAXException
576    {
577        lexicalHandler.startDTD (name, publicId, systemId);
578        
579        // ... the "name" is a declaration and should be given
580        // to the DeclHandler (but sax2 beta doesn't).
581
582        // the IDs for the external subset are lexical details,
583        // as are the contents of the internal subset; but sax2
584        // beta only provides the external subset "pre-parse"
585    }
586
587    void endDoctype () throws SAXException
588    {
589        // NOTE:  some apps may care that comments and PIs,
590        // are stripped from their DTD declaration context,
591        // and that those declarations are themselves quite
592        // thoroughly reordered here.
593
594        deliverDTDEvents ();
595        lexicalHandler.endDTD ();
596    }
597
598
599    void attribute (String aname, String value, boolean isSpecified)
600    throws SAXException
601    {
602        if (attributeCount++ == 0) {
603            if (namespaces)
604                    prefixStack.pushContext ();
605        }
606
607            // set nsTemp [0] == namespace URI (or empty)
608            // set nsTemp [1] == local name (or empty)
609            if (value != null) {
610                if (namespaces) {
611                        int     index = aname.indexOf (':');
612
613                        // prefixed name?
614                        if (index > 0) {
615                            // prefix declaration?
616                            if (index == 5 && aname.startsWith ("xmlns")) {
617                                String          prefix = aname.substring (index + 1);
618
619                                if (value.length () == 0) {
620                                    errorHandler.error (new SAXParseException (
621                                            "missing URI in namespace decl attribute: "
622                                                + aname,
623                                            this));
624                                } else {
625                                    prefixStack.declarePrefix (prefix, value);
626                                    contentHandler.startPrefixMapping (prefix, value);
627                                }
628                                if (!xmlNames)
629                                    return;
630                                nsTemp [0] = "";
631                                nsTemp [1] = aname;
632
633                            // prefix reference
634                            } else {
635                                if (prefixStack.processName (aname, nsTemp, true)
636                                        == null) {
637                                        // start of MHK code
638                                            nsTemp[0] = "";
639                                            nsTemp[1] = aname;    // defer checking till later
640                                            nspending = true;
641                                        // end of MHK code
642                                        // start of previous code    
643                                    // errorHandler.error (new SAXParseException (
644                                        //    "undeclared name prefix in: " + aname,
645                                        //    this));
646                                    // nsTemp [0] = nsTemp [1] = "";
647                                        // end of previous code    
648                                } // else nsTemp [0, 1] received { uri, local }
649                            }
650
651                        // no prefix
652                        } else {
653                            // default declaration?
654                            if ("xmlns".equals (aname)) {
655                                prefixStack.declarePrefix ("", value);
656                                contentHandler.startPrefixMapping ("", value);
657                                if (!xmlNames)
658                                    return;
659                            }
660                            nsTemp [0] = "";
661                            nsTemp [1] = aname;
662                        }
663                } else
664                        nsTemp [0] = nsTemp [1] = "";
665
666            attributeNamespaces.add (nsTemp [0]);
667            attributeLocalNames.add (nsTemp [1]);
668            attributeNames.add (aname);
669            // attribute type comes from querying parser's DTD records
670            attributeValues.add (value);
671        }
672    }
673
674    void startElement (String elname)
675    throws SAXException
676    {
677        ContentHandler handler = contentHandler;
678
679        //
680        // NOTE:  this implementation of namespace support adds something
681        // like six percent to parsing CPU time, in a large (~50 MB)
682        // document that doesn't use namespaces at all.  (Measured by PC
683        // sampling.)
684        //
685        // It ought to become notably faster in such cases.  Most
686        // costs are the prefix stack calling Hashtable.get() (2%),
687        // String.hashCode() (1.5%) and about 1.3% each for pushing
688        // the context, and two chunks of name processing.
689        //
690
691        if (attributeCount == 0)
692            prefixStack.pushContext ();
693
694        // save element name so attribute callbacks work
695        elementName = elname;
696        if (namespaces) {
697            // START MHK CODE
698            // Check that namespace prefixes for attributes are valid
699            if (attributeCount > 0 && nspending) {
700                for (int i=0; i<attributeLocalNames.size(); i++) {
701                    String aname = (String)attributeLocalNames.get(i);
702                    if (aname.indexOf(':')>0) {
703
704                        if (prefixStack.processName (aname, nsTemp, true) == null) {
705                                    errorHandler.error (new SAXParseException (
706                                            "undeclared name prefix in: " + aname,
707                                            this));
708                        } else {
709                            attributeNamespaces.set(i, nsTemp[0]);
710                            attributeLocalNames.set(i, nsTemp[1]);
711                        }
712                    }
713                }
714            }
715            // END MHK CODE
716
717            if (prefixStack.processName (elname, nsTemp, false) == null) {
718                errorHandler.error (new SAXParseException (
719                        "undeclared name prefix in: " + elname,
720                        this));
721                nsTemp [0] = nsTemp [1] = "";
722            }
723
724            handler.startElement (nsTemp [0], nsTemp [1], elname, this);
725        } else
726            handler.startElement ("", "", elname, this);
727        // elementName = null;
728
729        // elements with no attributes are pretty common!
730        if (attributeCount != 0) {
731            attributeNames.clear ();
732            attributeNamespaces.clear ();
733            attributeLocalNames.clear ();
734            attributeValues.clear ();
735            attributeCount = 0;
736        }
737        nspending = false;
738    }
739
740    void endElement (String elname)
741    throws SAXException
742    {
743        ContentHandler  handler = contentHandler;
744
745        handler.endElement ("", "", elname);
746
747        if (!namespaces)
748            return;
749
750        Enumeration     prefixes = prefixStack.getDeclaredPrefixes ();
751
752        while (prefixes.hasMoreElements ())
753            handler.endPrefixMapping ((String) prefixes.nextElement ());
754        prefixStack.popContext ();
755    }
756
757    void startCDATA ()
758    throws SAXException
759    {
760        lexicalHandler.startCDATA ();
761    }
762
763    void charData (char ch[], int start, int length)
764    throws SAXException
765    {
766        contentHandler.characters (ch, start, length);
767    }
768
769    void endCDATA ()
770    throws SAXException
771    {
772            lexicalHandler.endCDATA ();
773    }
774
775    void ignorableWhitespace (char ch[], int start, int length)
776    throws SAXException
777    {
778        contentHandler.ignorableWhitespace (ch, start, length);
779    }
780
781    void processingInstruction (String target, String data)
782    throws SAXException
783    {
784        // XXX if within DTD, perhaps it's best to discard
785        // PIs since the decls to which they (usually)
786        // apply get significantly rearranged
787
788        contentHandler.processingInstruction (target, data);
789    }
790
791    void comment (char ch[], int start, int length)
792    throws SAXException
793    {
794        // XXX if within DTD, perhaps it's best to discard
795        // comments since the decls to which they (usually)
796        // apply get significantly rearranged
797
798        if (lexicalHandler != base)
799            lexicalHandler.comment (ch, start, length);
800    }
801
802        // AElfred only has fatal errors
803        void error (String message, String url, int line, int column)
804        throws SAXException
805        {
806        SAXParseException fatal;
807        
808        fatal = new SAXParseException (message, null, url, line, column);
809        errorHandler.fatalError (fatal);
810
811        // Even if the application can continue ... we can't!
812        throw fatal;
813    }
814
815
816    //
817    // Before the endDtd event, deliver all non-PE declarations.
818    //
819    private void deliverDTDEvents ()
820    throws SAXException
821    {
822        String  ename;
823        String  nname;
824        String publicId;
825        String  systemId;
826
827        // First, report all notations.
828        if (dtdHandler != base) {
829            Iterator    notationNames = parser.declaredNotations ();
830
831            while (notationNames.hasNext ()) {
832                nname = (String) notationNames.next ();
833                publicId = parser.getNotationPublicId (nname);
834                systemId = parser.getNotationSystemId (nname);
835                dtdHandler.notationDecl (nname, publicId, systemId);
836            }
837        }
838
839        // Next, report all entities.
840        if (dtdHandler != base || declHandler != base) {
841            Iterator    entityNames = parser.declaredEntities ();
842            int type;
843
844            while (entityNames.hasNext ()) {
845                ename = (String) entityNames.next ();
846                type = parser.getEntityType (ename);
847
848                if (ename.charAt (0) == '%')
849                    continue;
850
851                // unparsed
852                if (type == XmlParser.ENTITY_NDATA) {
853                    publicId = parser.getEntityPublicId (ename);
854                    systemId = parser.getEntitySystemId (ename);
855                    nname = parser.getEntityNotationName (ename);
856                    dtdHandler.unparsedEntityDecl (ename,
857                                                    publicId, systemId, nname);
858
859                    // external parsed
860                }
861                else if (type == XmlParser.ENTITY_TEXT) {
862                    publicId = parser.getEntityPublicId (ename);
863                    systemId = parser.getEntitySystemId (ename);
864                    declHandler.externalEntityDecl (ename,
865                                                     publicId, systemId);
866
867                    // internal parsed
868                }
869                else if (type == XmlParser.ENTITY_INTERNAL) {
870                    // filter out the built-ins; even if they were
871                    // declared, they didn't need to be.
872                    if ("lt".equals (ename) || "gt".equals (ename)
873                            || "quot".equals (ename)
874                            || "apos".equals (ename)
875                            || "amp".equals (ename))
876                        continue;
877                    declHandler.internalEntityDecl (ename,
878                                 parser.getEntityValue (ename));
879                }
880            }
881        }
882
883        // elements, attributes
884        if (declHandler != base) {
885            Iterator    elementNames = parser.declaredElements ();
886            Iterator    attNames;
887
888            while (elementNames.hasNext ()) {
889                String model = null;
890
891                ename = (String) elementNames.next ();
892                switch (parser.getElementContentType (ename)) {
893                    case XmlParser.CONTENT_ANY:
894                        model = "ANY";
895                        break;
896                    case XmlParser.CONTENT_EMPTY:
897                        model = "EMPTY";
898                        break;
899                    case XmlParser.CONTENT_MIXED:
900                    case XmlParser.CONTENT_ELEMENTS:
901                        model = parser.getElementContentModel (ename);
902                        break;
903                    case XmlParser.CONTENT_UNDECLARED:
904                    default:
905                        model = null;
906                        break;
907                }
908                if (model != null)
909                    declHandler.elementDecl (ename, model);
910
911                attNames = parser.declaredAttributes (ename);
912                while (attNames != null && attNames.hasNext ()) {
913                    String aname = (String) attNames.next ();
914                    String type;
915                    String valueDefault;
916                    String value;
917
918                    switch (parser.getAttributeType (ename, aname)) {
919                    case XmlParser.ATTRIBUTE_CDATA:
920                        type = "CDATA";
921                        break;
922                    case XmlParser.ATTRIBUTE_ENTITY:
923                        type = "ENTITY";
924                        break;
925                    case XmlParser.ATTRIBUTE_ENTITIES:
926                        type = "ENTITIES";
927                        break;
928                    case XmlParser.ATTRIBUTE_ENUMERATED:
929                        type = parser.getAttributeIterator (ename, aname);
930                        break;
931                    case XmlParser.ATTRIBUTE_ID:
932                        type = "ID";
933                        break;
934                    case XmlParser.ATTRIBUTE_IDREF:
935                        type = "IDREF";
936                        break;
937                    case XmlParser.ATTRIBUTE_IDREFS:
938                        type = "IDREFS";
939                        break;
940                    case XmlParser.ATTRIBUTE_NMTOKEN:
941                        type = "NMTOKEN";
942                        break;
943                    case XmlParser.ATTRIBUTE_NMTOKENS:
944                        type = "NMTOKENS";
945                        break;
946
947                        // XXX SAX2 beta doesn't have a way to return the
948                        // enumerated list of permitted notations ... SAX1
949                        // kluged it as NMTOKEN, but that won't work for
950                        // the sort of apps that really use the DTD info
951                    case XmlParser.ATTRIBUTE_NOTATION:
952                        type = "NOTATION";
953                        break;
954
955                    default:
956                        errorHandler.fatalError (new SAXParseException (
957                                  "internal error, att type", this));
958                        type = null;
959                    }
960
961                    switch (parser.getAttributeDefaultValueType (
962                                 ename, aname)) {
963                    case XmlParser.ATTRIBUTE_DEFAULT_IMPLIED:
964                        valueDefault = "#IMPLIED";
965                        break;
966                    case XmlParser.ATTRIBUTE_DEFAULT_REQUIRED:
967                        valueDefault = "#REQUIRED";
968                        break;
969                    case XmlParser.ATTRIBUTE_DEFAULT_FIXED:
970                        valueDefault = "#FIXED";
971                        break;
972                    case XmlParser.ATTRIBUTE_DEFAULT_SPECIFIED:
973                        valueDefault = null;
974                        break;
975
976                    default:
977                        errorHandler.fatalError (new SAXParseException (
978                                    "internal error, att default", this));
979                        valueDefault = null;
980                    }
981
982                    value = parser.getAttributeDefaultValue (ename, aname);
983
984                    declHandler.attributeDecl (ename, aname,
985                                                type, valueDefault, value);
986                }
987            }
988        }
989    }
990
991
992    //
993    // Implementation of org.xml.sax.Attributes.
994    //
995
996    /**
997     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
998     * (don't invoke on parser);
999     */
1000    public int getLength ()
1001    {
1002            return attributeNames.size ();
1003    }
1004
1005    /**
1006     * <b>SAX2 Attributes</b> method (don't invoke on parser);
1007     */
1008    public String getURI (int index)
1009    {
1010            return (String) (attributeNamespaces.get (index));
1011    }
1012
1013    /**
1014     * <b>SAX2 Attributes</b> method (don't invoke on parser);
1015     */
1016    public String getLocalName (int index)
1017    {
1018            return (String) (attributeLocalNames.get (index));
1019    }
1020
1021    /**
1022     * <b>SAX2 Attributes</b> method (don't invoke on parser);
1023     */
1024    public String getQName (int i)
1025    {
1026            return (String) (attributeNames.get (i));
1027    }
1028
1029    /**
1030     * <b>SAX1 AttributeList</b> method (don't invoke on parser);
1031     */
1032    public String getName (int i)
1033    {
1034            return (String) (attributeNames.get (i));
1035    }
1036
1037    /**
1038     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1039     * (don't invoke on parser);
1040     */
1041    public String getType (int i)
1042    {
1043        switch (parser.getAttributeType (elementName, getQName (i))) {
1044
1045        case XmlParser.ATTRIBUTE_UNDECLARED:
1046        case XmlParser.ATTRIBUTE_CDATA:
1047            return "CDATA";
1048        case XmlParser.ATTRIBUTE_ID:
1049            return "ID";
1050        case XmlParser.ATTRIBUTE_IDREF:
1051            return "IDREF";
1052        case XmlParser.ATTRIBUTE_IDREFS:
1053            return "IDREFS";
1054        case XmlParser.ATTRIBUTE_ENTITY:
1055            return "ENTITY";
1056        case XmlParser.ATTRIBUTE_ENTITIES:
1057            return "ENTITIES";
1058        case XmlParser.ATTRIBUTE_ENUMERATED:
1059            // XXX doesn't have a way to return permitted enum values,
1060            // though they must each be a NMTOKEN 
1061        case XmlParser.ATTRIBUTE_NMTOKEN:
1062            return "NMTOKEN";
1063        case XmlParser.ATTRIBUTE_NMTOKENS:
1064            return "NMTOKENS";
1065        case XmlParser.ATTRIBUTE_NOTATION:
1066            // XXX doesn't have a way to return the permitted values,
1067            // each of which must be name a declared notation
1068            return "NOTATION";
1069
1070        }
1071
1072        return null;
1073    }
1074
1075
1076    /**
1077     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1078     * (don't invoke on parser);
1079     */
1080    public String getValue (int i)
1081    {
1082            return (String) (attributeValues.get (i));
1083    }
1084
1085
1086    /**
1087     * <b>SAX2 Attributes</b> method (don't invoke on parser);
1088     */
1089    public int getIndex (String uri, String local)
1090    {
1091        int length = getLength ();
1092
1093        for (int i = 0; i < length; i++) {
1094            if (!getURI (i).equals (uri))
1095                continue;
1096            if (getLocalName (i).equals (local))
1097                return i;
1098        }
1099        return -1;
1100    }
1101
1102
1103    /**
1104     * <b>SAX2 Attributes</b> method (don't invoke on parser);
1105     */
1106    public int getIndex (String xmlName)
1107    {
1108        int length = getLength ();
1109
1110        for (int i = 0; i < length; i++) {
1111            if (getQName (i).equals (xmlName))
1112                return i;
1113        }
1114        return -1;
1115    }
1116
1117
1118    /**
1119     * <b>SAX2 Attributes</b> method (don't invoke on parser);
1120     */
1121    public String getType (String uri, String local)
1122    {
1123        int index = getIndex (uri, local);
1124
1125        if (index < 0)
1126            return null;
1127        return getType (index);
1128    }
1129
1130
1131    /**
1132     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1133     * (don't invoke on parser);
1134     */
1135    public String getType (String xmlName)
1136    {
1137        int index = getIndex (xmlName);
1138
1139        if (index < 0)
1140            return null;
1141        return getType (index);
1142    }
1143
1144
1145    /**
1146     * <b>SAX Attributes</b> method (don't invoke on parser);
1147     */
1148    public String getValue (String uri, String local)
1149    {
1150        int index = getIndex (uri, local);
1151
1152        if (index < 0)
1153            return null;
1154        return getValue (index);
1155    }
1156
1157
1158    /**
1159     * <b>SAX1 AttributeList, SAX2 Attributes</b> method
1160     * (don't invoke on parser);
1161     */
1162    public String getValue (String xmlName)
1163    {
1164        int index = getIndex (xmlName);
1165
1166        if (index < 0)
1167            return null;
1168        return getValue (index);
1169    }
1170
1171
1172    //
1173    // Implementation of org.xml.sax.Locator.
1174    //
1175
1176    /**
1177     * <b>SAX Locator</b> method (don't invoke on parser);
1178     */
1179    public String getPublicId ()
1180    {
1181            return null;                // XXX track public IDs too
1182    }
1183
1184    /**
1185     * <b>SAX Locator</b> method (don't invoke on parser);
1186     */
1187    public String getSystemId ()
1188    {
1189            return (String) entityStack.get ( entityStack.size() - 1 );
1190    }
1191
1192    /**
1193     * <b>SAX Locator</b> method (don't invoke on parser);
1194     */
1195    public int getLineNumber ()
1196    {
1197            return parser.getLineNumber ();
1198    }
1199
1200    /**
1201     * <b>SAX Locator</b> method (don't invoke on parser);
1202     */
1203    public int getColumnNumber ()
1204    {
1205            return parser.getColumnNumber ();
1206    }
1207
1208    // adapter between content handler and document handler callbacks
1209    
1210    private static class Adapter implements ContentHandler
1211    {
1212        private DocumentHandler         docHandler;
1213
1214        Adapter (DocumentHandler dh)
1215            { docHandler = dh; }
1216
1217
1218        public void setDocumentLocator (Locator l)
1219            { docHandler.setDocumentLocator (l); }
1220        
1221        public void startDocument () throws SAXException
1222            { docHandler.startDocument (); }
1223        
1224        public void processingInstruction (String target, String data)
1225        throws SAXException
1226            { docHandler.processingInstruction (target, data); }
1227        
1228        public void startPrefixMapping (String prefix, String uri)
1229            { /* ignored */ }
1230
1231        public void startElement (
1232            String      namespace,
1233            String      local,
1234            String      name,
1235            Attributes  attrs ) throws SAXException
1236            {
1237                docHandler.startElement (name, (AttributeList) attrs);
1238        }
1239
1240        public void characters (char buf [], int offset, int len)
1241        throws SAXException
1242            {
1243                docHandler.characters (buf, offset, len);
1244        }
1245
1246        public void ignorableWhitespace (char buf [], int offset, int len)
1247        throws SAXException
1248        {
1249            docHandler.ignorableWhitespace (buf, offset, len);
1250        }
1251
1252        public void skippedEntity (String name)
1253            { /* ignored */ }
1254
1255        public void endElement (String u, String l, String name)
1256        throws SAXException
1257            { docHandler.endElement (name); }
1258
1259        public void endPrefixMapping (String prefix)
1260            { /* ignored */ }
1261
1262        public void endDocument () throws SAXException
1263            { docHandler.endDocument (); }
1264    }
1265}