Changeset 5056


Ignore:
Timestamp:
04/25/14 00:43:18 (10 years ago)
Author:
Oliver Schonefeld
Message:
  • add initial support for Schematron validation

Inspired by: Menzo

Location:
CMDIValidator/trunk/src/main
Files:
16 added
3 edited

Legend:

Unmodified
Added
Removed
  • CMDIValidator/trunk/src/main/java/eu/clarin/cmdi/validator/CMDIValidator.java

    r5052 r5056  
    1212import javax.xml.transform.dom.DOMSource;
    1313
     14import net.sf.saxon.s9api.Axis;
    1415import net.sf.saxon.s9api.DocumentBuilder;
    1516import net.sf.saxon.s9api.Processor;
     17import net.sf.saxon.s9api.QName;
    1618import net.sf.saxon.s9api.SaxonApiException;
    1719import net.sf.saxon.s9api.WhitespaceStrippingPolicy;
     20import net.sf.saxon.s9api.XPathCompiler;
     21import net.sf.saxon.s9api.XPathExecutable;
     22import net.sf.saxon.s9api.XPathSelector;
     23import net.sf.saxon.s9api.XdmDestination;
     24import net.sf.saxon.s9api.XdmItem;
    1825import net.sf.saxon.s9api.XdmNode;
     26import net.sf.saxon.s9api.XdmSequenceIterator;
     27import net.sf.saxon.s9api.XsltExecutable;
     28import net.sf.saxon.s9api.XsltTransformer;
    1929
    2030import org.apache.xerces.impl.xs.XMLSchemaLoader;
     
    6171            "http://apache.org/xml/features/honour-all-schemaLocations";
    6272    private static final int INITAL_SYMBOL_TABLE_SIZE = 16141;
     73    private static final String SVRL_NAMESPACE_URI = "http://purl.oclc.org/dsdl/svrl";
     74    private static final String SVRL_NAMESPACE_PREFIX = "svrl";
     75    private static final QName SVRL_TEXT =
     76            new QName(SVRL_NAMESPACE_URI, "text");
     77    private static final QName SVRL_FAILED_ASSERT =
     78            new QName(SVRL_NAMESPACE_URI, "failed-assert");
    6379    private final Processor processor;
     80    private final XPathExecutable xpath1;
     81    private final XPathExecutable xpath2;
     82    private final XsltTransformer schematronValidator;
    6483    private final XML11Configuration config;
    6584    private final DocumentBuilder builder;
     
    6786
    6887
    69     CMDIValidator(final Processor processor, final SchemaLoader schemaLoader)
     88    CMDIValidator(final Processor processor, final SchemaLoader schemaLoader,
     89            XsltExecutable schematronExecutable)
    7090            throws CMDIValidatorInitException {
    7191        if (processor == null) {
    72             throw new NullPointerException("validator == null");
     92            throw new NullPointerException("processor == null");
    7393        }
    7494        this.processor = processor;
     95        if (schematronExecutable != null) {
     96            try {
     97                final XPathCompiler compiler = processor.newXPathCompiler();
     98                compiler.declareNamespace(SVRL_NAMESPACE_PREFIX, SVRL_NAMESPACE_URI);
     99                this.xpath1 = compiler.compile("//(svrl:failed-assert|svrl:successful-report)");
     100                this.xpath2 = compiler.compile("preceding-sibling::svrl:fired-rule/@role");
     101                this.schematronValidator = schematronExecutable.load();
     102            } catch (SaxonApiException e) {
     103                throw new CMDIValidatorInitException(
     104                        "error initializing validator", e);
     105            }
     106        } else {
     107            this.xpath1              = null;
     108            this.xpath2              = null;
     109            this.schematronValidator = null;
     110        }
    75111
    76112        try {
     
    172208
    173209            /*
    174              * initialize Saxon
     210             * initialize Saxon document builder
    175211             */
    176212            this.builder = this.processor.newDocumentBuilder();
    177             this.builder.setWhitespaceStrippingPolicy(WhitespaceStrippingPolicy.IGNORABLE);
     213            this.builder.setWhitespaceStrippingPolicy(
     214                    WhitespaceStrippingPolicy.IGNORABLE);
    178215
    179216            /*
     
    184221            throw new CMDIValidatorInitException("initialization failed", e);
    185222        }
    186 }
     223    }
    187224
    188225
     
    205242             */
    206243            if (document != null) {
    207                 logger.debug("document = {}", document.getNodeName());
     244                if (schematronValidator != null) {
     245                    validateSchematron(document);
     246                }
    208247            } else {
    209248                logger.debug("parseInstance() returned no result");
     
    260299
    261300
    262     //    private void validateSchematron(XdmNode document)
    263 //            throws CMDIValidatorException {
    264 //        try {
    265 //            logger.debug("performing schematron validation ...");
    266 //            schematronValidator.setSource(document.asSource());
    267 //            final XdmDestination destination = new XdmDestination();
    268 //            schematronValidator.setDestination(destination);
    269 //            schematronValidator.transform();
    270 //
    271 ////            XdmNode validationReport = destination.getXdmNode();
    272 //
    273 //            // return ((net.sf.saxon.value.BooleanValue)
    274 //            // Saxon.evaluateXPath(validationReport,
    275 //            // "empty(//svrl:failed-assert[(preceding-sibling::svrl:fired-rule)[last()][empty(@role) or @role!='warning']])").evaluateSingle().getUnderlyingValue()).getBooleanValue();
    276 //        } catch (SaxonApiException e) {
    277 //            throw new CMDIValidatorException(
    278 //                    "error performing schematron validation", e);
    279 //        }
    280 //    }
     301    private void validateSchematron(XdmNode document)
     302            throws CMDIValidatorException {
     303        try {
     304            logger.debug("performing schematron validation ...");
     305            schematronValidator.setSource(document.asSource());
     306            final XdmDestination destination = new XdmDestination();
     307            schematronValidator.setDestination(destination);
     308            schematronValidator.transform();
     309
     310
     311            XPathSelector selector = xpath1.load();
     312            selector.setContextItem(destination.getXdmNode());
     313            for (XdmItem item : selector) {
     314                final XdmNode node = (XdmNode) item;
     315                final XdmNode text = getFirstChild(node, SVRL_TEXT);
     316                String msg = (text != null) ? text.getStringValue().trim() : null;
     317                if (SVRL_FAILED_ASSERT.equals(node.getNodeName())) {
     318                    XPathSelector selector2 = xpath2.load();
     319                    String role = null;
     320                    selector2.setContextItem(node);
     321                    XdmItem evaluateSingle = selector2.evaluateSingle();
     322                    if (evaluateSingle != null) {
     323                        role = evaluateSingle.getStringValue().trim();
     324                    }
     325                    if ("warning".equalsIgnoreCase(role)) {
     326                        result.reportWarning(-1, -1, msg);
     327                    } else {
     328                        result.reportError(-1, -1, msg);
     329                    }
     330                } else {
     331                    result.reportInfo(-1, -1, msg);
     332                }
     333            }
     334        } catch (SaxonApiException e) {
     335            throw new CMDIValidatorException(
     336                    "error performing schematron validation", e);
     337        }
     338    }
     339
     340
     341    private static XdmNode getFirstChild(XdmNode parent, QName name) {
     342        XdmSequenceIterator i = parent.axisIterator(Axis.CHILD, name);
     343        if (i.hasNext()) {
     344            return (XdmNode) i.next();
     345        } else {
     346            return null;
     347        }
     348    }
    281349
    282350
  • CMDIValidator/trunk/src/main/java/eu/clarin/cmdi/validator/CMDIValidatorFactory.java

    r5052 r5056  
    22
    33import java.io.File;
     4import java.net.URL;
    45
     6import javax.xml.transform.stream.StreamSource;
     7
     8import net.sf.saxon.s9api.DocumentBuilder;
    59import net.sf.saxon.s9api.Processor;
     10import net.sf.saxon.s9api.SaxonApiException;
     11import net.sf.saxon.s9api.XdmDestination;
     12import net.sf.saxon.s9api.XdmNode;
     13import net.sf.saxon.s9api.XsltCompiler;
     14import net.sf.saxon.s9api.XsltExecutable;
     15import net.sf.saxon.s9api.XsltTransformer;
    616
    717import org.apache.commons.lang3.SystemUtils;
     18import org.slf4j.Logger;
     19import org.slf4j.LoggerFactory;
     20
    821
    922public class CMDIValidatorFactory {
    10     private final Processor processor = new Processor(true);
     23    private static final String SCHEMATATRON_STAGE_1 =
     24            "/schematron/iso_dsdl_include.xsl";
     25    private static final String SCHEMATATRON_STAGE_2 =
     26            "/schematron/iso_abstract_expand.xsl";
     27    private static final String SCHEMATATRON_STAGE_3 =
     28            "/schematron/iso_svrl_for_xslt2.xsl";
     29    private static final String DEFAULT_SCHEMATRON_SCHEMA =
     30            "/default.sch";
     31    private static final Logger logger =
     32            LoggerFactory.getLogger(CMDIValidatorFactory.class);
     33    private final Processor processor;
    1134    private final SchemaLoader schemaLoader;
     35    private final XsltExecutable schematronValidator;
    1236
    1337
    14     private CMDIValidatorFactory(File cacheDirectory)
     38    private CMDIValidatorFactory(File cacheDirectory, boolean disableSchematron)
    1539            throws CMDIValidatorInitException {
     40        /*
     41         * initialize custom schema loader
     42         */
     43        logger.debug("initializing schema loader ...");
    1644        if (cacheDirectory == null) {
    1745            if (SystemUtils.IS_OS_WINDOWS &&
     
    5381        schemaLoader = new SchemaLoader(cacheDirectory,
    5482                SchemaLoader.DISABLE_CACHE_AGING);
     83
     84        /*
     85         * initialize Saxon processor
     86         */
     87        logger.debug("initializing Saxon ...");
     88        this.processor = new Processor(true);
     89
     90        /*
     91         * initialize Schematron validator
     92         */
     93        if (!disableSchematron) {
     94            logger.debug("initializing Schematron validator ...");
     95            URL schema = this.getClass().getResource(DEFAULT_SCHEMATRON_SCHEMA);
     96            if (schema == null) {
     97                throw new CMDIValidatorInitException(
     98                        "cannot locate schematron schema");
     99            }
     100            final XsltCompiler compiler = processor.newXsltCompiler();
     101            XsltTransformer stage1 =
     102                    loadStylesheet(compiler, SCHEMATATRON_STAGE_1);
     103            XsltTransformer stage2 =
     104                    loadStylesheet(compiler, SCHEMATATRON_STAGE_2);
     105            XsltTransformer stage3 =
     106                    loadStylesheet(compiler, SCHEMATATRON_STAGE_3);
     107            try {
     108                XdmDestination destination = new XdmDestination();
     109                stage1.setSource(new StreamSource(schema.toExternalForm()));
     110                stage1.setDestination(stage2);
     111                stage2.setDestination(stage3);
     112                stage3.setDestination(destination);
     113                stage1.transform();
     114                schematronValidator =
     115                        compiler.compile(destination.getXdmNode().asSource());
     116                logger.debug("Schematron validator successfully initializied");
     117            } catch (SaxonApiException e) {
     118                throw new CMDIValidatorInitException(
     119                        "error compiling schematron rules", e);
     120            }
     121        } else {
     122            logger.debug("disabling Schematron validator");
     123            this.schematronValidator = null;
     124        }
    55125    }
    56126
    57127
    58128    public CMDIValidator newValidator() throws CMDIValidatorInitException {
    59         return new CMDIValidator(processor, schemaLoader);
     129        return new CMDIValidator(processor, schemaLoader, schematronValidator);
    60130    }
    61131
    62132
    63     public static CMDIValidatorFactory newInstance(File cacheDircetory)
    64             throws CMDIValidatorInitException {
    65         return new CMDIValidatorFactory(cacheDircetory);
     133    public static CMDIValidatorFactory newInstance(File cacheDircetory,
     134            boolean disableSchematron) throws CMDIValidatorInitException {
     135        return new CMDIValidatorFactory(cacheDircetory, disableSchematron);
    66136    }
    67137
     
    69139    public static CMDIValidatorFactory newInstance()
    70140            throws CMDIValidatorInitException {
    71         return new CMDIValidatorFactory(null);
     141        return new CMDIValidatorFactory(null, false);
     142    }
     143
     144
     145    private XsltTransformer loadStylesheet(XsltCompiler compiler, String name)
     146            throws CMDIValidatorInitException {
     147        try {
     148            logger.debug("loading stylesheet '{}'", name);
     149            final URL uri = this.getClass().getResource(name);
     150            if (uri != null) {
     151                DocumentBuilder builder = processor.newDocumentBuilder();
     152                XdmNode source =
     153                        builder.build(new StreamSource(uri.toExternalForm()));
     154                XsltExecutable stylesheet = compiler.compile(source.asSource());
     155                return stylesheet.load();
     156            } else {
     157                throw new CMDIValidatorInitException("cannot find resource '" +
     158                        name + "'");
     159            }
     160        } catch (SaxonApiException e) {
     161            throw new CMDIValidatorInitException(
     162                    "error loading schematron stylesheet '" + name + "'", e);
     163        }
    72164    }
    73165
  • CMDIValidator/trunk/src/main/java/eu/clarin/cmdi/validator/tool/CMDIValidatorTool.java

    r5054 r5056  
    4646    private static final char OPT_NO_ESTIMATE           = 'E';
    4747    private static final char OPT_SCHEMA_CACHE_DIR      = 'C';
     48    private static final char OPT_NO_SCHEMATRON         = 'S';
     49    private static final char OPT_SCHEMATRON_FILE       = 's';
    4850    private static final Logger logger =
    4951            LoggerFactory.getLogger(CMDIValidatorTool.class);
     
    5557         * application defaults
    5658         */
    57         boolean debugging     = false;
    58         boolean quiet         = false;
    59         boolean verbose       = false;
    60         int threadCount       = Runtime.getRuntime().availableProcessors();
    61         boolean estimate      = true;
    62         long progressInterval = DEFAULT_PROGRESS_INTERVAL;
    63         File schemaCacheDir   = null;
     59        boolean debugging         = false;
     60        boolean quiet             = false;
     61        boolean verbose           = false;
     62        int threadCount           = Runtime.getRuntime().availableProcessors();
     63        boolean estimate          = true;
     64        long progressInterval     = DEFAULT_PROGRESS_INTERVAL;
     65        File schemaCacheDir       = null;
     66        boolean disableSchematron = false;
    6467
    6568        /*
     
    7275            // check incompatible combinations
    7376            if (line.hasOption(OPT_THREAD_COUNT) && line.hasOption(OPT_NO_THREADS)) {
    74                 throw new ParseException("The -t and -T option are mutually exclusive");
     77                throw new ParseException("The -t and -T options are mutually exclusive");
    7578            }
    7679            if (line.hasOption(OPT_DEBUG) && line.hasOption(OPT_QUIET)) {
    77                 throw new ParseException("The -d and -q switch are mutually exclusive");
     80                throw new ParseException("The -d and -q switches are mutually exclusive");
    7881            }
    7982            if (line.hasOption(OPT_VERBOSE) && line.hasOption(OPT_QUIET)) {
    80                 throw new ParseException("The -v and -q switch are mutually exclusive");
     83                throw new ParseException("The -v and -q switches are mutually exclusive");
     84            }
     85            if (line.hasOption(OPT_NO_SCHEMATRON) && line.hasOption(OPT_SCHEMATRON_FILE)) {
     86                throw new ParseException("The -s and -T options are mutually exclusive");
    8187            }
    8288            // extract options
     
    117123                }
    118124                schemaCacheDir = new File(dir);
     125            }
     126            if (line.hasOption(OPT_NO_SCHEMATRON)) {
     127                disableSchematron = true;
    119128            }
    120129
     
    149158                }
    150159                final CMDIValidatorFactory factory =
    151                         CMDIValidatorFactory.newInstance(schemaCacheDir);
     160                        CMDIValidatorFactory.newInstance(schemaCacheDir,
     161                                disableSchematron);
    152162
    153163                /*
     
    325335                .withLongOpt("schema-cache-dir")
    326336                .create(OPT_SCHEMA_CACHE_DIR));
     337        OptionGroup g3 = new OptionGroup();
     338        g3.addOption(OptionBuilder
     339                .withDescription("disable Schematron validator")
     340                .withLongOpt("no-schematron")
     341                .create(OPT_NO_SCHEMATRON));
     342//        g3.addOption(OptionBuilder
     343//                .withDescription("load Schematron validator rules from file")
     344//                .hasArg()
     345//                .withArgName("FILE")
     346//                .withLongOpt("schematron-file")
     347//                .create(OPT_SCHEMATRON_FILE));
     348        options.addOptionGroup(g3);
    327349        return options;
    328350    }
     
    442464                    logger.warn("file '{}' is valid (with warnings):", file);
    443465                    for (Message msg : result.getMessages()) {
    444                         logger.warn(" ({}) {} [line={}, column={}]",
    445                                 msg.getSeverity().getShortcut(),
    446                                 msg.getMessage(),
    447                                 msg.getLineNumber(),
    448                                 msg.getColumnNumber());
     466                        if ((msg.getLineNumber() != -1) &&
     467                                (msg.getColumnNumber() != -1)) {
     468                            logger.warn(" ({}) {} [line={}, column={}]",
     469                                    msg.getSeverity().getShortcut(),
     470                                    msg.getMessage(),
     471                                    msg.getLineNumber(),
     472                                    msg.getColumnNumber());
     473                        } else {
     474                            logger.warn(" ({}) {}",
     475                                    msg.getSeverity().getShortcut(),
     476                                    msg.getMessage());
     477                        }
    449478                    }
    450479                } else {
    451480                    Message msg = result.getFirstMessage(Severity.WARNING);
    452                     int count = result.getMessageCount(Severity.WARNING);
     481                    int count   = result.getMessageCount(Severity.WARNING);
    453482                    if (count > 1) {
    454483                        logger.warn("file '{}' is valid (with warnings): {} ({} more warnings)",
    455                                 file, msg, (count - 1));
     484                                file, msg.getMessage(), (count - 1));
    456485                    } else {
    457486                        logger.warn("file '{}' is valid (with warnings): {}",
    458                                 file, msg);
     487                                file, msg.getMessage());
    459488                    }
    460489                }
     
    479508            logger.error("file '{}' is invalid:", file);
    480509                for (Message msg : result.getMessages()) {
    481                     logger.error(" ({}) {} [line={}, column={}]",
    482                             msg.getSeverity().getShortcut(),
    483                             msg.getMessage(),
    484                             msg.getLineNumber(),
    485                             msg.getColumnNumber());
     510                    if ((msg.getLineNumber() != -1) &&
     511                            (msg.getColumnNumber() != -1)) {
     512                        logger.error(" ({}) {} [line={}, column={}]",
     513                                msg.getSeverity().getShortcut(),
     514                                msg.getMessage(),
     515                                msg.getLineNumber(),
     516                                msg.getColumnNumber());
     517                    } else {
     518                        logger.error(" ({}) {}",
     519                                msg.getSeverity().getShortcut(),
     520                                msg.getMessage());
     521                    }
    486522                }
    487523            } else {
    488524                Message msg = result.getFirstMessage(Severity.ERROR);
    489                 int count = result.getMessageCount(Severity.ERROR);
     525                int count   = result.getMessageCount(Severity.ERROR);
    490526                if (count > 1) {
    491527                    logger.error("file '{}' is invalid: {} ({} more errors)",
Note: See TracChangeset for help on using the changeset viewer.