All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.thaiopensource.validate.schematron.SchemaReaderImpl Maven / Gradle / Ivy

Go to download

A branch of Jing used by the Nu Html Checker. (Jing is a tool for validating documents against RelaxNG schemas.)

The newest version!
package com.thaiopensource.validate.schematron;

import com.thaiopensource.util.Localizer;
import com.thaiopensource.util.PropertyId;
import com.thaiopensource.util.PropertyMap;
import com.thaiopensource.util.PropertyMapBuilder;
import com.thaiopensource.validate.AbstractSchemaReader;
import com.thaiopensource.validate.IncorrectSchemaException;
import com.thaiopensource.validate.Option;
import com.thaiopensource.validate.ResolverFactory;
import com.thaiopensource.validate.Schema;
import com.thaiopensource.validate.ValidateProperty;
import com.thaiopensource.validate.Validator;
import com.thaiopensource.validate.prop.rng.RngProperty;
import com.thaiopensource.validate.prop.schematron.SchematronProperty;
import com.thaiopensource.validate.rng.CompactSchemaReader;
import com.thaiopensource.xml.sax.CountingErrorHandler;
import com.thaiopensource.xml.sax.DelegatingContentHandler;
import com.thaiopensource.xml.sax.DraconianErrorHandler;
import com.thaiopensource.xml.sax.ForkContentHandler;
import com.thaiopensource.xml.sax.Resolver;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.InputStream;

class SchemaReaderImpl extends AbstractSchemaReader {
  static final String SCHEMATRON_URI = "http://www.ascc.net/xml/schematron";
  private static final String LOCATION_URI = "http://www.thaiopensource.com/ns/location";
  private static final String ERROR_URI = "http://www.thaiopensource.com/ns/error";
  private final Localizer localizer = new Localizer(SchemaReaderImpl.class);

  private final Class transformerFactoryClass;
  private final TransformerFactoryInitializer transformerFactoryInitializer;
  private final Templates schematron;
  private final Schema schematronSchema;
  private static final String SCHEMATRON_SCHEMA = "schematron.rnc";
  private static final String SCHEMATRON_STYLESHEET = "schematron.xsl";
  private static final String SCHEMATRON_XSLTC_STYLESHEET = "schematron-xsltc.xsl";
  private static final PropertyId[] supportedPropertyIds = {
    ValidateProperty.ERROR_HANDLER,
    ValidateProperty.XML_READER_CREATOR,
    ValidateProperty.ENTITY_RESOLVER,
    ValidateProperty.URI_RESOLVER,
    ValidateProperty.RESOLVER,
    SchematronProperty.DIAGNOSE,
    SchematronProperty.PHASE,
  };

  SchemaReaderImpl(SAXTransformerFactory transformerFactory, TransformerFactoryInitializer transformerFactoryInitializer)
          throws TransformerConfigurationException, IncorrectSchemaException {
    this.transformerFactoryClass = transformerFactory.getClass();
    this.transformerFactoryInitializer = transformerFactoryInitializer;
    final boolean isXsltc = (transformerFactoryClass.getName().indexOf(".xsltc.") >= 0);
    final String stylesheet = isXsltc ? SCHEMATRON_XSLTC_STYLESHEET : SCHEMATRON_STYLESHEET;
    final String resourceName = fullResourceName(stylesheet);
    final StreamSource source = new StreamSource(getResourceAsStream(resourceName));
    initTransformerFactory(transformerFactory);
    schematron = transformerFactory.newTemplates(source);
    InputSource schemaSource = new InputSource(getResourceAsStream(fullResourceName(SCHEMATRON_SCHEMA)));
    PropertyMapBuilder builder = new PropertyMapBuilder();
    ValidateProperty.ERROR_HANDLER.put(builder, new DraconianErrorHandler());
    RngProperty.CHECK_ID_IDREF.add(builder);
    try {
      schematronSchema = CompactSchemaReader.getInstance().createSchema(schemaSource, builder.toPropertyMap());
    }
    catch (SAXException e) {
      throw new IncorrectSchemaException();
    }
    catch (IOException e) {
      throw new IncorrectSchemaException();
    }
  }

