blob: 13a8343f2e23083a75f5535f710126c142c12ac8 [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.components.validation.schematron;
import org.apache.avalon.framework.CascadingRuntimeException;
import org.apache.cocoon.components.validation.Schema;
import org.apache.cocoon.components.validation.SchemaFactory;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.log.Hierarchy;
import org.apache.log.Logger;
import org.apache.log.Priority;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
/**
* A helper class which builds a SchematronSchema instance object
* from a DOM source.
*
* @author Ivelin Ivanov, ivelin@acm.org, ivelin@iname.com
* @author Michael Ratliff, mratliff@collegenet.com <mratliff@collegenet.com>, May 2002
* @version CVS $Id: SchematronFactory.java,v 1.4 2004/03/05 13:02:37 bdelacretaz Exp $
*/
public class SchematronFactory extends SchemaFactory {
/**
* The schema name space prefix used in the schema document.
*/
private String schemaPrefix_;
/**
* The default schema name space prefix.
*/
private String defaultSchemaPrefix_ = "sch";
/**
* Private logger.
*/
private Logger logger = setupLogger();
//
// Constructors
//
/**
* Initialize logger.
*/
protected Logger setupLogger() {
Logger logger = Hierarchy.getDefaultHierarchy().getLoggerFor("XmlForm");
logger.setPriority(Priority.ERROR);
return logger;
}
/**
* Builds a new Schema instance from
* the given XML InputSource.
*
* @param schemaSrc
* the Schema document XML InputSource
*/
public Schema compileSchema(InputSource schemaSrc)
throws InstantiationException {
SchematronSchema schema = null;
try {
// load Schema file into a DOM document
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder dbld = dbf.newDocumentBuilder();
Document document = dbld.parse(schemaSrc);
schema = buildSchema(document);
} catch (Exception e) {
logger.error("!!! Failed loading Schematron schema", e);
throw new CascadingRuntimeException(" !!! Failed loading Schematron schema",
e);
}
return schema;
} // build
/**
* Build Schematron schema object from a DOM document.
*
* @param doc DOM document containing the schema
*/
protected SchematronSchema buildSchema(Document doc) {
SchematronSchema schema = new SchematronSchema();
doc.getNamespaceURI();
doc.getPrefix();
// Initialize the JXPath context
Element root = doc.createElement("root");
Element schemaElement = doc.getDocumentElement();
schemaPrefix_ = schemaElement.getPrefix();
root.appendChild(schemaElement);
JXPathContext jxpContext = JXPathContext.newContext(root);
jxpContext.setLenient(false);
// Bind sch:schema element
// schema title
String title = (String) jxpContext.getValue("/schema/title",
String.class);
schema.setTitle(title);
logger.debug("Schema title: "+schema.getTitle());
bindPatterns(schema, jxpContext);
bindPhases(schema, jxpContext);
return schema;
}
/**
* Populates the patterns elements from the dom tree.
*
* @param schema the schema instance
* @param jxpContext
*/
protected void bindPatterns(SchematronSchema schema,
JXPathContext jxpContext) {
// ensure that mandatory elements which are not found
// will result in Exception
jxpContext.setLenient(false);
// schema patterns
int ptCount = ((Integer) jxpContext.getValue("count(/schema/pattern)",
Integer.class)).intValue();
logger.debug("\nNumber of patterns: "+ptCount);
for (int i = 1; i<=ptCount; i++) {
logger.debug("Pattern# : "+i);
Pattern pattern = new Pattern();
String ptprefix = "/schema/pattern["+i+"]";
String name = (String) jxpContext.getValue(ptprefix+"/@name",
String.class);
pattern.setName(name);
logger.debug("Pattern name : "+pattern.getName());
String id = (String) jxpContext.getValue(ptprefix+"/@id",
String.class);
pattern.setId(id);
logger.debug("Pattern id : "+pattern.getId());
bindRules(pattern, ptprefix, jxpContext);
schema.addPattern(pattern);
}
}
/**
* Populates the rules elements for a pattern
* from the dom tree.
*
* @param pattern
* @param pathPrefix pattern path prefix
* @param jxpContext JXPathContext
*/
protected void bindRules(Pattern pattern, String pathPrefix,
JXPathContext jxpContext) {
// ensure that mandatory elements which are not found
// will result in Exception
jxpContext.setLenient(false);
// schema rules
int ruleCount = ((Integer) jxpContext.getValue("count("+pathPrefix+
"/rule)", Integer.class)).intValue();
logger.debug("\nNumber of rules: "+ruleCount);
for (int i = 1; i<=ruleCount; i++) {
logger.debug("Rule# : "+i);
Rule rule = new Rule();
String rulePrefix = pathPrefix+"/rule["+i+"]";
String context = (String) jxpContext.getValue(rulePrefix+
"/@context", String.class);
rule.setContext(context);
logger.debug("Rule context : "+rule.getContext());
bindAsserts(rule, rulePrefix, jxpContext);
// Patch to make reports work in schematron
// Note change to name of bindRerports [sic] function
bindReports(rule, rulePrefix, jxpContext);
pattern.addRule(rule);
}
}
/**
* Populates the assert elements for a rule
* from the dom tree.
*
* @param rule
* @param pathPrefix rule path prefix
* @param jxpContext JXPathContext
*/
protected void bindAsserts(Rule rule, String pathPrefix,
JXPathContext jxpContext) {
// ensure that mandatory elements which are not found
// will result in Exception
jxpContext.setLenient(false);
// schema reports
int elementCount = ((Integer) jxpContext.getValue("count("+pathPrefix+
"/assert)", Integer.class)).intValue();
logger.debug("\nNumber of asserts: "+elementCount);
for (int i = 1; i<=elementCount; i++) {
logger.debug("Assert# : "+i);
Assert assertion = new Assert();
String assertPrefix = pathPrefix+"/assert["+i+"]";
String test = (String) jxpContext.getValue(assertPrefix+"/@test",
String.class);
assertion.setTest(test);
logger.debug("Assert test : "+assertion.getTest());
// since diagnostics is a non-mandatory element
// we will try to get its value in a lenient mode
jxpContext.setLenient(true);
String diagnostics = (String) jxpContext.getValue(assertPrefix+
"/@diagnostics", String.class);
assertion.setDiagnostics(diagnostics);
logger.debug("Assert diagnostics : "+assertion.getDiagnostics());
jxpContext.setLenient(false);
// now read the report message
// TODO: The current implementation does not
// read xml tags used within the assert message.
// Solution is to use JXPath NodePointer to get
// to the DOM node and then convert it to a String.
// e.g.
// NodePointer nptr = (NodePointer) jxpContext.locateValue( assertPrefix );
// Node msgNode = (Node) nptr.getNodeValue();
// convery DOMNode to String
String message = (String) jxpContext.getValue(assertPrefix,
String.class);
assertion.setMessage(message);
logger.debug("Assert message : "+assertion.getMessage());
rule.addAssert(assertion);
}
}
/**
* Populates the report elements for a rule
* from the dom tree.
*
* @param rule
* @param pathPrefix rule path prefix
* @param jxpContext JXPathContext
*/
protected void bindReports(Rule rule, String pathPrefix,
JXPathContext jxpContext) {
// ensure that mandatory elements which are not found
// will result in Exception
jxpContext.setLenient(false);
// schema reports
int elementCount = ((Integer) jxpContext.getValue("count("+pathPrefix+
"/report)", Integer.class)).intValue();
logger.debug("\nNumber of reports: "+elementCount);
for (int i = 1; i<=elementCount; i++) {
logger.debug("Report# : "+i);
Report report = new Report();
String assertPrefix = pathPrefix+"/report["+i+"]";
String test = (String) jxpContext.getValue(assertPrefix+"/@test",
String.class);
report.setTest(test);
logger.debug("Report test : "+report.getTest());
// since diagnostics is a non-mandatory element
// we will try to get its value in a lenient mode
jxpContext.setLenient(true);
String diagnostics = (String) jxpContext.getValue(assertPrefix+
"/@diagnostics", String.class);
report.setDiagnostics(diagnostics);
logger.debug("Report diagnostics : "+report.getDiagnostics());
jxpContext.setLenient(false);
String message = (String) jxpContext.getValue(assertPrefix,
String.class);
report.setMessage(message);
logger.debug("Report message : "+report.getMessage());
rule.addReport(report);
}
}
/**
* Populates the phases elements from the dom tree.
*
* @param schema the schema instance
* @param jxpContext
*/
protected void bindPhases(SchematronSchema schema,
JXPathContext jxpContext) {
// ensure that mandatory elements which are not found
// will result in Exception
jxpContext.setLenient(false);
// schema phases
int phaseCount = ((Integer) jxpContext.getValue("count(/schema/phase)",
Integer.class)).intValue();
logger.debug("\nNumber of phases: "+phaseCount);
for (int i = 1; i<=phaseCount; i++) {
logger.debug("phase# : "+i);
Phase phase = new Phase();
String phprefix = "/schema/phase["+i+"]";
String id = (String) jxpContext.getValue(phprefix+"/@id",
String.class);
phase.setId(id);
logger.debug("phase id : "+phase.getId());
bindPhaseActivePatterns(phase, phprefix, jxpContext);
schema.addPhase(phase);
}
}
protected void bindPhaseActivePatterns(Phase phase, String pathPrefix,
JXPathContext jxpContext) {
// ensure that mandatory elements which are not found
// will result in Exception
jxpContext.setLenient(false);
// phase active patterns
int elementCount = ((Integer) jxpContext.getValue("count("+pathPrefix+
"/active)", Integer.class)).intValue();
logger.debug("Number of active patterns: "+elementCount);
for (int i = 1; i<=elementCount; i++) {
logger.debug("active pattern # : "+i);
ActivePattern activePattern = new ActivePattern();
String assertPrefix = pathPrefix+"/active["+i+"]";
String pt = (String) jxpContext.getValue(assertPrefix+
"/@pattern", String.class);
activePattern.setPattern(pt);
logger.debug("Phase active pattern : "+
activePattern.getPattern());
phase.addActive(activePattern);
}
}
/**
* Replace all occurances of sch: with the actual Schema prefix used in the document.
*
* TODO: fix this implementaion. There are problems with DOM.
* Returns null instead of the actual namespace prefix (e.g. "sch") as expected.
*
* @param path
*
*/
protected String fixns(String path) {
// Ironicly, at the time I am writing this
// JDK 1.4 is offering String.replaceAll(regex, str)
// I don't use it however for backward compatibility
StringBuffer strbuf = new StringBuffer(path);
int i = 0;
int j = 0;
String dprefix = defaultSchemaPrefix_+":";
int dplen = dprefix.length();
while ((j = path.indexOf(dprefix, i))>=0) {
strbuf.append(path.substring(i, j));
strbuf.append(schemaPrefix_);
strbuf.append(':');
i = j+dplen;
}
strbuf.append(path.substring(i));
return strbuf.toString();
}
}