package com.thaiopensource.validate.nvdl; import com.thaiopensource.resolver.xml.sax.SAXResolver; import com.thaiopensource.util.Localizer; import com.thaiopensource.util.PropertyId; import com.thaiopensource.util.PropertyMap; import com.thaiopensource.util.PropertyMapBuilder; import com.thaiopensource.util.Uri; import com.thaiopensource.validate.AbstractSchema; import com.thaiopensource.validate.IncorrectSchemaException; import com.thaiopensource.validate.Option; import com.thaiopensource.validate.OptionArgumentException; import com.thaiopensource.validate.OptionArgumentPresenceException; import com.thaiopensource.validate.ResolverFactory; import com.thaiopensource.validate.Schema; import com.thaiopensource.validate.ValidateProperty; import com.thaiopensource.validate.Validator; import com.thaiopensource.validate.auto.SchemaFuture; import com.thaiopensource.validate.prop.wrap.WrapProperty; import com.thaiopensource.xml.sax.CountingErrorHandler; import com.thaiopensource.xml.sax.DelegatingContentHandler; import com.thaiopensource.xml.sax.XmlBaseHandler; import com.thaiopensource.xml.util.WellKnownNamespaces; import org.xml.sax.Attributes; import org.xml.sax.ErrorHandler; 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.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; import java.util.Vector; /** * Schema implementation for NVDL scripts. */ class SchemaImpl extends AbstractSchema { /** * Mode name used when the script does not define modes and just enters * namespace and anyNamespace mappings directly inside rules. */ static private final String IMPLICIT_MODE_NAME = "#implicit"; /** * Mode name used when we have to use this script as an attributes schema. * The wrapper mode allows elements from any namespace. */ static private final String WRAPPER_MODE_NAME = "#wrapper"; /** * The NVDL URI. */ static final String NVDL_URI = "http://purl.oclc.org/dsdl/nvdl/ns/structure/1.0"; /** * A hash with the modes. */ private final Hashtable modeMap = new Hashtable(); /** * A hash with the triggers on namespace. * Element names are stored concatenated in a string, each name preceded by #. */ private final List triggers = new ArrayList(); /** * The start mode. */ private Mode startMode; /** * Default base mode, rejects everything. */ private final Mode defaultBaseMode; /** * Flag indicating if the schema needs to be changed to handle * attributes only, the element in this case is a placeholder. */ private final boolean attributesSchema; /** * Wrapps an IOException as a RuntimeException. * */ static private final class WrappedIOException extends RuntimeException { /** * The actual IO Exception. */ private final IOException exception; /** * Creates a wrapped exception. * @param exception The IOException. */ private WrappedIOException(IOException exception) { this.exception = exception; } /** * Get the actual IO Exception. * @return IOException. */ private IOException getException() { return exception; } } /** * Stores information about options that must be supported by the * validator. */ static private class MustSupportOption { /** * The option name. */ private final String name; /** * The property id. */ private final PropertyId pid; /** * Locator pointing to where this option is declared. */ private final Locator locator; /** * Creates a must support option. * @param name The option name * @param pid property id. * @param locator locator pointing to where this option is declared. */ MustSupportOption(String name, PropertyId pid, Locator locator) { this.name = name; this.pid = pid; this.locator = locator; } } /** * This class is registered as content handler on the XMLReader that * parses the NVDL script. * It creates the Schema representation for this script and also validates * the script against the NVDL schema. */ private class Handler extends DelegatingContentHandler implements SchemaFuture { /** * The schema receiver. Used to cretae other schemas and access options. */ private final SchemaReceiverImpl sr; /** * Flag indicating that we encountered an error. */ private boolean hadError = false; /** * The error handler. */ private final ErrorHandler eh; /** * A counting error handler that wraps the error handler. * It is useful to stop early if we encounter errors. */ private final CountingErrorHandler ceh; /** * The Resolver to use for resolving URIs and entities. */ private final SAXResolver resolver; /** * Convert error keys to messages. */ private final Localizer localizer = new Localizer(SchemaImpl.class); /** * Error locator. */ private Locator locator; /** * Handle xml:base attributes. */ private final XmlBaseHandler xmlBaseHandler = new XmlBaseHandler(); /** * For ignoring foreign elements. */ private int foreignDepth = 0; /** * The value of rules/@schemaType */ private String defaultSchemaType; /** * The validator that checks the script against the * NVDL RelaxNG schema. */ private Validator validator; /** * Stores mode data. * We use this to handle included and nested modes. */ class ModeData { /** * Points to the current mode. */ private Mode currentMode = null; /** * The value of the match attribute on the current rule. */ private ElementsOrAttributes match; /** * The current element actions. */ private ActionSet actions; /** * The current attribute actions. */ private AttributeActionSet attributeActions; /** * The URI reference for the schema for the current validate action. * This is as specified in the attribute before any resolution. */ private String schemaUriRef; /** * The base URI to be used for resolving schemaUriRef. */ private String schemaUriBase; /** * The current validate action schema type. */ private String schemaType; /** * The options defined for a validate action. */ private PropertyMapBuilder options; /** * The options that must be supported by the validator * for the current validate action. */ private final Vector mustSupportOptions = new Vector(); /** * The current mode usage, for the current action. */ private ModeUsage modeUsage; /** * Flag indicating if we are in a namespace rule or in an anyNamespace rule. */ private boolean anyNamespace; /** * The lastMode stores the last created mode. * For example when we have an action we need to create the * ModeUsage for it and lastMode points to the mode for that action. * It is possible that lastMode is created without having encountered * its definition, in the case of nested modes. In that case we have * no useMode attribute but a mode element can appear further inside * the action (a nested mode). If no mode appears inside the action then * we need to resolve the anonymous mode that is not defined to the current mode. */ private Mode lastMode; } /** * Stores mode data. */ ModeData md = new ModeData(); /** * Keeps the mode data stack. */ private Stack modeDataStack = new Stack(); /** * Keeps the elements from NVDL representing the current context. * We need it to distinguish between modes, included modes and * nested modes. */ private Stack nvdlStack = new Stack(); /** * Creates a handler. * @param sr The Schema Receiver implementation for NVDL schemas. */ Handler(SchemaReceiverImpl sr) { this.sr = sr; this.eh = sr.getProperties().get(ValidateProperty.ERROR_HANDLER); this.ceh = new CountingErrorHandler(this.eh); this.resolver = ResolverFactory.createResolver(sr.getProperties()); } /** * Callback with the document locator. * @param locator The document locator. */ public void setDocumentLocator(Locator locator) { xmlBaseHandler.setLocator(locator); this.locator = locator; } /** * On start document. */ public void startDocument() throws SAXException { // creates a validator that validates against the schema for NVDL. try { PropertyMapBuilder builder = new PropertyMapBuilder(sr.getProperties()); builder.put(ValidateProperty.ERROR_HANDLER, ceh); validator = sr.getNvdlSchema().createValidator(builder.toPropertyMap()); } catch (IOException e) { throw new WrappedIOException(e); } catch (IncorrectSchemaException e) { throw new RuntimeException("internal error in RNG schema for NVDL"); } // set that validator content handler as delegate to receive the NVDL schema content. setDelegate(validator.getContentHandler()); // forward the setDocumentLocator and startDocument to the delegate handler. if (locator != null) super.setDocumentLocator(locator); super.startDocument(); } public Schema getSchema() throws IncorrectSchemaException, SAXException { if (validator == null || ceh.getHadErrorOrFatalError()) throw new IncorrectSchemaException(); Hashset openModes = new Hashset(); Hashset checkedModes = new Hashset(); for (Enumeration e = modeMap.keys(); e.hasMoreElements();) { String modeName = (String)e.nextElement(); Mode mode = (Mode)modeMap.get(modeName); if (!mode.isDefined()) error("undefined_mode", modeName, mode.getWhereUsed()); for (Mode tem = mode; tem != null; tem = tem.getBaseMode()) { if (checkedModes.contains(tem)) break; if (openModes.contains(tem)) { error("mode_cycle", tem.getName(), tem.getWhereDefined()); break; } openModes.add(tem); } checkedModes.addAll(openModes); openModes.clear(); } 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; } /** * Start element callback. * @param uri The namespace uri for this element. * @param localName The element local name. * @param qName The element qualified name. * @param attributes The attributes of this element. */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // call delegate handler super.startElement(uri, localName, qName, attributes); // handle xml:base xmlBaseHandler.startElement(); String xmlBase = attributes.getValue(WellKnownNamespaces.XML, "base"); if (xmlBase != null) xmlBaseHandler.xmlBaseAttribute(xmlBase); // ignore foreign elements if (!NVDL_URI.equals(uri) || foreignDepth > 0) { foreignDepth++; return; } // stop if we got errors. if (ceh.getHadErrorOrFatalError()) return; // dispatch based on the element name if (localName.equals("rules")) parseRules(attributes); else if (localName.equals("mode")) { String parent = (String)nvdlStack.peek(); if ("rules".equals(parent)) parseMode(attributes); else if ("mode".equals(parent)) // mode inside mode - included mode. parseIncludedMode(attributes); else // nested mode parseNestedMode(attributes); } else if (localName.equals("namespace")) parseNamespace(attributes); else if (localName.equals("anyNamespace")) parseAnyNamespace(attributes); else if (localName.equals("validate")) parseValidate(attributes); else if (localName.equals("reject")) parseReject(attributes); else if (localName.equals("attach")) parseAttach(attributes); else if (localName.equals("unwrap")) parseUnwrap(attributes); else if (localName.equals("attachPlaceholder")) parseAttachPlaceholder(attributes); else if (localName.equals("allow")) parseAllow(attributes); else if (localName.equals("context")) parseContext(attributes); else if (localName.equals("option")) parseOption(attributes); else if (localName.equals("trigger")) parseTrigger(attributes); else if (localName.equals("schema")) error("embedded_schemas"); else if (localName.equals("cancelNestedActions")) parseCancelNestedActions(attributes); else if (localName.equals("message")) ; // noop else throw new RuntimeException("unexpected element \"" + localName + "\""); // add the NVDL element on the stack nvdlStack.push(localName); } /** * End element callback. * @param namespaceURI The namespace uri for this element. * @param localName The element local name. * @param qName The element qualified name. */ public void endElement(String namespaceURI, String localName, String qName) throws SAXException { // call the delegate handler super.endElement(namespaceURI, localName, qName); // handle xml:base xmlBaseHandler.endElement(); // ignore foreign elements if (foreignDepth > 0) { foreignDepth--; return; } // exit early if we got errors. if (ceh.getHadErrorOrFatalError()) return; // pop the NVDL element from the stack nvdlStack.pop(); // dispatch based on element name. if (localName.equals("validate")) finishValidate(); else if (localName.equals("mode")) { String parent = (String)nvdlStack.peek(); if ("rules".equals(parent)) finishMode(); else if ("mode".equals(parent)) // mode inside mode - included mode. finishIncludedMode(); else // nested mode. finishNestedMode(); } } /** * Parse the rules element. * Initializes * the start mode * the current mode * the defaultSchemaType * @param attributes The rule element attributes. */ private void parseRules(Attributes attributes) { startMode = getModeAttribute(attributes, "startMode"); // If not start mode specified we create an implicit mode. if (startMode == null) { startMode = lookupCreateMode(IMPLICIT_MODE_NAME); md.currentMode = startMode; // mark this implicit mode as not defined in the schema. startMode.noteDefined(null); } // Set the current location as the location the start mode is first used. startMode.noteUsed(locator); // if the schema should be used for validating only attributes // we need to create a wrapper that allows any element from any namespace // as the placeholder for the attributes we want to validate. if (attributesSchema) { Mode wrapper = lookupCreateMode(WRAPPER_MODE_NAME); // creates element actions - allow and set them for any namespace // the attributes will be validated further in the real schema start mode. ActionSet actions = new ActionSet(); actions.addNoResultAction(new AllowAction(new ModeUsage(startMode, startMode))); wrapper.bindElement(NamespaceSpecification.ANY_NAMESPACE, NamespaceSpecification.DEFAULT_WILDCARD, actions); wrapper.noteDefined(null); // we use the wrapper mode as the start mode. startMode = wrapper; } // Get the default value for schema type if it is specified in rule/@schemaType. defaultSchemaType = getSchemaType(attributes); } /** * Parse a mode element. * @param attributes The element attributes. * @throws SAXException */ private void parseMode(Attributes attributes) throws SAXException { // Get the mode (create it if it does not exists) corresponding to the name attribute. md.currentMode = getModeAttribute(attributes, "name"); // If already defined, report errors. if (md.currentMode.isDefined()) { error("duplicate_mode", md.currentMode.getName()); error("first_mode", md.currentMode.getName(), md.currentMode.getWhereDefined()); } else { // Check if we have a base mode and set that as the base mode for this mode. Mode base = getModeAttribute(attributes, "extends"); if (base != null) md.currentMode.setBaseMode(base); // record the location where this mode is defined. md.currentMode.noteDefined(locator); } } /** * Parse a mode element. * @param attributes The element attributes. * @throws SAXException */ private void parseIncludedMode(Attributes attributes) throws SAXException { // Create an anonymous mode. Mode parent = md.currentMode; modeDataStack.push(md); md = new ModeData(); md.currentMode = new Mode(defaultBaseMode); md.currentMode.noteDefined(locator); parent.addIncludedMode(md.currentMode); } /** * Parse a mode element. * @param attributes The element attributes. * @throws SAXException */ private void parseNestedMode(Attributes attributes) throws SAXException { // Nested mode is an anonymous mode inside an action. The action does // not have a useMode attribute and we alrady have the mode for that // created in the current mode data lastMode, so we use that and define it // as this nested mode. ModeData oldMd = md; modeDataStack.push(md); md = new ModeData(); md.currentMode = oldMd.lastMode; // If already defined, report errors. if (md.currentMode.isDefined()) { error("duplicate_mode", md.currentMode.getName()); error("first_mode", md.currentMode.getName(), md.currentMode.getWhereDefined()); } else { // record the location where this mode is defined. md.currentMode.noteDefined(locator); } } /** * Parse a namespace rule. * @param attributes The namespace element attributes. * @throws SAXException */ private void parseNamespace(Attributes attributes) throws SAXException { md.anyNamespace = false; parseRule(getNs(attributes), attributes); } /** * Parse an anyNamespace rule. * @param attributes The anyNamespace element attributes. * @throws SAXException */ private void parseAnyNamespace(Attributes attributes) throws SAXException { md.anyNamespace = true; parseRule(NamespaceSpecification.ANY_NAMESPACE, attributes); } /** * Parse namespace and anyNamespace rules/ * @param ns The namespace, ##any for anyNamespace * @param attributes The rule attributes. * @throws SAXException */ private void parseRule(String ns, Attributes attributes) throws SAXException { // gets the value of the match attribute, defaults to match elements only. md.match = toElementsOrAttributes(attributes.getValue("", "match"), ElementsOrAttributes.ELEMENTS); String wildcard = attributes.getValue("", "wildCard"); if (wildcard == null) { wildcard = NamespaceSpecification.DEFAULT_WILDCARD; } // check if match attributes if (md.match.containsAttributes()) { // creates an empty attributes action set. md.attributeActions = new AttributeActionSet(); // if we already have attribute actions for this namespace // signal an error. if (!md.currentMode.bindAttribute(ns, wildcard, md.attributeActions)) { if (ns.equals(NamespaceSpecification.ANY_NAMESPACE)) error("duplicate_attribute_action_any_namespace"); else error("duplicate_attribute_action", ns); } } else md.attributeActions = null; // XXX: george // } else md.attributeActions=null; //??? // check if match elements if (md.match.containsElements()) { // creates an empty action set. md.actions = new ActionSet(); // if we already have actions for this namespace // signal an error. if (!md.currentMode.bindElement(ns, wildcard, md.actions)) { if (ns.equals(NamespaceSpecification.ANY_NAMESPACE)) error("duplicate_element_action_any_namespace"); else error("duplicate_element_action", ns); } } else md.actions = null; } /** * Parse a validate action. * @param attributes The validate element attributes. * @throws SAXException */ private void parseValidate(Attributes attributes) throws SAXException { // get the resolved URI pointing to the schema. md.schemaUriRef = getSchema(attributes); md.schemaUriBase = xmlBaseHandler.getBaseUri(); // get the schema type md.schemaType = getSchemaType(attributes); // if no schemaType attribute, use the default schema type. if (md.schemaType == null) md.schemaType = defaultSchemaType; if (SchemaReceiverImpl.LEGACY_RNC_MEDIA_TYPE.equals(md.schemaType)) warning("legacy_rnc_media_type", locator); // if we matched on elements create a mode usage. if (md.actions != null) md.modeUsage = getModeUsage(attributes); else md.modeUsage = null; // prepare to receive validate options. md.options = new PropertyMapBuilder(); md.mustSupportOptions.clear(); } /** * Notification that the validate element ends. * @throws SAXException */ private void finishValidate() throws SAXException { if (md.schemaUriRef != null) { try { // if we had attribute actions, that is matching attributes // we add a schema to the attributes action set. if (md.attributeActions != null) { Schema schema = createSubSchema(true); md.attributeActions.addSchema(schema); } // if we had element actions, that is macting elements // we add a validate action with the schema and the specific mode usage. if (md.actions != null) { Schema schema = createSubSchema(false); md.actions.addNoResultAction(new ValidateAction(md.modeUsage, schema)); } } catch (IncorrectSchemaException e) { hadError = true; } catch (IOException e) { throw new WrappedIOException(e); } } } /** * Notification that the mode element ends. * @throws SAXException */ private void finishMode() throws SAXException { } /** * Notification that the mode element ends. * @throws SAXException */ private void finishIncludedMode() throws SAXException { md = (ModeData)modeDataStack.pop(); } /** * Notification that the mode element ends. * @throws SAXException */ private void finishNestedMode() throws SAXException { md = (ModeData)modeDataStack.pop(); } /** * Creates a sub schema for the ending validate action (this is * called from finishValidate). * * @param isAttributesSchema If the schema is intended to validate only attributes. * @return A Schema. * @throws IOException * @throws IncorrectSchemaException * @throws SAXException */ private Schema createSubSchema(boolean isAttributesSchema) throws IOException, IncorrectSchemaException, SAXException { // the user specified options PropertyMap requestedProperties = md.options.toPropertyMap(); // let the schema receiver create a child schema // XXX parse the media type to the resolver Schema schema = sr.createChildSchema(resolver.resolve(md.schemaUriRef, md.schemaUriBase), md.schemaType, requestedProperties, isAttributesSchema); // get the schema properties PropertyMap actualProperties = schema.getProperties(); // Check if the actual properties match the must support properties. for (Enumeration e = md.mustSupportOptions.elements(); e.hasMoreElements();) { MustSupportOption mso = (MustSupportOption)e.nextElement(); Object actualValue = actualProperties.get(mso.pid); if (actualValue == null) error("unsupported_option", mso.name, mso.locator); else if (!actualValue.equals(requestedProperties.get(mso.pid))) error("unsupported_option_arg", mso.name, mso.locator); } return schema; } /** * Parse a validate option. * @param attributes The option element attributes. * @throws SAXException */ private void parseOption(Attributes attributes) throws SAXException { // get the mustSupport flag boolean mustSupport; String mustSupportValue = attributes.getValue("", "mustSupport"); if (mustSupportValue != null) { mustSupportValue = mustSupportValue.trim(); mustSupport = mustSupportValue.equals("1") || mustSupportValue.equals("true"); } else mustSupport = false; // Resolve the option if specified relative to the NVDL URI. String name = Uri.resolve(NVDL_URI, attributes.getValue("", "name")); Option option = sr.getOption(name); // check if we got a known option. if (option == null) { if (mustSupport) error("unknown_option", name); } else { // known option, look for arguments String arg = attributes.getValue("", "arg"); try { PropertyId pid = option.getPropertyId(); Object value = option.valueOf(arg); Object oldValue = md.options.get(pid); if (oldValue != null) { value = option.combine(new Object[]{oldValue, value}); if (value == null) error("duplicate_option", name); else md.options.put(pid, value); } else { md.options.put(pid, value); md.mustSupportOptions.addElement(new MustSupportOption(name, pid, locator == null ? null : new LocatorImpl(locator))); } } catch (OptionArgumentPresenceException e) { error(arg == null ? "option_requires_argument" : "option_unexpected_argument", name); } catch (OptionArgumentException e) { if (arg == null) error("option_requires_argument", name); else error("option_bad_argument", name, arg); } } } /** * Parse a trigger element. * @param attributes The trigger element attributes. * @throws SAXException */ private void parseTrigger(Attributes attributes) throws SAXException { // get the ns and nameList, we know they are not null as we validate against the nvdl.rng schema. String ns = attributes.getValue("", "ns"); String nameList = attributes.getValue("", "nameList"); StringTokenizer st = new StringTokenizer(nameList); Set names = new HashSet(st.countTokens()); while (st.hasMoreTokens()) { names.add(st.nextToken()); } triggers.add(new Trigger(ns, names)); } /** * Parse an attach action. * @param attributes The attach element attributes. */ private void parseAttach(Attributes attributes) { // if the rule matched attributes set the attach flag in the attribute actions. if (md.attributeActions != null) md.attributeActions.setAttach(true); // if the rule matched elements, the the mode usage and create a attach result action // with that mode usage. if (md.actions != null) { md.modeUsage = getModeUsage(attributes); md.actions.setResultAction(new AttachAction(md.modeUsage)); } // no element action -> no modeUsage. else md.modeUsage = null; } /** * Parse an unwrap action. * @param attributes The unwrap element attributes. */ private void parseUnwrap(Attributes attributes) { // this makes sense only on elements // if we have element actions, create the mode usage and add // an unwrap action with this mode usage. if (md.actions != null) { md.modeUsage = getModeUsage(attributes); md.actions.setResultAction(new UnwrapAction(md.modeUsage)); } // no element actions, no modeUsage. else md.modeUsage = null; } /** * Parse an attachPlaceholder action. * @param attributes The attachPlaceholder element attributes. */ private void parseAttachPlaceholder(Attributes attributes) { // this makes sense only on elements // if we have element actions, create the mode usage and add // an attachPlaceholder action with this mode usage. if (md.actions != null) { md.modeUsage = getModeUsage(attributes); md.actions.setResultAction(new AttachPlaceholderAction(md.modeUsage)); } // no element actions, no modeUsage. else md.modeUsage = null; } /** * Parse an allow action. * * @param attributes The allow element attributes. */ private void parseAllow(Attributes attributes) { // if we match on elements, create the mode usage and add an allow action. if (md.actions != null) { md.modeUsage = getModeUsage(attributes); md.actions.addNoResultAction(new AllowAction(md.modeUsage)); } // no actions, no mode usage. else md.modeUsage = null; // no need to add anything in the attribute actions, allow // is equivalent with a noop action. } /** * Parse a reject action. * @param attributes The reject element attributes. */ private void parseReject(Attributes attributes) { // if element actions, get the mode usage and add a reject // action with this mode usage. if (md.actions != null) { md.modeUsage = getModeUsage(attributes); md.actions.addNoResultAction(new RejectAction(md.modeUsage)); } // no actions, no mode usage else md.modeUsage = null; // if attribute actions, set the reject flag. if (md.attributeActions != null) md.attributeActions.setReject(true); } /** * Parse a cancel nested actions action. * * @param attributes The cancelNestedActions element attributes. */ private void parseCancelNestedActions(Attributes attributes) { // if we match on elements, create the mode usage and add a // cancelNestedActions action. if (md.actions != null) { md.modeUsage = getModeUsage(attributes); md.actions.setCancelNestedActions(true); } // no actions, no mode usage. else md.modeUsage = null; // if attribute actions set the cancelNestedActions flag. if (md.attributeActions != null) { md.attributeActions.setCancelNestedActions(true); } } /** * Parse context dependent mode usages. * @param attributes The context element attributes. * @throws SAXException */ private void parseContext(Attributes attributes) throws SAXException { // TODO: check this in the NVDL spec. // context not allowed within anyNamespace.??? // IT SEEMS IT IS ALLOWED IN NVDL... //if (md.anyNamespace) { // error("context_any_namespace"); // return; //} // Get the mode to be used further on this context. Mode mode = getUseMode(attributes); md.lastMode = mode; try { // parse the path value into a list of Path objects // and add them to the mode usage Vector paths = Path.parse(attributes.getValue("", "path")); // XXX warning if modeUsage is null if (md.modeUsage != null) { for (int i = 0, len = paths.size(); i < len; i++) { Path path = (Path)paths.elementAt(i); if (!md.modeUsage.addContext(path.isRoot(), path.getNames(), mode)) error("duplicate_path", path.toString()); } } } catch (Path.ParseException e) { error(e.getMessageKey()); } } /** * Get the URI specified by a schema attribute and if we have a * relative location resolve that against the base URI taking into * account also eventual xml:base attributes. * @param attributes The validate element attributes. * @return A resolved URI as string. * @throws SAXException If the schema contains a fragment id. */ private String getSchema(Attributes attributes) throws SAXException { String schemaUri = attributes.getValue("", "schema"); if ("".equals(schemaUri)) { error("no_schema"); schemaUri = null; } if (schemaUri != null) { if (Uri.hasFragmentId(schemaUri)) error("schema_fragment_id"); return schemaUri; } return null; } /** * Get the schema type * @param attributes The attributes * @return The value of the schemaType attribute. */ private String getSchemaType(Attributes attributes) { return attributes.getValue("", "schemaType"); } /** * Get an ElementsOrAttributes instance depending on the match attribute value. * @param value The match attribute value. * @param defaultValue The default value if value is null. * @return an ElementsOrAttributes constant. */ 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; } /** * Creates a mode usage that matches current mode and uses further * the mode specified by the useMode attribute. * @param attributes The action element attributes. * @return A mode usage from currentMode to the mode specified * by the useMode attribute. */ private ModeUsage getModeUsage(Attributes attributes) { md.lastMode = getUseMode(attributes); return new ModeUsage(md.lastMode, md.currentMode); } /** * Get the Mode for the useMode attribute. * @param attributes the attributes * @return the mode with the useMode name or the special Mode.CURRENT mode that * will be resolved to the current mode in a Mode usage. */ private Mode getUseMode(Attributes attributes) { Mode mode = getModeAttribute(attributes, "useMode"); if (mode == null) return new Mode(defaultBaseMode); mode.noteUsed(locator); return mode; } /** * Get the namespace from the ns attribute. * Also check that the namespace is an absolute URI and report an * error otherwise. * @param attributes The list of attributes of the namespace element * @return The ns value. * @throws SAXException */ private String getNs(Attributes attributes) throws SAXException { String ns = attributes.getValue("", "ns"); if (ns != null && !Uri.isAbsolute(ns) && !ns.equals("")) error("ns_absolute"); return ns; } /** * Report a no arguments error from a key. * @param key The error key. * @throws SAXException */ void error(String key) throws SAXException { hadError = true; if (eh == null) return; eh.error(new SAXParseException(localizer.message(key), locator)); } /** * Report an one argument error. * @param key The error key. * @param arg The argument. * @throws SAXException */ void error(String key, String arg) throws SAXException { hadError = true; if (eh == null) return; eh.error(new SAXParseException(localizer.message(key, arg), locator)); } /** * Report an one argument error with location. * @param key The error key. * @param arg The argument. * @param locator The location. * @throws SAXException */ 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)); } /** * Report a two arguments error. * @param key The error key. * @param arg1 The first argument. * @param arg2 The second argument. * @throws SAXException */ 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)); } /** * Report a no argument warning with location. * @param key The warning key. * @param locator The location. * @throws SAXException */ void warning(String key, Locator locator) throws SAXException { if (eh == null) return; eh.warning(new SAXParseException(localizer.message(key), locator)); } } /** * Creates a NVDL schema implementation. * Initializes the attributesSchema flag and the built in modes. * @param properties Properties. */ SchemaImpl(PropertyMap properties) { super(properties); this.attributesSchema = properties.contains(WrapProperty.ATTRIBUTE_OWNER); makeBuiltinMode("#allow", AllowAction.class); makeBuiltinMode("#attach", AttachAction.class); makeBuiltinMode("#unwrap", UnwrapAction.class); defaultBaseMode = makeBuiltinMode("#reject", RejectAction.class); } /** * Makes a built in mode. * @param name The mode name. * @param cls The action class. * @return A Mode object. */ private Mode makeBuiltinMode(String name, Class cls) { // lookup/create a mode with the given name. Mode mode = lookupCreateMode(name); // Init the element action set for this mode. ActionSet actions = new ActionSet(); // from the current mode we will use further the built in mode. ModeUsage modeUsage = new ModeUsage(Mode.CURRENT, mode); // Add the action corresponding to the built in mode. if (cls == AttachAction.class) actions.setResultAction(new AttachAction(modeUsage)); else if (cls == AllowAction.class) actions.addNoResultAction(new AllowAction(modeUsage)); else if (cls == UnwrapAction.class) actions.setResultAction(new UnwrapAction(modeUsage)); else actions.addNoResultAction(new RejectAction(modeUsage)); // set the actions on any namespace. mode.bindElement(NamespaceSpecification.ANY_NAMESPACE, NamespaceSpecification.DEFAULT_WILDCARD, actions); // the mode is not defined in the script explicitelly mode.noteDefined(null); // creates attribute actions AttributeActionSet attributeActions = new AttributeActionSet(); // if we have a schema for attributes then in the built in modes // we reject attributes by default // otherwise we attach attributes by default in the built in modes if (attributesSchema) attributeActions.setReject(true); else attributeActions.setAttach(true); // set the attribute actions on any namespace mode.bindAttribute(NamespaceSpecification.ANY_NAMESPACE, NamespaceSpecification.DEFAULT_WILDCARD, attributeActions); return mode; } /** * Installs the schema handler on the reader. * * @param in The reader. * @param sr The schema receiver. * @return The installed handler that implements also SchemaFuture. */ SchemaFuture installHandlers(XMLReader in, SchemaReceiverImpl sr) { Handler h = new Handler(sr); in.setContentHandler(h); return h; } /** * Creates a Validator for validating XML documents against this * NVDL script. * @param properties properties. */ public Validator createValidator(PropertyMap properties) { return new ValidatorImpl(startMode, triggers, properties); } /** * Get the mode specified by an attribute from no namespace. * * @param attributes The attributes. * @param localName The attribute name. * @return The mode refered by the licanName attribute. */ private Mode getModeAttribute(Attributes attributes, String localName) { return lookupCreateMode(attributes.getValue("", localName)); } /** * Gets a mode with the given name from the mode map. * If not present then it creates a new mode extending the default base mode. * * @param name The mode to look for or create if it does not exist. * @return Always a not null mode. */ private Mode lookupCreateMode(String name) { if (name == null) return null; name = name.trim(); Mode mode = (Mode)modeMap.get(name); if (mode == null) { mode = new Mode(name, defaultBaseMode); modeMap.put(name, mode); } return mode; } }