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 }