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 <dbrownell@users.sourceforge.net>
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 }