001 package com.oxygenxml.validate.isoschematron;
002
003 import com.thaiopensource.util.PropertyMap;
004 import com.thaiopensource.util.PropertyMapBuilder;
005 import com.thaiopensource.util.Localizer;
006 import com.thaiopensource.util.PropertyId;
007 import com.thaiopensource.validate.IncorrectSchemaException;
008 import com.thaiopensource.validate.Schema;
009 import com.thaiopensource.validate.SchemaReader;
010 import com.thaiopensource.validate.ValidateProperty;
011 import com.thaiopensource.validate.Validator;
012 import com.thaiopensource.validate.Option;
013 import com.thaiopensource.validate.rng.CompactSchemaReader;
014 import com.thaiopensource.validate.rng.RngProperty;
015 import com.thaiopensource.xml.sax.CountingErrorHandler;
016 import com.thaiopensource.xml.sax.DelegatingContentHandler;
017 import com.thaiopensource.xml.sax.DraconianErrorHandler;
018 import com.thaiopensource.xml.sax.ForkContentHandler;
019 import com.thaiopensource.xml.sax.XMLReaderCreator;
020 import org.xml.sax.Attributes;
021 import org.xml.sax.ContentHandler;
022 import org.xml.sax.ErrorHandler;
023 import org.xml.sax.InputSource;
024 import org.xml.sax.Locator;
025 import org.xml.sax.SAXException;
026 import org.xml.sax.SAXParseException;
027 import org.xml.sax.XMLReader;
028
029 import javax.xml.transform.ErrorListener;
030 import javax.xml.transform.SourceLocator;
031 import javax.xml.transform.Templates;
032 import javax.xml.transform.Transformer;
033 import javax.xml.transform.TransformerConfigurationException;
034 import javax.xml.transform.TransformerException;
035 import javax.xml.transform.TransformerFactory;
036 import javax.xml.transform.sax.SAXResult;
037 import javax.xml.transform.sax.SAXSource;
038 import javax.xml.transform.stream.StreamSource;
039 import java.io.IOException;
040 import java.io.InputStream;
041
042 class SchemaReaderImpl implements SchemaReader {
043 static final String ISO_SCHEMATRON_URI = "http://purl.oclc.org/dsdl/schematron";
044 private static final String LOCATION_URI = "http://www.thaiopensource.com/ns/location";
045 private static final String ERROR_URI = "http://www.thaiopensource.com/ns/error";
046 private final Localizer localizer = new Localizer(SchemaReaderImpl.class);
047
048 private final Class transformerFactoryClass;
049 private final Templates schematron;
050 private final Schema schematronSchema;
051 private static final String SCHEMATRON_SCHEMA = "iso-schematron.rnc";
052 private static final String SCHEMATRON_STYLESHEET = "iso_schematron_skeleton.xsl";
053 private static final PropertyId[] supportedPropertyIds = {
054 ValidateProperty.ERROR_HANDLER,
055 ValidateProperty.XML_READER_CREATOR,
056 SchematronProperty.DIAGNOSE,
057 SchematronProperty.PHASE,
058 };
059
060 SchemaReaderImpl(TransformerFactory transformerFactory) throws TransformerConfigurationException, IncorrectSchemaException {
061 this.transformerFactoryClass = transformerFactory.getClass();
062 String resourceName = fullResourceName(SCHEMATRON_STYLESHEET);
063 StreamSource source = new StreamSource(getResourceAsStream(resourceName));
064 initTransformerFactory(transformerFactory);
065 schematron = transformerFactory.newTemplates(source);
066 InputSource schemaSource = new InputSource(getResourceAsStream(fullResourceName(SCHEMATRON_SCHEMA)));
067 PropertyMapBuilder builder = new PropertyMapBuilder();
068 ValidateProperty.ERROR_HANDLER.put(builder, new DraconianErrorHandler());
069 RngProperty.CHECK_ID_IDREF.add(builder);
070 try {
071 schematronSchema = CompactSchemaReader.getInstance().createSchema(schemaSource, builder.toPropertyMap());
072 }
073 catch (SAXException e) {
074 throw new IncorrectSchemaException();
075 }
076 catch (IOException e) {
077 throw new IncorrectSchemaException();
078 }
079 }
080
081 public Option getOption(String uri) {
082 return SchematronProperty.getOption(uri);
083 }
084
085 private void initTransformerFactory(TransformerFactory factory) {
086 String name = factory.getClass().getName();
087 try {
088 if (name.equals("com.icl.saxon.TransformerFactoryImpl"))
089 factory.setAttribute("http://icl.com/saxon/feature/linenumbering",
090 Boolean.TRUE);
091 else if (name.equals("org.apache.xalan.processor.TransformerFactoryImpl")) {
092 // Try both the documented URI and the URI that the code expects.
093 try {
094 // This is the URI that the code expects.
095 factory.setAttribute("http://xml.apache.org/xalan/properties/source-location",
096 Boolean.TRUE);
097 }
098 catch (IllegalArgumentException e) {
099 // This is the URI that's documented.
100 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 // Xalan takes it upon itself to catch exceptions and pass them to the ErrorListener.
373 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 // Xalan sometimes loses the systemId; work around this.
383 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 // XXX see if we should borrow 1.2 code from Service
494 if (cl == null)
495 return ClassLoader.getSystemResourceAsStream(resourceName);
496 else
497 return cl.getResourceAsStream(resourceName);
498 }
499 }