  public Option getOption(String uri) {
    return SchematronProperty.getOption(uri);
  }

  private void initTransformerFactory(TransformerFactory factory) {
    transformerFactoryInitializer.initTransformerFactory(factory);
  }

  static class ValidateStage extends XMLReaderImpl {
    private final ContentHandler validator;
    private ContentHandler contentHandler;
    private final XMLReader reader;
    private final CountingErrorHandler ceh;

    ValidateStage(XMLReader reader, Validator validator, CountingErrorHandler ceh) {
      this.reader = reader;
      this.validator = validator.getContentHandler();
      this.ceh = ceh;
    }

    public void parse(InputSource input)
            throws SAXException, IOException {
      reader.parse(input);
      if (ceh.getHadErrorOrFatalError())
        throw new SAXException(new IncorrectSchemaException());
    }

    public void setContentHandler(ContentHandler handler) {
      this.contentHandler = handler;
      reader.setContentHandler(new ForkContentHandler(validator, contentHandler));
    }

    public ContentHandler getContentHandler() {
      return contentHandler;
    }
  }

  static class UserException extends Exception {
    private final SAXException exception;

    UserException(SAXException exception) {
      this.exception = exception;
    }

    SAXException getException() {
      return exception;
    }
  }

  static class UserWrapErrorHandler extends CountingErrorHandler {
    UserWrapErrorHandler(ErrorHandler errorHandler) {
      super(errorHandler);
    }

    public void warning(SAXParseException exception)
            throws SAXException {
      try {
        super.warning(exception);
      }
      catch (SAXException e) {
        throw new SAXException(new UserException(e));
      }
    }

    public void error(SAXParseException exception)
            throws SAXException {
      try {
        super.error(exception);
      }
      catch (SAXException e) {
        throw new SAXException(new UserException(e));
      }
    }

    public void fatalError(SAXParseException exception)
            throws SAXException {
      try {
        super.fatalError(exception);
      }
      catch (SAXException e) {
        throw new SAXException(new UserException(e));
      }
    }
  }

  static class ErrorFilter extends DelegatingContentHandler {
    private final ErrorHandler eh;
    private final Localizer localizer;
    private Locator locator;

    ErrorFilter(ContentHandler delegate, ErrorHandler eh, Localizer localizer) {
      super(delegate);
      this.eh = eh;
      this.localizer = localizer;
    }

    public void setDocumentLocator(Locator locator) {
      this.locator = locator;
      super.setDocumentLocator(locator);
    }

    public void startElement(String namespaceURI, String localName,
                             String qName, Attributes atts)
            throws SAXException {
      if (namespaceURI.equals(ERROR_URI) && localName.equals("error"))
        eh.error(new SAXParseException(localizer.message(atts.getValue("", "message"),
                                                         atts.getValue("", "arg")),
                                       locator));
      super.startElement(namespaceURI, localName, qName, atts);
    }
  }

  static class LocationFilter extends DelegatingContentHandler implements Locator {
    private final String mainSystemId;
    private String systemId = null;
    private int lineNumber = -1;
    private int columnNumber = -1;
    private SAXException exception = null;

    LocationFilter(ContentHandler delegate, String systemId) {
      super(delegate);
      this.mainSystemId = systemId;
    }

    SAXException getException() {
      return exception;
    }

    public void setDocumentLocator(Locator locator) {
    }

    public void startDocument()
            throws SAXException {
      getDelegate().setDocumentLocator(this);
      super.startDocument();
    }

    public void startElement(String namespaceURI, String localName,
                             String qName, Attributes atts)
            throws SAXException {
      systemId = getLocationAttribute(atts, "system-id");
      lineNumber = toInteger(getLocationAttribute(atts, "line-number"));
      columnNumber = toInteger(getLocationAttribute(atts, "column-number"));
      try {
        super.startElement(namespaceURI, localName, qName, atts);
      }
      catch (SAXException e) {
        this.exception = e;
        setDelegate(null);
      }
      systemId = null;
      lineNumber = -1;
      columnNumber = -1;
    }

