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 }