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 }