001 /* XmlReader.java -- 002 Copyright (C) 1999,2000,2001 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 package fi.iki.hsivonen.gnu.xml.aelfred2; 039 040 import java.io.IOException; 041 import java.util.Locale; 042 043 import org.xml.sax.*; 044 import org.xml.sax.ext.*; 045 046 import gnu.xml.pipeline.EventFilter; 047 import gnu.xml.pipeline.ValidationConsumer; 048 049 050 /** 051 * This SAX2 parser optionally layers a validator over the Ælfred2 052 * SAX2 parser. While this will not evaluate every XML validity constraint, 053 * it does support all the validity constraints that are of any real utility 054 * outside the strict SGML-compatible world. See the documentation for the 055 * SAXDriver class for information about the SAX2 features and properties 056 * that are supported, and documentation for the ValidationConsumer for 057 * information about what validity constraints may not be supported. 058 * (Ælfred2 tests some of those, even in non-validating mode, to 059 * achieve better conformance.) 060 * 061 * <p> Note that due to its internal construction, you can't change most 062 * handlers until parse() returns. This diverges slightly from SAX, which 063 * expects later binding to be supported. Early binding involves less 064 * runtime overhead, which is an issue for event pipelines as used inside 065 * this parser. Rather than relying on the parser to handle late binding 066 * to your own handlers, do it yourself. 067 * 068 * @see SAXDriver 069 * @see gnu.xml.pipeline.ValidationConsumer 070 * 071 * @author David Brownell 072 */ 073 public final class XmlReader 074 implements XMLReader 075 { 076 077 static class FatalErrorHandler 078 extends DefaultHandler2 079 { 080 081 public void error(SAXParseException e) 082 throws SAXException 083 { 084 throw e; 085 } 086 087 } 088 089 private SAXDriver aelfred2 = new SAXDriver(); 090 private EventFilter filter = new EventFilter(); 091 private boolean isValidating; 092 private boolean active; 093 094 /** 095 * Constructs a SAX Parser. 096 */ 097 public XmlReader() 098 { 099 } 100 101 /** 102 * Constructs a SAX Parser, optionally treating validity errors 103 * as if they were fatal errors. 104 */ 105 public XmlReader(boolean invalidIsFatal) 106 { 107 if (invalidIsFatal) 108 { 109 setErrorHandler(new FatalErrorHandler()); 110 } 111 } 112 113 /** 114 * <b>SAX2</b>: Returns the object used to report the logical 115 * content of an XML document. 116 */ 117 public ContentHandler getContentHandler() 118 { 119 return filter.getContentHandler(); 120 } 121 122 /** 123 * <b>SAX2</b>: Assigns the object used to report the logical 124 * content of an XML document. 125 * @exception IllegalStateException if called mid-parse 126 */ 127 public void setContentHandler(ContentHandler handler) 128 { 129 if (active) 130 { 131 throw new IllegalStateException("already parsing"); 132 } 133 filter.setContentHandler(handler); 134 } 135 136 /** 137 * <b>SAX2</b>: Returns the object used to process declarations related 138 * to notations and unparsed entities. 139 */ 140 public DTDHandler getDTDHandler() 141 { 142 return filter.getDTDHandler(); 143 } 144 145 /** 146 * <b>SAX1</b> Assigns DTD handler 147 * @exception IllegalStateException if called mid-parse 148 */ 149 public void setDTDHandler(DTDHandler handler) 150 { 151 if (active) 152 { 153 throw new IllegalStateException("already parsing"); 154 } 155 filter.setDTDHandler(handler); 156 } 157 158 /** 159 * <b>SAX2</b>: Returns the object used when resolving external 160 * entities during parsing (both general and parameter entities). 161 */ 162 public EntityResolver getEntityResolver() 163 { 164 return aelfred2.getEntityResolver(); 165 } 166 167 /** 168 * <b>SAX1</b> Assigns parser's entity resolver 169 */ 170 public void setEntityResolver(EntityResolver handler) 171 { 172 aelfred2.setEntityResolver(handler); 173 } 174 175 /** 176 * <b>SAX2</b>: Returns the object used to receive callbacks for XML 177 * errors of all levels (fatal, nonfatal, warning); this is never null; 178 */ 179 public ErrorHandler getErrorHandler() 180 { 181 return aelfred2.getErrorHandler(); 182 } 183 184 /** 185 * <b>SAX1</b> Assigns error handler 186 * @exception IllegalStateException if called mid-parse 187 */ 188 public void setErrorHandler(ErrorHandler handler) 189 { 190 if (active) 191 { 192 throw new IllegalStateException("already parsing"); 193 } 194 aelfred2.setErrorHandler(handler); 195 } 196 197 /** 198 * <b>SAX2</b>: Assigns the specified property. 199 * @exception IllegalStateException if called mid-parse 200 */ 201 public void setProperty(String propertyId, Object value) 202 throws SAXNotRecognizedException, SAXNotSupportedException 203 { 204 if (active) 205 { 206 throw new IllegalStateException("already parsing"); 207 } 208 if (getProperty(propertyId) != value) 209 { 210 filter.setProperty(propertyId, value); 211 } 212 } 213 214 /** 215 * <b>SAX2</b>: Returns the specified property. 216 */ 217 public Object getProperty(String propertyId) 218 throws SAXNotRecognizedException 219 { 220 if ((SAXDriver.PROPERTY + "declaration-handler").equals(propertyId) 221 || (SAXDriver.PROPERTY + "lexical-handler").equals(propertyId)) 222 { 223 return filter.getProperty(propertyId); 224 } 225 throw new SAXNotRecognizedException(propertyId); 226 } 227 228 private void forceValidating() 229 throws SAXNotRecognizedException, SAXNotSupportedException 230 { 231 aelfred2.setFeature(SAXDriver.FEATURE + "namespace-prefixes", 232 true); 233 aelfred2.setFeature(SAXDriver.FEATURE + "external-general-entities", 234 true); 235 aelfred2.setFeature(SAXDriver.FEATURE + "external-parameter-entities", 236 true); 237 } 238 239 /** 240 * <b>SAX2</b>: Sets the state of features supported in this parser. 241 * Note that this parser requires reporting of namespace prefixes when 242 * validating. 243 */ 244 public void setFeature(String featureId, boolean state) 245 throws SAXNotRecognizedException, SAXNotSupportedException 246 { 247 boolean value = getFeature(featureId); 248 249 if (state == value) 250 { 251 return; 252 } 253 254 if ((SAXDriver.FEATURE + "validation").equals(featureId)) 255 { 256 if (active) 257 { 258 throw new SAXNotSupportedException("already parsing"); 259 } 260 if (state) 261 { 262 forceValidating(); 263 } 264 isValidating = state; 265 } 266 else 267 { 268 aelfred2.setFeature(featureId, state); 269 } 270 } 271 272 /** 273 * <b>SAX2</b>: Tells whether this parser supports the specified feature. 274 * At this time, this directly parallels the underlying SAXDriver, 275 * except that validation is optionally supported. 276 * 277 * @see SAXDriver 278 */ 279 public boolean getFeature(String featureId) 280 throws SAXNotRecognizedException, SAXNotSupportedException 281 { 282 if ((SAXDriver.FEATURE + "validation").equals(featureId)) 283 { 284 return isValidating; 285 } 286 287 return aelfred2.getFeature(featureId); 288 } 289 290 /** 291 * <b>SAX1</b>: Sets the locale used for diagnostics; currently, 292 * only locales using the English language are supported. 293 * @param locale The locale for which diagnostics will be generated 294 */ 295 public void setLocale(Locale locale) 296 throws SAXException 297 { 298 aelfred2.setLocale(locale); 299 } 300 301 /** 302 * <b>SAX1</b>: Preferred API to parse an XML document, using a 303 * system identifier (URI). 304 */ 305 public void parse(String systemId) 306 throws SAXException, IOException 307 { 308 parse(new InputSource(systemId)); 309 } 310 311 /** 312 * <b>SAX1</b>: Underlying API to parse an XML document, used 313 * directly when no URI is available. When this is invoked, 314 * and the parser is set to validate, some features will be 315 * automatically reset to appropriate values: for reporting 316 * namespace prefixes, and incorporating external entities. 317 * 318 * @param source The XML input source. 319 * 320 * @exception IllegalStateException if called mid-parse 321 * @exception SAXException The handlers may throw any SAXException, 322 * and the parser normally throws SAXParseException objects. 323 * @exception IOException IOExceptions are normally through through 324 * the parser if there are problems reading the source document. 325 */ 326 public void parse(InputSource source) 327 throws SAXException, IOException 328 { 329 EventFilter next; 330 boolean nsdecls; 331 332 synchronized (aelfred2) 333 { 334 if (active) 335 { 336 throw new IllegalStateException("already parsing"); 337 } 338 active = true; 339 } 340 341 // set up the output pipeline 342 if (isValidating) 343 { 344 forceValidating(); 345 next = new ValidationConsumer(filter); 346 } 347 else 348 { 349 next = filter; 350 } 351 352 // connect pipeline and error handler 353 // don't let _this_ call to bind() affect xmlns* attributes 354 nsdecls = aelfred2.getFeature(SAXDriver.FEATURE + "namespace-prefixes"); 355 EventFilter.bind(aelfred2, next); 356 if (!nsdecls) 357 { 358 aelfred2.setFeature(SAXDriver.FEATURE + "namespace-prefixes", 359 false); 360 } 361 362 // parse, clean up 363 try 364 { 365 aelfred2.parse(source); 366 } 367 finally 368 { 369 active = false; 370 } 371 } 372 373 } 374