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