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