001    package com.oxygenxml.validate.nvdl;
002    
003    import java.io.IOException;
004    import java.util.Enumeration;
005    import java.util.Hashtable;
006    import java.util.Stack;
007    import java.util.Vector;
008    
009    import org.xml.sax.Attributes;
010    import org.xml.sax.ErrorHandler;
011    import org.xml.sax.InputSource;
012    import org.xml.sax.Locator;
013    import org.xml.sax.SAXException;
014    import org.xml.sax.SAXParseException;
015    import org.xml.sax.XMLReader;
016    import org.xml.sax.helpers.LocatorImpl;
017    
018    import com.thaiopensource.util.Localizer;
019    import com.thaiopensource.util.PropertyId;
020    import com.thaiopensource.util.PropertyMap;
021    import com.thaiopensource.util.PropertyMapBuilder;
022    import com.thaiopensource.util.Uri;
023    import com.thaiopensource.validate.AbstractSchema;
024    import com.thaiopensource.validate.IncorrectSchemaException;
025    import com.thaiopensource.validate.Option;
026    import com.thaiopensource.validate.OptionArgumentException;
027    import com.thaiopensource.validate.OptionArgumentPresenceException;
028    import com.thaiopensource.validate.Schema;
029    import com.thaiopensource.validate.ValidateProperty;
030    import com.thaiopensource.validate.Validator;
031    import com.thaiopensource.validate.auto.SchemaFuture;
032    import com.thaiopensource.xml.sax.CountingErrorHandler;
033    import com.thaiopensource.xml.sax.DelegatingContentHandler;
034    import com.thaiopensource.xml.sax.XmlBaseHandler;
035    import com.thaiopensource.xml.util.WellKnownNamespaces;
036    
037    /**
038     * Schema implementation for NVDL scripts.
039     */
040    class SchemaImpl extends AbstractSchema {
041      /**
042       * Mode name used when the script does not define modes and just enters
043       * namespace and anyNamespace mappings directly inside rules.
044       */
045      static private final String IMPLICIT_MODE_NAME = "#implicit";
046      
047      /**
048       * Mode name used when we have to use this script as an attributes schema.
049       * The wrapper mode allows elements from any namespace.
050       */
051      static private final String WRAPPER_MODE_NAME = "#wrapper";
052      
053      /**
054       * The NVDL URI.
055       */
056      static final String NVDL_URI = "http://purl.oclc.org/dsdl/nvdl/ns/structure/1.0";
057      
058      /**
059       * A hash with the modes.
060       */
061      private final Hashtable modeMap = new Hashtable();
062      
063      /**
064       * A hash with the triggers on namespace.
065       * Element names are stored concatenated in a string, each name preceded by #.
066       */
067      private final Triggers triggers = new Triggers();
068        
069      /**
070       * The start mode.
071       */
072      private Mode startMode;
073      
074      /**
075       * Default base mode, rejects everything.
076       */
077      private final Mode defaultBaseMode;
078      
079      /**
080       * Flag indicating if the schema needs to be changed to handle
081       * attributes only, the element in this case is a placeholder.
082       */
083      private final boolean attributesSchema;
084    
085      /**
086       * Wrapps an IOException as a RuntimeException.
087       *
088       */
089      static private final class WrappedIOException extends RuntimeException {
090        /**
091         * The actual IO Exception.
092         */
093        private final IOException exception;
094    
095        /**
096         * Creates a wrapped exception.
097         * @param exception The IOException.
098         */
099        private WrappedIOException(IOException exception) {
100          this.exception = exception;
101        }
102    
103        /**
104         * Get the actual IO Exception.
105         * @return IOException.
106         */
107        private IOException getException() {
108          return exception;
109        }
110      }
111    
112      /**
113       * Stores information about options that must be supported by the 
114       * validator.
115       */
116      static private class MustSupportOption {
117        /**
118         * The option name.
119         */
120        private final String name;
121        /**
122         * The property id.
123         */
124        private final PropertyId pid;
125        
126        /**
127         * Locator pointing to where this option is declared.
128         */
129        private final Locator locator;
130    
131        /**
132         * Creates a mustu support option.
133         * @param name The option name
134         * @param pid property id.
135         * @param locator locator pointing to where this option is declared.
136         */
137        MustSupportOption(String name, PropertyId pid, Locator locator) {
138          this.name = name;
139          this.pid = pid;
140          this.locator = locator;
141        }
142      }
143    
144      /**
145       * This class is registered as content handler on the XMLReader that
146       * parses the NVDL script.
147       * It creates the Schema representation for this script and also validates 
148       * the script against the NVDL schema.
149       */
150      private class Handler extends DelegatingContentHandler implements SchemaFuture {
151        /**
152         * The schema receiver. Used to cretae other schemas and access options.
153         */
154        private final SchemaReceiverImpl sr;
155        
156        /**
157         * Flag indicating that we encountered an error.
158         */
159        private boolean hadError = false;
160        
161        /**
162         * The error handler.
163         */
164        private final ErrorHandler eh;
165        
166        /**
167         * A counting error handler that wraps the error handler.
168         * It is useful to stop early if we encounter errors.
169         */
170        private final CountingErrorHandler ceh;
171        
172        /**
173         * Convert error keys to messages.
174         */
175        private final Localizer localizer = new Localizer(SchemaImpl.class);
176        
177        /**
178         * Error locator.
179         */
180        private Locator locator;
181        
182        /**
183         * Handle xml:base attributes.
184         */
185        private final XmlBaseHandler xmlBaseHandler = new XmlBaseHandler();
186        
187        /**
188         * For ignoring foreign elements.
189         */
190        private int foreignDepth = 0;
191        
192        
193        /**
194         * The value of rules/@schemaType
195         */
196        private String defaultSchemaType;
197        
198        /**
199         * The validator that checks the script against the
200         * NVDL RelaxNG schema.
201         */
202        private Validator validator;
203        
204        class ModeData {
205          /**
206           * Points to the current mode.
207           */
208          private Mode currentMode = null;
209        
210          /**
211           * The value of the match attribute on the current rule.
212           */
213          private ElementsOrAttributes match;
214          
215          /**
216           * The current element actions.
217           */
218          private ActionSet actions;
219          
220          /**
221           * The current attribute actions.
222           */
223          private AttributeActionSet attributeActions;
224          
225          /**
226           * The URI of the schema for the current validate action.
227           */
228          private String schemaUri;
229          
230          /**
231           * The current validate action schema type.
232           */
233          private String schemaType;
234          
235          /**
236           * The options defined for a validate action.
237           */
238          private PropertyMapBuilder options;
239          
240          /**
241           * The options that must be supported by the validator
242           * for the current validate action.
243           */
244          private final Vector mustSupportOptions = new Vector();
245          
246          /**
247           * The current mode usage, for the current action.
248           */
249          private ModeUsage modeUsage;
250          
251          /**
252           * Flag indicating if we are in a namespace rule or in an anyNamespace rule.
253           */
254          private boolean anyNamespace;
255          
256          private Mode lastMode;
257        }
258        
259        /**
260         * Stores mode data.
261         */
262        ModeData md = new ModeData();
263        
264        /**
265         * Keeps the elements from mode data stack.
266         */
267        private Stack modeDataStack = new Stack();
268        
269        
270        /**
271         * Keeps the elements from NVDL representing the current context.
272         */
273        private Stack nvdlStack = new Stack();
274        
275        /**
276         * Creates a handler.
277         * @param sr The Schema Receiver implementation for NVDL schemas.
278         */
279        Handler(SchemaReceiverImpl sr) {
280          this.sr = sr;
281          this.eh = ValidateProperty.ERROR_HANDLER.get(sr.getProperties());
282          this.ceh = new CountingErrorHandler(this.eh);
283        }
284    
285        /**
286         * Callback with the document locator.
287         * @param locator The document locator.
288         */
289        public void setDocumentLocator(Locator locator) {
290          xmlBaseHandler.setLocator(locator);
291          this.locator = locator;
292        }
293    
294        /**
295         * On start document.
296         */
297        public void startDocument() throws SAXException {
298          // creates a validator that validates against the schema for NVDL.
299          try {
300            PropertyMapBuilder builder = new PropertyMapBuilder(sr.getProperties());
301            ValidateProperty.ERROR_HANDLER.put(builder, ceh);
302            validator = sr.getNvdlSchema().createValidator(builder.toPropertyMap());
303          }
304          catch (IOException e) {
305            throw new WrappedIOException(e);
306          }
307          catch (IncorrectSchemaException e) {
308            throw new RuntimeException("internal error in RNG schema for NVDL");
309          }
310          // set that validator content handler as delegate to receive the NVDL schema content.
311          setDelegate(validator.getContentHandler());
312          // forward the setDocumentLocator and startDocument to the delegate handler.
313          if (locator != null)
314            super.setDocumentLocator(locator);
315          super.startDocument();
316        }
317    
318        public Schema getSchema() throws IncorrectSchemaException, SAXException {
319          if (validator == null || ceh.getHadErrorOrFatalError())
320            throw new IncorrectSchemaException();
321          Hashset openModes = new Hashset();
322          Hashset checkedModes = new Hashset();
323          for (Enumeration en = modeMap.keys(); en.hasMoreElements();) {
324            String modeName = (String)en.nextElement();
325            Mode mode = (Mode)modeMap.get(modeName);
326            if (!mode.isDefined())
327              error("undefined_mode", modeName, mode.getWhereUsed());
328            for (Mode tem = mode; tem != null; tem = tem.getBaseMode()) {
329              if (checkedModes.contains(tem))
330                break;
331              if (openModes.contains(tem)) {
332                error("mode_cycle", tem.getName(), tem.getWhereDefined());
333                break;
334              }
335              openModes.add(tem);
336            }
337            checkedModes.addAll(openModes);
338            openModes.clear();
339          }
340          if (hadError)
341            throw new IncorrectSchemaException();
342          return SchemaImpl.this;
343        }
344    
345        public RuntimeException unwrapException(RuntimeException e) throws SAXException, IOException, IncorrectSchemaException {
346          if (e instanceof WrappedIOException)
347            throw ((WrappedIOException)e).getException();
348          return e;
349        }
350    
351        /**
352         * Start element callback.
353         * @param uri The namespace uri for this element.
354         * @param localName The element local name.
355         * @param qName The element qualified name.
356         * @param attributes The attributes of this element.
357         */
358        public void startElement(String uri, String localName,
359                                 String qName, Attributes attributes)
360                throws SAXException {
361          // call delegate handler
362          super.startElement(uri, localName, qName, attributes);
363          // handle xml:base
364          xmlBaseHandler.startElement();
365          String xmlBase = attributes.getValue(WellKnownNamespaces.XML, "base");
366          if (xmlBase != null)
367            xmlBaseHandler.xmlBaseAttribute(xmlBase);
368          // ignore foreign elements
369          if (!NVDL_URI.equals(uri) || foreignDepth > 0) {
370            foreignDepth++;
371            return;
372          }      
373          // stop if we got errors.
374          if (ceh.getHadErrorOrFatalError()) {
375            return;
376          }
377          // dispatch based on the element name
378          if (localName.equals("rules")) {
379            parseRules(attributes);
380          } else if (localName.equals("mode")) {
381            if (nvdlStack.size()>0) {
382              if ("rules".equals(nvdlStack.peek())) {        
383                parseMode(attributes);
384              } else if ("mode".equals(nvdlStack.peek())) {
385                // mode inside mode - included mode.
386                parseIncludedMode(attributes);
387              } else {
388                // nested modes - 
389                parseNestedMode(attributes);
390              }
391            }
392          } else if (localName.equals("namespace")) {
393            parseNamespace(attributes);
394          } else if (localName.equals("anyNamespace")) {
395            parseAnyNamespace(attributes);
396          } else if (localName.equals("validate")) {
397            parseValidate(attributes);
398          } else if (localName.equals("reject")) {
399            parseReject(attributes);
400          } else if (localName.equals("attach")) {
401            parseAttach(attributes);
402          } else if (localName.equals("unwrap")) {
403            parseUnwrap(attributes);
404          } else if (localName.equals("attachPlaceholder")) {
405            parseAttachPlaceholder(attributes);
406          } else if (localName.equals("allow")) {
407            parseAllow(attributes);
408          } else if (localName.equals("context")) {
409            parseContext(attributes);
410          } else if (localName.equals("option")) {
411            parseOption(attributes);
412          } else if (localName.equals("trigger")) {
413            parseTrigger(attributes);
414          } else if (localName.equals("schema")) {
415            error("embedded_schemas");
416          } else if (localName.equals("cancelNestedActions")) {
417            parseCancelNestedActions(attributes);
418          } else if (localName.equals("message")) {
419              // noop;
420          } else {
421            error("unexpected_element", localName, locator);
422            throw new RuntimeException("unexpected element \"" + localName + "\"");
423          }
424          // add the NVDL element on the stack
425          nvdlStack.push(localName);
426          
427        }
428    
429        /**
430         * End element callback.
431         * @param namespaceURI The namespace uri for this element.
432         * @param localName The element local name.
433         * @param qName The element qualified name.
434         */
435        public void endElement(String namespaceURI, String localName,
436                               String qName)
437                throws SAXException {
438          // call the delegate handler
439          super.endElement(namespaceURI, localName, qName);
440          // handle xml:base
441          xmlBaseHandler.endElement();
442          // ignore foreign elements
443          if (foreignDepth > 0) {
444            foreignDepth--;
445            return;
446          }
447          // exit early if we got errors.
448          if (ceh.getHadErrorOrFatalError())
449            return;
450          // pop the NVDL element from the stack
451          nvdlStack.pop();
452          // dispacth based on element name.
453          if (localName.equals("validate"))
454            finishValidate();
455          else if (localName.equals("mode")) {
456            if (nvdlStack.size()>0) {
457              if ("rules".equals(nvdlStack.peek())) {        
458                finishMode();
459              } else if ("mode".equals(nvdlStack.peek())) {
460                // mode inside mode - included mode.
461                finishIncludedMode();
462              } else {
463                // nested modes - 
464                finishNestedMode();
465              }
466            }
467          }
468        }
469    
470        /**
471         * Parse the rules element.
472         * Initializes 
473         *  the start mode 
474         *  the current mode
475         *  the defaultSchemaType
476         * @param attributes The rule element attributes.
477         */
478        private void parseRules(Attributes attributes) {
479          startMode = getModeAttribute(attributes, "startMode");
480          // If not start mode specified we create an implicit mode.
481          if (startMode == null) {
482            startMode = lookupCreateMode(IMPLICIT_MODE_NAME);
483            md.currentMode = startMode;
484            // mark this implicit mode as not defined in the schema.
485            startMode.noteDefined(null);
486          }
487          // Set the current location as the location the start mode is first used.
488          startMode.noteUsed(locator);
489          // if the schema should be used for validating only attributes
490          // we need to create a wrapper that allows any element from any namespace
491          // as the placeholder for the attributes we want to validate.
492          if (attributesSchema) {
493            Mode wrapper = lookupCreateMode(WRAPPER_MODE_NAME);
494            // creates element actions - allow and set them for any namespace
495            // the attributes will be validated further in the real schema start mode.
496            ActionSet actions = new ActionSet();
497            actions.addNoResultAction(new AllowAction(new ModeUsage(startMode, startMode)));
498            wrapper.bindElement(NamespaceSpecification.ANY_NAMESPACE, NamespaceSpecification.DEFAULT_WILDCARD, actions);
499            // wrapper mode is not defines
500            wrapper.noteDefined(null);
501            // we use the wrapper mode as the start mode.
502            startMode = wrapper;
503          }
504          // Get the default value for schema type if it is specified in rule/@schemaType.
505          defaultSchemaType = getSchemaType(attributes);
506        }
507    
508        /**
509         * Parse a mode element.
510         * @param attributes The element attributes.
511         * @throws SAXException
512         */
513        private void parseMode(Attributes attributes) throws SAXException {
514          // Get the mode (create it if it does not exists) corresponding to the name attribute.
515          md.currentMode = getModeAttribute(attributes, "name");
516          // If already defined, report errors.
517          if (md.currentMode.isDefined()) {
518            error("duplicate_mode", md.currentMode.getName());
519            error("first_mode", md.currentMode.getName(), md.currentMode.getWhereDefined());
520          }
521          else {
522            // Check if we have a base mode and set that as the base mode for this mode.
523            Mode base = getModeAttribute(attributes, "extends");
524            if (base != null)
525              md.currentMode.setBaseMode(base);
526            // record the location where this mode is defined.
527            md.currentMode.noteDefined(locator);
528          }
529        }
530    
531        /**
532         * Parse a mode element.
533         * @param attributes The element attributes.
534         * @throws SAXException
535         */
536        private void parseIncludedMode(Attributes attributes) throws SAXException {
537          // Create an anonymous mode.
538          Mode parent = md.currentMode;
539          modeDataStack.push(md);
540          md = new ModeData();      
541          md.currentMode = new Mode(defaultBaseMode);;
542          md.currentMode.noteDefined(locator);
543          parent.addIncludedMode(md.currentMode);
544        }
545    
546        /**
547         * Parse a mode element.
548         * @param attributes The element attributes.
549         * @throws SAXException
550         */
551        private void parseNestedMode(Attributes attributes) throws SAXException {
552          // Get the mode (create it if it does not exists) corresponding to the name attribute.
553          
554          ModeData oldMd = md;
555          modeDataStack.push(md);
556          md = new ModeData();
557          md.currentMode = oldMd.lastMode;
558          // If already defined, report errors.
559          if (md.currentMode.isDefined()) {
560            error("duplicate_mode", md.currentMode.getName());
561            error("first_mode", md.currentMode.getName(), md.currentMode.getWhereDefined());
562          }
563          else {
564            // record the location where this mode is defined.
565            md.currentMode.noteDefined(locator);
566        }
567        }
568        
569        /**
570         * Parse a namespace rule. 
571         * @param attributes The namespace element attributes.
572         * @throws SAXException
573         */
574        private void parseNamespace(Attributes attributes) throws SAXException {
575          md.anyNamespace = false;
576          parseRule(getNs(attributes), attributes);
577        }
578    
579        /**
580         * Parse an anyNamespace rule.
581         * @param attributes The anyNamespace element attributes.
582         * @throws SAXException
583         */
584        private void parseAnyNamespace(Attributes attributes) throws SAXException {
585          md.anyNamespace = true;
586          parseRule(NamespaceSpecification.ANY_NAMESPACE, attributes);
587        }
588    
589        /**
590         * Parse namespace and anyNamespace rules/
591         * @param ns The namespace, ##any for anyNamespace
592         * @param attributes The rule attributes.
593         * @throws SAXException
594         */
595        private void parseRule(String ns, Attributes attributes) throws SAXException {
596          // gets the value of the match attribute, defaults to match elements only.
597          md.match = toElementsOrAttributes(attributes.getValue("", "match"),
598                                         ElementsOrAttributes.ELEMENTS);
599          String wildcard = attributes.getValue("", "wildCard");
600          if (wildcard==null) {
601            wildcard = NamespaceSpecification.DEFAULT_WILDCARD;
602          }
603          
604          // check if match attributes
605          if (md.match.containsAttributes()) {
606            // creates an empty attributes action set.
607            md.attributeActions = new AttributeActionSet();
608            // if we already have attribute actions for this namespace 
609            // signal an error.
610            if (!md.currentMode.bindAttribute(ns, wildcard, md.attributeActions)) {
611              if (ns.equals(NamespaceSpecification.ANY_NAMESPACE))
612                error("duplicate_attribute_action_any_namespace");
613              else
614                error("duplicate_attribute_action", ns);
615            }
616          } else {
617            md.attributeActions = null;
618          }
619          // XXX: george // } else attributeActions=null; //???
620          
621          // check if match elements
622          if (md.match.containsElements()) {
623            // creates an empty action set.
624            md.actions = new ActionSet();
625            // if we already have actions for this namespace 
626            // signal an error.
627            if (!md.currentMode.bindElement(ns, wildcard, md.actions)) {
628              if (ns.equals(NamespaceSpecification.ANY_NAMESPACE))
629                error("duplicate_element_action_any_namespace");
630              else
631                error("duplicate_element_action", ns);
632            }
633          }
634          else
635            md.actions = null;
636          
637        }
638    
639        /**
640         * Parse a validate action.
641         * @param attributes The validate element attributes.
642         * @throws SAXException
643         */
644        private void parseValidate(Attributes attributes) throws SAXException {
645          // get the resolved URI pointing to the schema.
646          md.schemaUri = getSchema(attributes);
647          // get the schema type
648          md.schemaType = getSchemaType(attributes);
649          // if no schemaType attribute, use the default schema type.
650          if (md.schemaType == null)
651            md.schemaType = defaultSchemaType;
652          // if we matched on elements create a mode usage.
653          if (md.actions != null)
654            md.modeUsage = getModeUsage(attributes);
655          else
656            md.modeUsage = null;
657          // prepare to receive validate options.
658          md.options = new PropertyMapBuilder();
659          md.mustSupportOptions.clear();
660        }
661    
662        /**
663         * Notification that the validate element ends.
664         * @throws SAXException
665         */
666        private void finishValidate() throws SAXException {
667          if (md.schemaUri != null) {
668            try {
669              // if we had attribute actions, that is matching attributes
670              // we add a schema to the attributes action set.
671              if (md.attributeActions != null) {
672                Schema schema = createSubSchema(true);
673                md.attributeActions.addSchema(schema);
674              }
675              // if we had element actions, that is macting elements
676              // we add a validate action with the schema and the specific mode usage.
677              if (md.actions != null) {
678                Schema schema = createSubSchema(false);
679                md.actions.addNoResultAction(new ValidateAction(md.modeUsage, schema));
680              }
681            }
682            catch (IncorrectSchemaException e) {
683              hadError = true;
684            }
685            catch (IOException e) {
686              throw new WrappedIOException(e);
687            }
688          }
689        }
690    
691        /**
692         * Notification that the mode element ends.
693         * @throws SAXException
694         */
695        private void finishMode() throws SAXException {
696        }
697        
698        /**
699         * Notification that the mode element ends.
700         * @throws SAXException
701         */
702        private void finishIncludedMode() throws SAXException {
703          md = (ModeData)modeDataStack.pop();
704        }
705        
706        /**
707         * Notification that the mode element ends.
708         * @throws SAXException
709         */
710        private void finishNestedMode() throws SAXException {
711          md = (ModeData)modeDataStack.pop();
712        }
713        
714        /**
715         * Creates a sub schema for the ending validate action (this is 
716         * called from finishValidate).
717         * 
718         * @param isAttributesSchema If the schema is intended to validate only attributes.
719         * @return A Schema.
720         * @throws IOException
721         * @throws IncorrectSchemaException
722         * @throws SAXException
723         */
724        private Schema createSubSchema(boolean isAttributesSchema) throws IOException, IncorrectSchemaException, SAXException {
725          // the user specified options
726          PropertyMap requestedProperties = md.options.toPropertyMap();
727          // let the schema receiver create a child schema
728          Schema schema = sr.createChildSchema(new InputSource(md.schemaUri),
729                                               md.schemaType,
730                                               requestedProperties,
731                                               isAttributesSchema);
732          // get the schema properties
733          PropertyMap actualProperties = schema.getProperties();
734          // Check if the actual properties match the must support properties.
735          for (Enumeration enumeration = md.mustSupportOptions.elements(); enumeration.hasMoreElements();) {
736            MustSupportOption mso = (MustSupportOption)enumeration.nextElement();
737            Object actualValue = actualProperties.get(mso.pid);
738            if (actualValue == null)
739              error("unsupported_option", mso.name, mso.locator);
740            else if (!actualValue.equals(requestedProperties.get(mso.pid)))
741              error("unsupported_option_arg", mso.name, mso.locator);
742          }
743          return schema;
744        }
745    
746        /**
747         * Parse a validate option.
748         * @param attributes The option element attributes.
749         * @throws SAXException
750         */
751        private void parseOption(Attributes attributes) throws SAXException {
752          // get the mustSupport flag
753          boolean mustSupport;
754          String mustSupportValue = attributes.getValue("", "mustSupport");
755          if (mustSupportValue != null) {
756            mustSupportValue = mustSupportValue.trim();
757            mustSupport = mustSupportValue.equals("1") || mustSupportValue.equals("true");
758          }
759          else
760            mustSupport = false;
761          // Resolve the option if specified relative to the NVDL URI.
762          String name = Uri.resolve(NVDL_URI, attributes.getValue("", "name"));
763          Option option = sr.getOption(name);
764          // check if we got a known option.
765          if (option == null) {
766            if (mustSupport)
767              error("unknown_option", name);
768          }
769          else {
770          // known option, look for arguments
771            String arg = attributes.getValue("", "arg");
772            try {
773              PropertyId pid = option.getPropertyId();
774              Object value = option.valueOf(arg);
775              Object oldValue = md.options.get(pid);
776              if (oldValue != null) {
777                value = option.combine(new Object[]{oldValue, value});
778                if (value == null)
779                  error("duplicate_option", name);
780                else
781                  md.options.put(pid, value);
782              }
783              else {
784                md.options.put(pid, value);
785                md.mustSupportOptions.addElement(new MustSupportOption(name, pid,
786                                                                    locator == null
787                                                                    ? null
788                                                                    : new LocatorImpl(locator)));
789              }
790            }
791            catch (OptionArgumentPresenceException e) {
792              error(arg == null ? "option_requires_argument" : "option_unexpected_argument", name);
793            }
794            catch (OptionArgumentException e) {
795              if (arg == null)
796                error("option_requires_argument", name);
797              else
798                error("option_bad_argument", name, arg);
799            }
800          }
801        }
802    
803        /**
804         * Parse a trigger element.
805         * @param attributes The trigger element attributes.
806         * @throws SAXException
807         */
808        private void parseTrigger(Attributes attributes) throws SAXException {
809          // get the ns and nameList
810          String ns = attributes.getValue("", "ns");
811          String nameList = attributes.getValue("", "nameList");
812          if (ns != null && nameList != null) {
813            String errors = triggers.addTrigger(ns, nameList);
814            if (errors!=null) {
815              error ("invalid_nodeList", errors, locator);
816            }
817          }
818        }
819        
820        /**
821         * Parse an attach action.
822         * @param attributes The attach element attributes.
823         */
824        private void parseAttach(Attributes attributes) {
825          // if the rule matched attributes set the attach flag in the attribute actions.
826          if (md.attributeActions != null)
827            md.attributeActions.setAttach(true);
828          // if the rule matched elements, the the mode usage and create a attach result action
829          // with that mode usage.
830          if (md.actions != null) {
831            md.modeUsage = getModeUsage(attributes);
832            md.actions.setResultAction(new AttachAction(md.modeUsage));
833          }
834          // no element action -> no modeUsage.
835          else
836            md.modeUsage = null;
837        }
838    
839        /**
840         * Parse an unwrap action.
841         * @param attributes The unwrap element attributes.
842         */
843        private void parseUnwrap(Attributes attributes) {
844          // this makes sense only on elements
845          // if we have element actions, create the mode usage and add
846          // an unwrap action with this mode usage.
847          if (md.actions != null) {
848            md.modeUsage = getModeUsage(attributes);
849            md.actions.setResultAction(new UnwrapAction(md.modeUsage));
850          }
851          // no element actions, no modeUsage.
852          else
853            md.modeUsage = null;
854        }
855    
856        /**
857         * Parse an attachPlaceholder action.
858         * @param attributes The attachPlaceholder element attributes.
859         */
860        private void parseAttachPlaceholder(Attributes attributes) {
861          // this makes sense only on elements
862          // if we have element actions, create the mode usage and add
863          // an attachPlaceholder action with this mode usage.
864          if (md.actions != null) {
865            md.modeUsage = getModeUsage(attributes);
866            md.actions.setResultAction(new AttachPlaceholderAction(md.modeUsage));
867          }
868          // no element actions, no modeUsage.
869          else
870            md.modeUsage = null;
871        }
872        
873        /**
874         * Parse an allow action.
875         * 
876         * @param attributes The allow element attributes.
877         */
878        private void parseAllow(Attributes attributes) {
879          // if we match on elements, create the mode usage and add an allow action.
880          if (md.actions != null) {
881            md.modeUsage = getModeUsage(attributes);
882            md.actions.addNoResultAction(new AllowAction(md.modeUsage));
883          }
884          // no actions, no mode usage.
885          else
886            md.modeUsage = null;
887          
888          // no need to add anything in the attribute actions, allow
889          // is equivalent with a noop action.
890        }
891    
892        /**
893         * Parse a reject action.
894         * @param attributes The reject element attributes.
895         */
896        private void parseReject(Attributes attributes) {
897          // if element actions, get the mode usage and add a reject 
898          // action with this mode usage.
899          if (md.actions != null) {
900            md.modeUsage = getModeUsage(attributes);
901            md.actions.addNoResultAction(new RejectAction(md.modeUsage));
902          }
903          // no actions, no mode usage
904          else
905            md.modeUsage = null;
906          
907          // if attribute actions, set the reject flag.
908          if (md.attributeActions != null)
909            md.attributeActions.setReject(true);
910        }
911    
912        /**
913         * Parse a cancel nested actions action.
914         * 
915         * @param attributes The cancelNestedActions element attributes.
916         */
917        private void parseCancelNestedActions(Attributes attributes) {
918          // if we match on elements, create the mode usage and add a cancelNestedActions action.
919          if (md.actions != null) {
920            md.modeUsage = getModeUsage(attributes);
921            md.actions.setCancelNestedActions(true);
922          } // no actions, no mode usage.
923          else {
924            md.modeUsage = null;
925          }
926          
927          // if attribute actions.
928          if (md.attributeActions != null) {
929            md.attributeActions.setCancelNestedActions(true);        
930          }
931        }
932    
933        /**
934         * Parse context dependent mode usages.
935         * @param attributes The context element attributes.
936         * @throws SAXException
937         */
938        private void parseContext(Attributes attributes) throws SAXException {
939          // TODO: check this in the NVDL spec.
940          // context not allowed within anyNamespace.???
941          // IT SEEMS IT IS ALLOWED IN NVDL...
942          //if (md.anyNamespace) {
943          //  error("context_any_namespace");
944          //  return;
945          //}
946          // Get the mode to be used further on this context.
947          Mode mode = getUseMode(attributes);
948          md.lastMode = mode;
949          try {
950            // parse the path value into a list of Path objects
951            // and add them to the mode usage
952            Vector paths = Path.parse(attributes.getValue("", "path"));
953            // XXX warning if modeUsage is null
954            if (md.modeUsage != null) {
955              for (int i = 0, len = paths.size(); i < len; i++) {
956                Path path = (Path)paths.elementAt(i);
957                if (!md.modeUsage.addContext(path.isRoot(), path.getNames(), mode))
958                  error("duplicate_path", path.toString());
959              }
960            }
961          }
962          catch (Path.ParseException e) {
963            error(e.getMessageKey());
964          }
965        }
966    
967        /**
968         * Get the URI specified by a schema attribute and if we have a 
969         * relative location resolve that against the base URI taking into
970         * account also eventual xml:base attributes.
971         * @param attributes The validate element attributes.
972         * @return A resolved URI as string.
973         * @throws SAXException If the schema contains a fragment id.
974         */
975        private String getSchema(Attributes attributes) throws SAXException {
976          String schemaUri = attributes.getValue("", "schema");
977          if ("".equals(schemaUri)) {
978            error("no_schema");
979            schemaUri = null;
980          }
981          if (schemaUri != null) {
982            if (Uri.hasFragmentId(schemaUri))
983              error("schema_fragment_id");
984            return Uri.resolve(xmlBaseHandler.getBaseUri(),
985                               Uri.escapeDisallowedChars(schemaUri));
986          }
987          return null;
988        }
989    
990        /**
991         * Get the schema type
992         * @param attributes The attributes
993         * @return The value of the schemaType attribute.
994         */
995        private String getSchemaType(Attributes attributes) {
996          return attributes.getValue("", "schemaType");
997        }
998    
999        /**
1000         * Get an ElementsOrAttributes instance depending on the match attribute value.
1001         * @param value The match attribute value.
1002         * @param defaultValue The default value if value is null.
1003         * @return an ElementsOrAttributes constant.
1004         */
1005        private ElementsOrAttributes toElementsOrAttributes(String value, ElementsOrAttributes defaultValue) {
1006          if (value == null)
1007            return defaultValue;
1008          ElementsOrAttributes eoa = ElementsOrAttributes.NEITHER;
1009          if (value.indexOf("elements") >= 0)
1010            eoa = eoa.addElements();
1011          if (value.indexOf("attributes") >= 0)
1012            eoa = eoa.addAttributes();
1013          return eoa;
1014        }
1015    
1016        /**
1017         * Creates a mode usage that matches current mode and uses further 
1018         * the mode specified by the useMode attribute.
1019         * @param attributes The action element attributes.
1020         * @return A mode usage from currentMode to the mode specified 
1021         * by the useMode attribute.
1022         */
1023        private ModeUsage getModeUsage(Attributes attributes) {
1024          md.lastMode = getUseMode(attributes);
1025          return new ModeUsage(md.lastMode, md.currentMode);
1026        }
1027    
1028        /**
1029         * Get the Mode for the useMode attribute.
1030         * @param attributes the attributes
1031         * @return the mode with the useMode name or the special Mode.CURRENT mode that
1032         * will be resolved to the current mode in a Mode usage.
1033         */
1034        private Mode getUseMode(Attributes attributes) {
1035          Mode mode = getModeAttribute(attributes, "useMode");
1036          if (mode == null)
1037            return new Mode(defaultBaseMode);
1038          mode.noteUsed(locator);
1039          return mode;
1040        }
1041    
1042        /**
1043         * Get the namespace from the ns attribute.
1044         * Also check that the namespace is an absolute URI and report an 
1045         * error otherwise.
1046         * @param attributes The list of attributes of the namespace element
1047         * @return The ns value.
1048         * @throws SAXException
1049         */
1050        private String getNs(Attributes attributes) throws SAXException {
1051          String ns = attributes.getValue("", "ns");
1052          if (ns != null && !Uri.isAbsolute(ns) && !ns.equals(""))
1053            error("ns_absolute");
1054          return ns;
1055        }
1056    
1057        /**
1058         * Report a no arguments error from a key.
1059         * @param key The error key.
1060         * @throws SAXException
1061         */
1062        void error(String key) throws SAXException {
1063          hadError = true;
1064          if (eh == null)
1065            return;
1066          eh.error(new SAXParseException(localizer.message(key), locator));
1067        }
1068    
1069        /**
1070         * Report an one argument error.
1071         * @param key The error key.
1072         * @param arg The argument.
1073         * @throws SAXException
1074         */
1075        void error(String key, String arg) throws SAXException {
1076          hadError = true;
1077          if (eh == null)
1078            return;
1079          eh.error(new SAXParseException(localizer.message(key, arg), locator));
1080        }
1081    
1082        /**
1083         * Report an one argument error with location.
1084         * @param key The error key.
1085         * @param arg The argument.
1086         * @param locator The location.
1087         * @throws SAXException
1088         */
1089        void error(String key, String arg, Locator locator) throws SAXException {
1090          hadError = true;
1091          if (eh == null)
1092            return;
1093          eh.error(new SAXParseException(localizer.message(key, arg), locator));
1094        }
1095    
1096        /**
1097         * Report a two arguments error.
1098         * @param key The error key. 
1099         * @param arg1 The first argument.
1100         * @param arg2 The second argument.
1101         * @throws SAXException
1102         */
1103        void error(String key, String arg1, String arg2) throws SAXException {
1104          hadError = true;
1105          if (eh == null)
1106            return;
1107          eh.error(new SAXParseException(localizer.message(key, arg1, arg2), locator));
1108        }
1109    
1110      }
1111      /**
1112       * Creates a NVDL schema implementation.
1113       * Initializes the attributesSchema flag and the built in modes.
1114       * @param properties Properties.
1115       */
1116      SchemaImpl(PropertyMap properties) {
1117        super(properties);
1118        this.attributesSchema = properties.contains(NvdlProperty.ATTRIBUTES_SCHEMA);
1119        makeBuiltinMode("#allow", AllowAction.class);
1120        makeBuiltinMode("#attach", AttachAction.class);
1121        makeBuiltinMode("#unwrap", UnwrapAction.class);
1122        defaultBaseMode = makeBuiltinMode("#reject", RejectAction.class);
1123      }
1124    
1125      /**
1126       * Makes a built in mode.
1127       * @param name The mode name.
1128       * @param cls The action class.
1129       * @return A Mode object.
1130       */
1131      private Mode makeBuiltinMode(String name, Class cls) {
1132        // lookup/create a mode with the given name.
1133        Mode mode = lookupCreateMode(name);
1134        // Init the element action set for this mode.
1135        ActionSet actions = new ActionSet();
1136        // from the current mode we will use further the built in mode.
1137        ModeUsage modeUsage = new ModeUsage(Mode.CURRENT, mode);
1138        // Add the action corresponding to the built in mode.
1139        if (cls == AttachAction.class)
1140          actions.setResultAction(new AttachAction(modeUsage));
1141        else if (cls == AllowAction.class)
1142          actions.addNoResultAction(new AllowAction(modeUsage));
1143        else if (cls == UnwrapAction.class)
1144          actions.setResultAction(new UnwrapAction(modeUsage));
1145        else
1146          actions.addNoResultAction(new RejectAction(modeUsage));
1147        // set the actions on any namespace.
1148        mode.bindElement(NamespaceSpecification.ANY_NAMESPACE, NamespaceSpecification.DEFAULT_WILDCARD, actions);
1149        // the mode is not defined in the script explicitelly
1150        mode.noteDefined(null);
1151        // creates attribute actions
1152        AttributeActionSet attributeActions = new AttributeActionSet();
1153        // if we have a schema for attributes then in the built in modes
1154        // we reject attributes by default
1155        // otherwise we attach attributes by default in the built in modes
1156        if (attributesSchema)
1157          attributeActions.setReject(true);
1158        else      
1159          attributeActions.setAttach(true);
1160        // set the attribute actions on any namespace
1161        mode.bindAttribute(NamespaceSpecification.ANY_NAMESPACE, NamespaceSpecification.DEFAULT_WILDCARD, attributeActions);
1162        return mode;
1163      }
1164    
1165      /**
1166       * Installs the schema handler on the reader.
1167       * 
1168       * @param in The reader.
1169       * @param sr The schema receiver.
1170       * @return The installed handler that implements also SchemaFuture.
1171       */
1172      SchemaFuture installHandlers(XMLReader in, SchemaReceiverImpl sr) {
1173        Handler h = new Handler(sr);
1174        in.setContentHandler(h);
1175        return h;
1176      }
1177    
1178      /**
1179       * Creates a Validator for validating XML documents against this 
1180       * NVDL script.
1181       * @param properties properties.
1182       */
1183      public Validator createValidator(PropertyMap properties) {
1184        return new ValidatorImpl(startMode, triggers, properties);
1185      }
1186    
1187      /**
1188       * Get the mode specified by an attribute from no namespace.
1189       * 
1190       * @param attributes The attributes.
1191       * @param localName The attribute name.
1192       * @return The mode refered by the licanName attribute.
1193       */
1194      private Mode getModeAttribute(Attributes attributes, String localName) {
1195        return lookupCreateMode(attributes.getValue("", localName));
1196      }
1197    
1198      /**
1199       * Gets a mode with the given name from the mode map.
1200       * If not present then it creates a new mode extending the default base mode.
1201       * 
1202       * @param name The mode to look for or create if it does not exist.
1203       * @return Always a not null mode.
1204       */
1205      private Mode lookupCreateMode(String name) {
1206        if (name == null)
1207          return null;
1208        name = name.trim();
1209        Mode mode = (Mode)modeMap.get(name);
1210        if (mode == null) {
1211          mode = new Mode(name, defaultBaseMode);
1212          modeMap.put(name, mode);
1213        }
1214        return mode;
1215      }
1216    
1217    }