package com.thaiopensource.validate.mns; import com.thaiopensource.util.Localizer; import com.thaiopensource.util.PropertyMap; import com.thaiopensource.util.Uri; import com.thaiopensource.util.PropertyMapBuilder; import com.thaiopensource.validate.IncorrectSchemaException; import com.thaiopensource.validate.Schema; import com.thaiopensource.validate.ValidateProperty; import com.thaiopensource.validate.Validator; import com.thaiopensource.validate.AbstractSchema; import com.thaiopensource.validate.auto.SchemaFuture; import com.thaiopensource.xml.sax.XmlBaseHandler; import com.thaiopensource.xml.sax.DelegatingContentHandler; import com.thaiopensource.xml.sax.CountingErrorHandler; import com.thaiopensource.xml.util.Name; import com.thaiopensource.xml.util.StringSplitter; import com.thaiopensource.xml.util.WellKnownNamespaces; import org.xml.sax.Attributes; 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 org.xml.sax.helpers.LocatorImpl; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Stack; class SchemaImpl extends AbstractSchema { static final String MNS_URI = "http://www.thaiopensource.com/ns/mns"; private final Hashtable modeMap = new Hashtable(); private Mode startMode; private static final String DEFAULT_MODE_NAME = "#default"; private final boolean attributesSchema; static private final class WrappedIOException extends RuntimeException { private final IOException exception; private WrappedIOException(IOException exception) { this.exception = exception; } private IOException getException() { return exception; } } static class ElementAction { private final Schema schema; private final Mode mode; private final ContextMap contextMap; private final ElementsOrAttributes prune; private final Hashset covered = new Hashset(); ElementAction(String ns, Schema schema, Mode mode, ContextMap contextMap, ElementsOrAttributes prune) { this.schema = schema; this.mode = mode; this.contextMap = contextMap; this.prune = prune; covered.add(ns); } Mode getMode() { return mode; } ContextMap getContextMap() { return contextMap; } Schema getSchema() { return schema; } ElementsOrAttributes getPrune() { return prune; } Hashset getCoveredNamespaces() { return covered; } } static class Mode { private Locator whereDefined; private boolean defined = false; private ElementsOrAttributes lax; private boolean strictDefined = false; private final Hashtable elementMap = new Hashtable(); private final Hashtable attributesMap = new Hashtable(); Mode(ElementsOrAttributes lax) { this.lax = lax; } ElementsOrAttributes getLax() { return lax; } Schema getAttributesSchema(String ns) { return (Schema)attributesMap.get(ns); } ElementAction getElementAction(String ns) { return (ElementAction)elementMap.get(ns); } } private class Handler extends DelegatingContentHandler implements SchemaFuture { private final SchemaReceiverImpl sr; private ElementAction currentElementAction; private boolean hadError = false; private final ErrorHandler eh; private final CountingErrorHandler ceh; private final Localizer localizer = new Localizer(SchemaImpl.class); private Locator locator; private final XmlBaseHandler xmlBaseHandler = new XmlBaseHandler(); private int foreignDepth = 0; private String contextNs; private Mode contextMode; private String elementNs; private String defaultSchemaType; private final Stack nameStack = new Stack(); private boolean isRoot; private int pathDepth = 0; private Validator validator; Handler(SchemaReceiverImpl sr) { this.sr = sr; this.eh = sr.getProperties().get(ValidateProperty.ERROR_HANDLER); this.ceh = new CountingErrorHandler(eh); } public void setDocumentLocator(Locator locator) { xmlBaseHandler.setLocator(locator); this.locator = locator; } public void startDocument() throws SAXException { try { PropertyMapBuilder builder = new PropertyMapBuilder(sr.getProperties()); builder.put(ValidateProperty.ERROR_HANDLER, ceh); validator = sr.getMnsSchema().createValidator(builder.toPropertyMap()); } catch (IOException e) { throw new WrappedIOException(e); } catch (IncorrectSchemaException e) { throw new RuntimeException("internal error in RNG schema for MNS"); } setDelegate(validator.getContentHandler()); if (locator != null) super.setDocumentLocator(locator); super.startDocument(); } public Schema getSchema() throws IncorrectSchemaException, SAXException { if (validator == null || ceh.getHadErrorOrFatalError()) throw new IncorrectSchemaException(); for (Enumeration e = modeMap.keys(); e.hasMoreElements();) { String modeName = (String)e.nextElement(); Mode mode = (Mode)modeMap.get(modeName); if (!mode.defined && !modeName.equals(DEFAULT_MODE_NAME)) error("undefined_mode", modeName, mode.whereDefined); } if (hadError) throw new IncorrectSchemaException(); return SchemaImpl.this; } public RuntimeException unwrapException(RuntimeException e) throws SAXException, IOException, IncorrectSchemaException { if (e instanceof WrappedIOException) throw ((WrappedIOException)e).getException(); return e; } public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); xmlBaseHandler.startElement(); String xmlBase = attributes.getValue(WellKnownNamespaces.XML, "base"); if (xmlBase != null) xmlBaseHandler.xmlBaseAttribute(xmlBase); if (!MNS_URI.equals(uri) || foreignDepth > 0) { foreignDepth++; return; } if (ceh.getHadErrorOrFatalError()) return; if (localName.equals("rules")) parseRules(attributes); else if (localName.equals("cover")) parseCover(attributes); else if (localName.equals("context")) parseContext(attributes); else if (localName.equals("root")) parseRoot(attributes); else if (localName.equals("element")) parseElement(attributes); else if (localName.equals("lax")) parseLax(attributes); else parseValidate(localName.equals("validateAttributes"), attributes); } public void endElement(String namespaceURI, String localName, String qName) throws SAXException { super.endElement(namespaceURI, localName, qName); xmlBaseHandler.endElement(); if (foreignDepth > 0) { foreignDepth--; return; } if (pathDepth > 0) { pathDepth--; if (pathDepth == 0) endPath(); } } private void parseRules(Attributes attributes) { String modeName = attributes.getValue("", "startMode"); if (modeName == null) modeName = DEFAULT_MODE_NAME; defaultSchemaType = getSchemaType(attributes); startMode = lookupCreateMode(modeName); } private void parseCover(Attributes attributes) throws SAXException { String ns = getNs(attributes, false); currentElementAction.covered.add(ns); } private void parseLax(Attributes attributes) throws SAXException { String[] modeNames = getInModes(attributes); Mode[] modes = getModes(modeNames); ElementsOrAttributes lax = toElementsOrAttributes(attributes.getValue("", "allow"), ElementsOrAttributes.BOTH); for (int i = 0; i < modes.length; i++) { if (modes[i].strictDefined) error("lax_multiply_defined", modeNames[i]); else { modes[i].lax = lax; modes[i].strictDefined = true; } } } private void parseValidate(boolean isAttribute, Attributes attributes) throws SAXException { String[] modeNames = getInModes(attributes); Mode[] modes = getModes(modeNames); String ns = getNs(attributes, isAttribute); String schemaUri = getSchema(attributes); String schemaType = getSchemaType(attributes); if (schemaType == null) schemaType = defaultSchemaType; try { if (isAttribute) { Schema schema = sr.createChildSchema(new InputSource(schemaUri), schemaType, true); for (int i = 0; i < modes.length; i++) { if (modes[i].attributesMap.get(ns) != null) error("validate_attributes_multiply_defined", modeNames[i], ns); else modes[i].attributesMap.put(ns, schema); } } else { Schema schema = sr.createChildSchema(new InputSource(schemaUri), schemaType, false); currentElementAction = new ElementAction(ns, schema, getUseMode(attributes), new ContextMap(), getPrune(attributes)); contextNs = ns; for (int i = 0; i < modes.length; i++) { if (modes[i].elementMap.get(ns) != null) error("validate_element_multiply_defined", modeNames[i], ns); else modes[i].elementMap.put(ns, currentElementAction); } } } catch (IncorrectSchemaException e) { hadError = true; } catch (IOException e) { throw new WrappedIOException(e); } } private void parseContext(Attributes attributes) throws SAXException { String ns = getNs(attributes, false); if (ns != null) contextNs = ns; elementNs = contextNs; contextMode = getUseMode(attributes); } private void parseRoot(Attributes attributes) throws SAXException { String ns = getNs(attributes, false); if (ns != null) elementNs = ns; isRoot = true; pathDepth++; } private void parseElement(Attributes attributes) throws SAXException { String ns = getNs(attributes, false); if (ns != null) elementNs = ns; if (!currentElementAction.covered.contains(elementNs)) error("context_ns_not_covered", elementNs); nameStack.push(new Name(elementNs, attributes.getValue("", "name").trim())); pathDepth++; } private void endPath() throws SAXException { if (!currentElementAction.contextMap.put(isRoot, nameStack, contextMode)) error("path_multiply_defined", displayPath(isRoot, nameStack)); elementNs = contextNs; isRoot = false; nameStack.setSize(0); } private String displayPath(boolean isRoot, Stack nameStack) { StringBuffer buf = new StringBuffer(); for (int i = 0, len = nameStack.size(); i < len; i++) { if (i > 0 || isRoot) buf.append('/'); Name name = (Name)nameStack.elementAt(i); if (name.getNamespaceUri().length() > 0) { buf.append('{'); buf.append(name.getNamespaceUri()); buf.append('}'); } buf.append(name.getLocalName()); } return buf.toString(); } private String getSchema(Attributes attributes) throws SAXException { String schemaUri = attributes.getValue("", "schema"); if (Uri.hasFragmentId(schemaUri)) error("schema_fragment_id"); return Uri.resolve(xmlBaseHandler.getBaseUri(), Uri.escapeDisallowedChars(schemaUri)); } private String getSchemaType(Attributes attributes) { return attributes.getValue("", "schemaType"); } private ElementsOrAttributes getPrune(Attributes attributes) { return toElementsOrAttributes(attributes.getValue("", "prune"), ElementsOrAttributes.NEITHER); } private ElementsOrAttributes toElementsOrAttributes(String value, ElementsOrAttributes defaultValue) { if (value == null) return defaultValue; ElementsOrAttributes eoa = ElementsOrAttributes.NEITHER; if (value.indexOf("elements") >= 0) eoa = eoa.addElements(); if (value.indexOf("attributes") >= 0) eoa = eoa.addAttributes(); return eoa; } private Mode getUseMode(Attributes attributes) { String modeName = attributes.getValue("", "useMode"); if (modeName == null) modeName = DEFAULT_MODE_NAME; Mode mode = lookupCreateMode(modeName); if (mode.whereDefined == null && locator != null) mode.whereDefined = new LocatorImpl(locator); return mode; } private String getNs(Attributes attributes, boolean forbidEmpty) throws SAXException { String ns = attributes.getValue("", "ns"); if (ns != null && !Uri.isAbsolute(ns) && (forbidEmpty || !ns.equals(""))) error("ns_absolute"); return ns; } private Mode[] getModes(String[] modeNames) { Mode[] modes = new Mode[modeNames.length]; for (int i = 0; i < modes.length; i++) { modes[i] = lookupCreateMode(modeNames[i]); modes[i].defined = true; } return modes; } private String[] getInModes(Attributes attributes) { String inModes = attributes.getValue("", "inModes"); if (inModes == null) return new String[] { DEFAULT_MODE_NAME }; return StringSplitter.split(inModes); } void error(String key) throws SAXException { hadError = true; if (eh == null) return; eh.error(new SAXParseException(localizer.message(key), locator)); } void error(String key, String arg) throws SAXException { hadError = true; if (eh == null) return; eh.error(new SAXParseException(localizer.message(key, arg), locator)); } void error(String key, String arg, Locator locator) throws SAXException { hadError = true; if (eh == null) return; eh.error(new SAXParseException(localizer.message(key, arg), locator)); } void error(String key, String arg1, String arg2) throws SAXException { hadError = true; if (eh == null) return; eh.error(new SAXParseException(localizer.message(key, arg1, arg2), locator)); } } SchemaImpl(boolean attributesSchema) { this.attributesSchema = attributesSchema; } SchemaFuture installHandlers(XMLReader in, SchemaReceiverImpl sr) { Handler h = new Handler(sr); in.setContentHandler(h); return h; } public Validator createValidator(PropertyMap properties) { return new ValidatorImpl(startMode, properties); } private Mode lookupCreateMode(String name) { Mode mode = (Mode)modeMap.get(name); if (mode == null) { mode = new Mode(attributesSchema ? ElementsOrAttributes.ELEMENTS : ElementsOrAttributes.NEITHER); modeMap.put(name, mode); } return mode; } }