    private static String getLocationAttribute(Attributes atts, String name) {
      return atts.getValue(LOCATION_URI, name);
    }

    private static int toInteger(String value) {
      if (value == null)
        return -1;
      try {
        return Integer.parseInt(value);
      }
      catch (NumberFormatException e) {
        return -1;
      }
    }

    public String getPublicId() {
      return null;
    }

    public String getSystemId() {
      if (systemId != null && !systemId.equals(""))
        return systemId;
      return mainSystemId;
    }

    public int getLineNumber() {
      return lineNumber;
    }

    public int getColumnNumber() {
      return columnNumber;
    }
  }

  static class TransformStage extends XMLReaderImpl {
    private ContentHandler contentHandler;
    private final Transformer transformer;
    private final SAXSource transformSource;
    private final String systemId;
    private final CountingErrorHandler ceh;
    private final Localizer localizer;

    TransformStage(Transformer transformer, SAXSource transformSource, String systemId,
                   CountingErrorHandler ceh, Localizer localizer) {
      this.transformer = transformer;
      this.transformSource = transformSource;
      this.systemId = systemId;
      this.ceh = ceh;
      this.localizer = localizer;
    }

    public void parse(InputSource input)
            throws IOException, SAXException {
      try {
        LocationFilter handler = new LocationFilter(new ErrorFilter(contentHandler, ceh, localizer),
                                                                    systemId);
        transformer.transform(transformSource, new SAXResult(handler));
        SAXException exception = handler.getException();
        if (exception != null)
          throw exception;
      }
      catch (TransformerException e) {
        if (e.getException() instanceof IOException)
          throw (IOException)e.getException();
        throw toSAXException(e);
      }
      if (ceh.getHadErrorOrFatalError())
        throw new SAXException(new IncorrectSchemaException());
    }

    public ContentHandler getContentHandler() {
      return contentHandler;
    }

    public void setContentHandler(ContentHandler contentHandler) {
      this.contentHandler = contentHandler;
    }
  }

  static class SAXErrorListener implements ErrorListener {
     private final ErrorHandler eh;
     private final String systemId;
     private boolean hadError = false;
     SAXErrorListener(ErrorHandler eh, String systemId) {
       this.eh = eh;
       this.systemId = systemId;
     }

     boolean getHadError() {
       return hadError;
     }

     public void warning(TransformerException exception)
             throws TransformerException {
       SAXParseException spe = transform(exception);
       try {
         eh.warning(spe);
       }
       catch (SAXException e) {
         throw new TransformerException(new UserException(e));
       }
     }

     public void error(TransformerException exception)
             throws TransformerException {
       hadError = true;
       SAXParseException spe = transform(exception);
       try {
         eh.error(spe);
       }
       catch (SAXException e) {
         throw new TransformerException(new UserException(e));
       }
     }

     public void fatalError(TransformerException exception)
             throws TransformerException {
       hadError = true;
       SAXParseException spe = transform(exception);
       try {
         eh.fatalError(spe);
       }
       catch (SAXException e) {
         throw new TransformerException(new UserException(e));
       }
     }

     SAXParseException transform(TransformerException exception) throws TransformerException {
       Throwable cause = exception.getException();
       // Xalan takes it upon itself to catch exceptions and pass them to the ErrorListener.
       if (cause instanceof RuntimeException)
         throw (RuntimeException)cause;
       if (cause instanceof SAXException
           || cause instanceof IncorrectSchemaException
           || cause instanceof IOException)
         throw exception;
       SourceLocator locator = exception.getLocator();
       if (locator == null)
         return new SAXParseException(exception.getMessage(), null);
       // Xalan sometimes loses the mainSystemId; work around this.
       String s = locator.getSystemId();
       if (s == null)
        s = systemId;
       return new SAXParseException(exception.getMessage(),
                                    locator.getPublicId(),
                                    s,
                                    locator.getLineNumber(),
                                    locator.getColumnNumber());
     }
   }


