1 package com.thaiopensource.validate.schematron; 2 3 import com.thaiopensource.util.PropertyMap; 4 import com.thaiopensource.util.PropertyMapBuilder; 5 import com.thaiopensource.util.Localizer; 6 import com.thaiopensource.util.PropertyId; 7 import com.thaiopensource.validate.IncorrectSchemaException; 8 import com.thaiopensource.validate.Schema; 9 import com.thaiopensource.validate.SchemaReader; 10 import com.thaiopensource.validate.ValidateProperty; 11 import com.thaiopensource.validate.Validator; 12 import com.thaiopensource.validate.Option; 13 import com.thaiopensource.validate.rng.CompactSchemaReader; 14 import com.thaiopensource.validate.rng.RngProperty; 15 import com.thaiopensource.xml.sax.CountingErrorHandler; 16 import com.thaiopensource.xml.sax.DelegatingContentHandler; 17 import com.thaiopensource.xml.sax.DraconianErrorHandler; 18 import com.thaiopensource.xml.sax.ForkContentHandler; 19 import com.thaiopensource.xml.sax.XMLReaderCreator; 20 import org.xml.sax.Attributes ; 21 import org.xml.sax.ContentHandler ; 22 import org.xml.sax.ErrorHandler ; 23 import org.xml.sax.InputSource ; 24 import org.xml.sax.Locator ; 25 import org.xml.sax.SAXException ; 26 import org.xml.sax.SAXParseException ; 27 import org.xml.sax.XMLReader ; 28 29 import javax.xml.transform.ErrorListener ; 30 import javax.xml.transform.SourceLocator ; 31 import javax.xml.transform.Templates ; 32 import javax.xml.transform.Transformer ; 33 import javax.xml.transform.TransformerConfigurationException ; 34 import javax.xml.transform.TransformerException ; 35 import javax.xml.transform.TransformerFactory ; 36 import javax.xml.transform.sax.SAXResult ; 37 import javax.xml.transform.sax.SAXSource ; 38 import javax.xml.transform.stream.StreamSource ; 39 import java.io.IOException ; 40 import java.io.InputStream ; 41 42 class SchemaReaderImpl implements SchemaReader { 43 static final String SCHEMATRON_URI = "http://www.ascc.net/xml/schematron"; 44 private static final String LOCATION_URI = "http://www.thaiopensource.com/ns/location"; 45 private static final String ERROR_URI = "http://www.thaiopensource.com/ns/error"; 46 private final Localizer localizer = new Localizer(SchemaReaderImpl.class); 47 48 private final Class transformerFactoryClass; 49 private final Templates schematron; 50 private final Schema schematronSchema; 51 private static final String SCHEMATRON_SCHEMA = "schematron.rnc"; 52 private static final String SCHEMATRON_STYLESHEET = "schematron.xsl"; 53 private static final PropertyId[] supportedPropertyIds = { 54 ValidateProperty.ERROR_HANDLER, 55 ValidateProperty.XML_READER_CREATOR, 56 SchematronProperty.DIAGNOSE, 57 SchematronProperty.PHASE, 58 }; 59 60 SchemaReaderImpl(TransformerFactory transformerFactory) throws TransformerConfigurationException , IncorrectSchemaException { 61 this.transformerFactoryClass = transformerFactory.getClass(); 62 String resourceName = fullResourceName(SCHEMATRON_STYLESHEET); 63 StreamSource source = new StreamSource (getResourceAsStream(resourceName)); 64 initTransformerFactory(transformerFactory); 65 schematron = transformerFactory.newTemplates(source); 66 InputSource schemaSource = new InputSource (getResourceAsStream(fullResourceName(SCHEMATRON_SCHEMA))); 67 PropertyMapBuilder builder = new PropertyMapBuilder(); 68 ValidateProperty.ERROR_HANDLER.put(builder, new DraconianErrorHandler()); 69 RngProperty.CHECK_ID_IDREF.add(builder); 70 try { 71 schematronSchema = CompactSchemaReader.getInstance().createSchema(schemaSource, builder.toPropertyMap()); 72 } 73 catch (SAXException e) { 74 throw new IncorrectSchemaException(); 75 } 76 catch (IOException e) { 77 throw new IncorrectSchemaException(); 78 } 79 } 80 81 public Option getOption(String uri) { 82 return SchematronProperty.getOption(uri); 83 } 84 85 private void initTransformerFactory(TransformerFactory factory) { 86 String name = factory.getClass().getName(); 87 try { 88 if (name.equals("com.icl.saxon.TransformerFactoryImpl")) 89 factory.setAttribute("http://icl.com/saxon/feature/linenumbering", 90 Boolean.TRUE); 91 else if (name.equals("org.apache.xalan.processor.TransformerFactoryImpl")) { 92 try { 94 factory.setAttribute("http://xml.apache.org/xalan/properties/source-location", 96 Boolean.TRUE); 97 } 98 catch (IllegalArgumentException e) { 99 factory.setAttribute("http://apache.org/xalan/features/source_location", 101 Boolean.TRUE); 102 } 103 } 104 } 105 catch (IllegalArgumentException e) { 106 } 107 } 108 109 static class ValidateStage extends XMLReaderImpl { 110 private final ContentHandler validator; 111 private ContentHandler contentHandler; 112 private final XMLReader reader; 113 private final CountingErrorHandler ceh; 114 115 ValidateStage(XMLReader reader, Validator validator, CountingErrorHandler ceh) { 116 this.reader = reader; 117 this.validator = validator.getContentHandler(); 118 this.ceh = ceh; 119 } 120 121 public void parse(InputSource input) 122 throws SAXException , IOException { 123 reader.parse(input); 124 if (ceh.getHadErrorOrFatalError()) 125 throw new SAXException (new IncorrectSchemaException()); 126 } 127 128 public void setContentHandler(ContentHandler handler) { 129 this.contentHandler = handler; 130 reader.setContentHandler(new ForkContentHandler(validator, contentHandler)); 131 } 132 133 public ContentHandler getContentHandler() { 134 return contentHandler; 135 } 136 } 137 138 static class UserException extends Exception { 139 private final SAXException exception; 140 141 UserException(SAXException exception) { 142 this.exception = exception; 143 } 144 145 SAXException getException() { 146 return exception; 147 } 148 } 149 150 static class UserWrapErrorHandler extends CountingErrorHandler { 151 UserWrapErrorHandler(ErrorHandler errorHandler) { 152 super(errorHandler); 153 } 154 155 public void warning(SAXParseException exception) 156 throws SAXException { 157 try { 158 super.warning(exception); 159 } 160 catch (SAXException e) { 161 throw new SAXException (new UserException(e)); 162 } 163 } 164 165 public void error(SAXParseException exception) 166 throws SAXException { 167 try { 168 super.error(exception); 169 } 170 catch (SAXException e) { 171 throw new SAXException (new UserException(e)); 172 } 173 } 174 175 public void fatalError(SAXParseException exception) 176 throws SAXException { 177 try { 178 super.fatalError(exception); 179 } 180 catch (SAXException e) { 181 throw new SAXException (new UserException(e)); 182 } 183 } 184 } 185 186 static class ErrorFilter extends DelegatingContentHandler { 187 private final ErrorHandler eh; 188 private final Localizer localizer; 189 private Locator locator; 190 191 ErrorFilter(ContentHandler delegate, ErrorHandler eh, Localizer localizer) { 192 super(delegate); 193 this.eh = eh; 194 this.localizer = localizer; 195 } 196 197 public void setDocumentLocator(Locator locator) { 198 this.locator = locator; 199 super.setDocumentLocator(locator); 200 } 201 202 public void startElement(String namespaceURI, String localName, 203 String qName, Attributes atts) 204 throws SAXException { 205 if (namespaceURI.equals(ERROR_URI) && localName.equals("error")) 206 eh.error(new SAXParseException (localizer.message(atts.getValue("", "message"), 207 atts.getValue("", "arg")), 208 locator)); 209 super.startElement(namespaceURI, localName, qName, atts); 210 } 211 } 212 213 static class LocationFilter extends DelegatingContentHandler implements Locator { 214 private final String systemId; 215 private int lineNumber = -1; 216 private SAXException exception = null; 217 218 LocationFilter(ContentHandler delegate, String systemId) { 219 super(delegate); 220 this.systemId = systemId; 221 } 222 223 SAXException getException() { 224 return exception; 225 } 226 227 public void setDocumentLocator(Locator locator) { 228 } 229 230 public void startDocument() 231 throws SAXException { 232 getDelegate().setDocumentLocator(this); 233 super.startDocument(); 234 } 235 236 public void startElement(String namespaceURI, String localName, 237 String qName, Attributes atts) 238 throws SAXException { 239 String value = atts.getValue(LOCATION_URI, "line-number"); 240 if (value != null) { 241 try { 242 lineNumber = Integer.parseInt(value); 243 } 244 catch (NumberFormatException e) { 245 lineNumber = -1; 246 } 247 } 248 else 249 lineNumber = -1; 250 try { 251 super.startElement(namespaceURI, localName, qName, atts); 252 } 253 catch (SAXException e) { 254 this.exception = e; 255 setDelegate(null); 256 } 257 lineNumber = -1; 258 } 259 260 public String getPublicId() { 261 return null; 262 } 263 264 public String getSystemId() { 265 return systemId; 266 } 267 268 public int getLineNumber() { 269 return lineNumber; 270 } 271 272 public int getColumnNumber() { 273 return -1; 274 } 275 } 276 277 static class TransformStage extends XMLReaderImpl { 278 private ContentHandler contentHandler; 279 private final Transformer transformer; 280 private final SAXSource transformSource; 281 private final String systemId; 282 private final CountingErrorHandler ceh; 283 private final Localizer localizer; 284 285 TransformStage(Transformer transformer, SAXSource transformSource, String systemId, 286 CountingErrorHandler ceh, Localizer localizer) { 287 this.transformer = transformer; 288 this.transformSource = transformSource; 289 this.systemId = systemId; 290 this.ceh = ceh; 291 this.localizer = localizer; 292 } 293 294 public void parse(InputSource input) 295 throws IOException , SAXException { 296 try { 297 LocationFilter handler = new LocationFilter(new ErrorFilter(contentHandler, ceh, localizer), 298 systemId); 299 transformer.transform(transformSource, new SAXResult (handler)); 300 SAXException exception = handler.getException(); 301 if (exception != null) 302 throw exception; 303 } 304 catch (TransformerException e) { 305 if (e.getException() instanceof IOException ) 306 throw (IOException )e.getException(); 307 throw ValidatorImpl.toSAXException(e); 308 } 309 if (ceh.getHadErrorOrFatalError()) 310 throw new SAXException (new IncorrectSchemaException()); 311 } 312 313 public ContentHandler getContentHandler() { 314 return contentHandler; 315 } 316 317 public void setContentHandler(ContentHandler contentHandler) { 318 this.contentHandler = contentHandler; 319 } 320 } 321 322 static class SAXErrorListener implements ErrorListener { 323 private final ErrorHandler eh; 324 private final String systemId; 325 private boolean hadError = false; 326 SAXErrorListener(ErrorHandler eh, String systemId) { 327 this.eh = eh; 328 this.systemId = systemId; 329 } 330 331 boolean getHadError() { 332 return hadError; 333 } 334 335 public void warning(TransformerException exception) 336 throws TransformerException { 337 SAXParseException spe = transform(exception); 338 try { 339 eh.warning(spe); 340 } 341 catch (SAXException e) { 342 throw new TransformerException (new UserException(e)); 343 } 344 } 345 346 public void error(TransformerException exception) 347 throws TransformerException { 348 hadError = true; 349 SAXParseException spe = transform(exception); 350 try { 351 eh.error(spe); 352 } 353 catch (SAXException e) { 354 throw new TransformerException (new UserException(e)); 355 } 356 } 357 358 public void fatalError(TransformerException exception) 359 throws TransformerException { 360 hadError = true; 361 SAXParseException spe = transform(exception); 362 try { 363 eh.fatalError(spe); 364 } 365 catch (SAXException e) { 366 throw new TransformerException (new UserException(e)); 367 } 368 } 369 370 SAXParseException transform(TransformerException exception) throws TransformerException { 371 Throwable cause = exception.getException(); 372 if (cause instanceof RuntimeException ) 374 throw (RuntimeException )cause; 375 if (cause instanceof SAXException 376 || cause instanceof IncorrectSchemaException 377 || cause instanceof IOException ) 378 throw exception; 379 SourceLocator locator = exception.getLocator(); 380 if (locator == null) 381 return new SAXParseException (exception.getMessage(), null); 382 String s = locator.getSystemId(); 384 if (s == null) 385 s = systemId; 386 return new SAXParseException (exception.getMessage(), 387 null, 388 s, 389 locator.getLineNumber(), 390 -1); 391 } 392 } 393 394 public Schema createSchema(InputSource in, PropertyMap properties) 395 throws IOException , SAXException , IncorrectSchemaException { 396 ErrorHandler eh = ValidateProperty.ERROR_HANDLER.get(properties); 397 SAXErrorListener errorListener = new SAXErrorListener(eh, in.getSystemId()); 398 UserWrapErrorHandler ueh1 = new UserWrapErrorHandler(eh); 399 UserWrapErrorHandler ueh2 = new UserWrapErrorHandler(eh); 400 try { 401 PropertyMapBuilder builder = new PropertyMapBuilder(properties); 402 ValidateProperty.ERROR_HANDLER.put(builder, ueh1); 403 SAXSource source = createValidatingSource(in, builder.toPropertyMap(), ueh1); 404 source = createTransformingSource(source, 405 SchematronProperty.PHASE.get(properties), 406 properties.contains(SchematronProperty.DIAGNOSE), 407 in.getSystemId(), 408 ueh2); 409 TransformerFactory transformerFactory = (TransformerFactory )transformerFactoryClass.newInstance(); 410 initTransformerFactory(transformerFactory); 411 transformerFactory.setErrorListener(errorListener); 412 Templates templates = transformerFactory.newTemplates(source); 413 return new SchemaImpl(templates, properties, supportedPropertyIds); 414 } 415 catch (TransformerConfigurationException e) { 416 throw toSAXException(e, errorListener.getHadError() 417 || ueh1.getHadErrorOrFatalError() 418 || ueh2.getHadErrorOrFatalError()); 419 } 420 catch (InstantiationException e) { 421 throw new SAXException (e); 422 } 423 catch (IllegalAccessException e) { 424 throw new SAXException (e); 425 } 426 } 427 428 private SAXSource createValidatingSource(InputSource in, PropertyMap properties, CountingErrorHandler ceh) throws SAXException { 429 Validator validator = schematronSchema.createValidator(properties); 430 XMLReaderCreator xrc = ValidateProperty.XML_READER_CREATOR.get(properties); 431 XMLReader xr = xrc.createXMLReader(); 432 xr.setErrorHandler(ceh); 433 return new SAXSource (new ValidateStage(xr, validator, ceh), in); 434 } 435 436 private SAXSource createTransformingSource(SAXSource in, String phase, boolean diagnose, 437 String systemId, CountingErrorHandler ceh) throws SAXException { 438 try { 439 Transformer transformer = schematron.newTransformer(); 440 transformer.setErrorListener(new DraconianErrorListener()); 441 if (phase != null) 442 transformer.setParameter("phase", phase); 443 if (diagnose) 444 transformer.setParameter("diagnose", Boolean.TRUE); 445 return new SAXSource (new TransformStage(transformer, in, systemId, ceh, localizer), 446 new InputSource (systemId)); 447 } 448 catch (TransformerConfigurationException e) { 449 throw new SAXException (e); 450 } 451 } 452 453 private SAXException toSAXException(TransformerException e, boolean hadError) throws IOException , IncorrectSchemaException { 454 return causeToSAXException(e.getException(), hadError); 455 } 456 457 private SAXException causeToSAXException(Throwable cause, boolean hadError) throws IOException , IncorrectSchemaException { 458 if (cause instanceof RuntimeException ) 459 throw (RuntimeException )cause; 460 if (cause instanceof IOException ) 461 throw (IOException )cause; 462 if (cause instanceof IncorrectSchemaException) 463 throw (IncorrectSchemaException)cause; 464 if (cause instanceof SAXException ) 465 return causeToSAXException(((SAXException )cause).getException(), hadError); 466 if (cause instanceof TransformerException ) 467 return toSAXException((TransformerException )cause, hadError); 468 if (cause instanceof UserException) 469 return toSAXException((UserException)cause); 470 if (hadError) 471 throw new IncorrectSchemaException(); 472 return new SAXException (localizer.message("unexpected_schema_creation_error"), 473 cause instanceof Exception ? (Exception )cause : null); 474 } 475 476 private static SAXException toSAXException(UserException e) throws IOException , IncorrectSchemaException { 477 SAXException se = e.getException(); 478 Exception cause = se.getException(); 479 if (cause instanceof IncorrectSchemaException) 480 throw (IncorrectSchemaException)cause; 481 if (cause instanceof IOException ) 482 throw (IOException )cause; 483 return se; 484 } 485 486 private static String fullResourceName(String name) { 487 String className = SchemaReaderImpl.class.getName(); 488 return className.substring(0, className.lastIndexOf('.')).replace('.', '/') + "/resources/" + name; 489 } 490 491 private static InputStream getResourceAsStream(String resourceName) { 492 ClassLoader cl = SchemaReaderImpl.class.getClassLoader(); 493 if (cl == null) 495 return ClassLoader.getSystemResourceAsStream(resourceName); 496 else 497 return cl.getResourceAsStream(resourceName); 498 } 499 } 500 | Popular Tags |