  // This is an alternative approach for implementing createSchema.
  // This approach uses SAXTransformerFactory. We will stick with
  // the original for now since it works and is debugged.  Also
  // Saxon 6.5.2 prints to System.err in TemplatesHandlerImpl.getTemplates().

  private Schema createSchema2(InputSource in, PropertyMap properties)
            throws IOException, SAXException, IncorrectSchemaException {
    ErrorHandler eh = ValidateProperty.ERROR_HANDLER.get(properties);
    CountingErrorHandler ceh = new CountingErrorHandler(eh);
    String systemId = in.getSystemId();
    try {
      SAXTransformerFactory factory = (SAXTransformerFactory)transformerFactoryClass.newInstance();
      initTransformerFactory(factory);
      TransformerHandler transformerHandler = factory.newTransformerHandler(schematron);
      // XXX set up phase and diagnose
      PropertyMapBuilder builder = new PropertyMapBuilder(properties);
      ValidateProperty.ERROR_HANDLER.put(builder, ceh);
      Validator validator = schematronSchema.createValidator(builder.toPropertyMap());
      Resolver resolver = ResolverFactory.createResolver(properties);
      XMLReader xr = resolver.createXMLReader();
      xr.setContentHandler(new ForkContentHandler(validator.getContentHandler(),
                                                  transformerHandler));
      factory.setErrorListener(new SAXErrorListener(ceh, systemId));
      TemplatesHandler templatesHandler = factory.newTemplatesHandler();
      LocationFilter stage2 = new LocationFilter(new ErrorFilter(templatesHandler, ceh, localizer), systemId);
      transformerHandler.setResult(new SAXResult(stage2));
      xr.setErrorHandler(ceh);
      xr.parse(in);
      SAXException exception = stage2.getException();
      if (exception != null)
        throw exception;
      if (ceh.getHadErrorOrFatalError())
        throw new IncorrectSchemaException();
      return new SchemaImpl(templatesHandler.getTemplates(),
                            transformerFactoryClass,
                            properties,
                            supportedPropertyIds);
    }
    catch (TransformerConfigurationException e) {
      throw new SAXException(localizer.message("unexpected_schema_creation_error"));
    }
    catch (InstantiationException e) {
      throw new SAXException(e);
    }
    catch (IllegalAccessException e) {
      throw new SAXException(e);
    }
  }

  // This implementation was written in ignorance of SAXTransformerFactory.

  public Schema createSchema(SAXSource source, PropertyMap properties)
          throws IOException, SAXException, IncorrectSchemaException {
    ErrorHandler eh = ValidateProperty.ERROR_HANDLER.get(properties);
    SAXErrorListener errorListener = new SAXErrorListener(eh, source.getSystemId());
    UserWrapErrorHandler ueh1 = new UserWrapErrorHandler(eh);
    UserWrapErrorHandler ueh2 = new UserWrapErrorHandler(eh);
    try {
      PropertyMapBuilder builder = new PropertyMapBuilder(properties);
      ValidateProperty.ERROR_HANDLER.put(builder, ueh1);
      source = createValidatingSource(source, builder.toPropertyMap(), ueh1);
      source = createTransformingSource(source,
                                        SchematronProperty.PHASE.get(properties),
                                        properties.contains(SchematronProperty.DIAGNOSE),
                                        source.getSystemId(),
                                        ueh2);
      TransformerFactory transformerFactory = (TransformerFactory)transformerFactoryClass.newInstance();
      initTransformerFactory(transformerFactory);
      transformerFactory.setErrorListener(errorListener);
      transformerFactory.setURIResolver(ResolverFactory.createResolver(properties).getURIResolver());  
      Templates templates = transformerFactory.newTemplates(source);
      return new SchemaImpl(templates, transformerFactoryClass, properties, supportedPropertyIds);
    }
    catch (TransformerConfigurationException e) {
      throw toSAXException(e, errorListener.getHadError()
                              || ueh1.getHadErrorOrFatalError()
                              || ueh2.getHadErrorOrFatalError());
    }
    catch (InstantiationException e) {
      throw new SAXException(e);
    }
    catch (IllegalAccessException e) {
      throw new SAXException(e);
    }
  }

  private SAXSource createValidatingSource(SAXSource source, PropertyMap properties, CountingErrorHandler ceh) throws SAXException {
    Validator validator = schematronSchema.createValidator(properties);
    XMLReader xr = source.getXMLReader();
    if (xr == null)
      xr = ResolverFactory.createResolver(properties).createXMLReader();
    xr.setErrorHandler(ceh);
    return new SAXSource(new ValidateStage(xr, validator, ceh), source.getInputSource());
  }

  private SAXSource createTransformingSource(SAXSource in, String phase, boolean diagnose,
                                             String systemId, CountingErrorHandler ceh) throws SAXException {
    try {
      Transformer transformer = schematron.newTransformer();
      transformer.setErrorListener(new DraconianErrorListener());
      if (phase != null)
        transformer.setParameter("phase", phase);
      if (diagnose)
        transformer.setParameter("diagnose", Boolean.TRUE);
      return new SAXSource(new TransformStage(transformer, in, systemId, ceh, localizer),
                           new InputSource(systemId));
    }
    catch (TransformerConfigurationException e) {
      throw new SAXException(e);
    }
  }

  private SAXException toSAXException(TransformerException e, boolean hadError) throws IOException, IncorrectSchemaException {
      return causeToSAXException(e.getException(), hadError);
    }

  private SAXException causeToSAXException(Throwable cause, boolean hadError) throws IOException, IncorrectSchemaException {
      if (cause instanceof RuntimeException)
        throw (RuntimeException)cause;
      if (cause instanceof IOException)
        throw (IOException)cause;
      if (cause instanceof IncorrectSchemaException)
        throw (IncorrectSchemaException)cause;
      if (cause instanceof SAXException)
        return causeToSAXException(((SAXException)cause).getException(), hadError);
      if (cause instanceof TransformerException)
        return toSAXException((TransformerException)cause, hadError);
      if (cause instanceof UserException)
        return toSAXException((UserException)cause);
      if (hadError)
        throw new IncorrectSchemaException();
      return new SAXException(localizer.message("unexpected_schema_creation_error"),
                              cause instanceof Exception ? (Exception)cause : null);
    }

  private static SAXException toSAXException(UserException e) throws IOException, IncorrectSchemaException {
      SAXException se = e.getException();
      Exception cause = se.getException();
      if (cause instanceof IncorrectSchemaException)
        throw (IncorrectSchemaException)cause;
      if (cause instanceof IOException)
        throw (IOException)cause;
      return se;
    }

  private static String fullResourceName(String name) {
    String className = SchemaReaderImpl.class.getName();
    return className.substring(0, className.lastIndexOf('.')).replace('.', '/') + "/resources/" + name;
  }

  private static InputStream getResourceAsStream(String resourceName) {
    ClassLoader cl = SchemaReaderImpl.class.getClassLoader();
    // XXX see if we should borrow 1.2 code from Service
    if (cl == null)
      return ClassLoader.getSystemResourceAsStream(resourceName);
    else
      return cl.getResourceAsStream(resourceName);
  }

  static SAXException toSAXException(TransformerException transformerException) {
    // Unwrap where possible
    Throwable wrapped = transformerException.getException();
    if (wrapped instanceof SAXException)
      return (SAXException)wrapped;
    if (wrapped instanceof RuntimeException)
      throw (RuntimeException)wrapped;
    if (wrapped instanceof Exception)
      return new SAXException((Exception)wrapped);
    return new SAXException(transformerException);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy