1 package com.thaiopensource.validate.nrl; 2 3 import com.thaiopensource.util.Localizer; 4 import com.thaiopensource.util.PropertyMap; 5 import com.thaiopensource.util.Uri; 6 import com.thaiopensource.util.PropertyMapBuilder; 7 import com.thaiopensource.util.PropertyId; 8 import com.thaiopensource.validate.IncorrectSchemaException; 9 import com.thaiopensource.validate.Schema; 10 import com.thaiopensource.validate.ValidateProperty; 11 import com.thaiopensource.validate.Validator; 12 import com.thaiopensource.validate.Option; 13 import com.thaiopensource.validate.OptionArgumentException; 14 import com.thaiopensource.validate.OptionArgumentPresenceException; 15 import com.thaiopensource.validate.AbstractSchema; 16 import com.thaiopensource.validate.SchemaReader; 17 import com.thaiopensource.validate.auto.SchemaFuture; 18 import com.thaiopensource.xml.sax.XmlBaseHandler; 19 import com.thaiopensource.xml.sax.DelegatingContentHandler; 20 import com.thaiopensource.xml.sax.CountingErrorHandler; 21 import com.thaiopensource.xml.util.WellKnownNamespaces; 22 import org.xml.sax.Attributes; 23 import org.xml.sax.ErrorHandler; 24 import org.xml.sax.InputSource; 25 import org.xml.sax.Locator; 26 import org.xml.sax.SAXException; 27 import org.xml.sax.SAXParseException; 28 import org.xml.sax.XMLReader; 29 import org.xml.sax.helpers.LocatorImpl; 30 31 import java.io.IOException; 32 import java.util.Enumeration; 33 import java.util.Hashtable; 34 import java.util.Vector; 35 36 class SchemaImpl extends AbstractSchema { 37 static private final String IMPLICIT_MODE_NAME = "#implicit"; 38 static private final String WRAPPER_MODE_NAME = "#wrapper"; 39 static final String NRL_URI = SchemaReader.BASE_URI + "nrl"; 40 private final Hashtable modeMap = new Hashtable(); 41 private Mode startMode; 42 private final Mode defaultBaseMode; 43 private final boolean attributesSchema; 44 45 static private final class WrappedIOException extends RuntimeException { 46 private final IOException exception; 47 48 private WrappedIOException(IOException exception) { 49 this.exception = exception; 50 } 51 52 private IOException getException() { 53 return exception; 54 } 55 } 56 57 static private class MustSupportOption { 58 private final String name; 59 private final PropertyId pid; 60 private final Locator locator; 61 62 MustSupportOption(String name, PropertyId pid, Locator locator) { 63 this.name = name; 64 this.pid = pid; 65 this.locator = locator; 66 } 67 } 68 69 private class Handler extends DelegatingContentHandler implements SchemaFuture { 70 private final SchemaReceiverImpl sr; 71 private boolean hadError = false; 72 private final ErrorHandler eh; 73 private final CountingErrorHandler ceh; 74 private final Localizer localizer = new Localizer(SchemaImpl.class); 75 private Locator locator; 76 private final XmlBaseHandler xmlBaseHandler = new XmlBaseHandler(); 77 private int foreignDepth = 0; 78 private Mode currentMode = null; 79 private String defaultSchemaType; 80 private Validator validator; 81 private ElementsOrAttributes match; 82 private ActionSet actions; 83 private AttributeActionSet attributeActions; 84 private String schemaUri; 85 private String schemaType; 86 private PropertyMapBuilder options; 87 private final Vector mustSupportOptions = new Vector(); 88 private ModeUsage modeUsage; 89 private boolean anyNamespace; 90 91 Handler(SchemaReceiverImpl sr) { 92 this.sr = sr; 93 this.eh = ValidateProperty.ERROR_HANDLER.get(sr.getProperties()); 94 this.ceh = new CountingErrorHandler(this.eh); 95 } 96 97 public void setDocumentLocator(Locator locator) { 98 xmlBaseHandler.setLocator(locator); 99 this.locator = locator; 100 } 101 102 public void startDocument() throws SAXException { 103 try { 104 PropertyMapBuilder builder = new PropertyMapBuilder(sr.getProperties()); 105 ValidateProperty.ERROR_HANDLER.put(builder, ceh); 106 validator = sr.getNrlSchema().createValidator(builder.toPropertyMap()); 107 } 108 catch (IOException e) { 109 throw new WrappedIOException(e); 110 } 111 catch (IncorrectSchemaException e) { 112 throw new RuntimeException("internal error in RNG schema for NRL"); 113 } 114 setDelegate(validator.getContentHandler()); 115 if (locator != null) 116 super.setDocumentLocator(locator); 117 super.startDocument(); 118 } 119 120 public Schema getSchema() throws IncorrectSchemaException, SAXException { 121 if (validator == null || ceh.getHadErrorOrFatalError()) 122 throw new IncorrectSchemaException(); 123 Hashset openModes = new Hashset(); 124 Hashset checkedModes = new Hashset(); 125 for (Enumeration enum = modeMap.keys(); enum.hasMoreElements();) { 126 String modeName = (String)enum.nextElement(); 127 Mode mode = (Mode)modeMap.get(modeName); 128 if (!mode.isDefined()) 129 error("undefined_mode", modeName, mode.getWhereUsed()); 130 for (Mode tem = mode; tem != null; tem = tem.getBaseMode()) { 131 if (checkedModes.contains(tem)) 132 break; 133 if (openModes.contains(tem)) { 134 error("mode_cycle", tem.getName(), tem.getWhereDefined()); 135 break; 136 } 137 openModes.add(tem); 138 } 139 checkedModes.addAll(openModes); 140 openModes.clear(); 141 } 142 if (hadError) 143 throw new IncorrectSchemaException(); 144 return SchemaImpl.this; 145 } 146 147 public RuntimeException unwrapException(RuntimeException e) throws SAXException, IOException, IncorrectSchemaException { 148 if (e instanceof WrappedIOException) 149 throw ((WrappedIOException)e).getException(); 150 return e; 151 } 152 153 public void startElement(String uri, String localName, 154 String qName, Attributes attributes) 155 throws SAXException { 156 super.startElement(uri, localName, qName, attributes); 157 xmlBaseHandler.startElement(); 158 String xmlBase = attributes.getValue(WellKnownNamespaces.XML, "base"); 159 if (xmlBase != null) 160 xmlBaseHandler.xmlBaseAttribute(xmlBase); 161 if (!NRL_URI.equals(uri) || foreignDepth > 0) { 162 foreignDepth++; 163 return; 164 } 165 if (ceh.getHadErrorOrFatalError()) 166 return; 167 if (localName.equals("rules")) 168 parseRules(attributes); 169 else if (localName.equals("mode")) 170 parseMode(attributes); 171 else if (localName.equals("namespace")) 172 parseNamespace(attributes); 173 else if (localName.equals("anyNamespace")) 174 parseAnyNamespace(attributes); 175 else if (localName.equals("validate")) 176 parseValidate(attributes); 177 else if (localName.equals("reject")) 178 parseReject(attributes); 179 else if (localName.equals("attach")) 180 parseAttach(attributes); 181 else if (localName.equals("unwrap")) 182 parseUnwrap(attributes); 183 else if (localName.equals("allow")) 184 parseAllow(attributes); 185 else if (localName.equals("context")) 186 parseContext(attributes); 187 else if (localName.equals("option")) 188 parseOption(attributes); 189 else 190 throw new RuntimeException("unexpected element \"" + localName + "\""); 191 } 192 193 public void endElement(String namespaceURI, String localName, 194 String qName) 195 throws SAXException { 196 super.endElement(namespaceURI, localName, qName); 197 xmlBaseHandler.endElement(); 198 if (foreignDepth > 0) { 199 foreignDepth--; 200 return; 201 } 202 if (ceh.getHadErrorOrFatalError()) 203 return; 204 if (localName.equals("validate")) 205 finishValidate(); 206 } 207 208 private void parseRules(Attributes attributes) { 209 startMode = getModeAttribute(attributes, "startMode"); 210 if (startMode == null) { 211 startMode = lookupCreateMode(IMPLICIT_MODE_NAME); 212 currentMode = startMode; 213 startMode.noteDefined(null); 214 } 215 startMode.noteUsed(locator); 216 if (attributesSchema) { 217 Mode wrapper = lookupCreateMode(WRAPPER_MODE_NAME); 218 ActionSet actions = new ActionSet(); 219 actions.addNoResultAction(new AllowAction(new ModeUsage(startMode, startMode))); 220 wrapper.bindElement(Mode.ANY_NAMESPACE, actions); 221 wrapper.noteDefined(null); 222 startMode = wrapper; 223 } 224 defaultSchemaType = getSchemaType(attributes); 225 } 226 227 private void parseMode(Attributes attributes) throws SAXException { 228 currentMode = getModeAttribute(attributes, "name"); 229 if (currentMode.isDefined()) { 230 error("duplicate_mode", currentMode.getName()); 231 error("first_mode", currentMode.getName(), currentMode.getWhereDefined()); 232 } 233 else { 234 Mode base = getModeAttribute(attributes, "extends"); 235 if (base != null) 236 currentMode.setBaseMode(base); 237 currentMode.noteDefined(locator); 238 } 239 } 240 241 private void parseNamespace(Attributes attributes) throws SAXException { 242 anyNamespace = false; 243 parseRule(getNs(attributes), attributes); 244 } 245 246 private void parseAnyNamespace(Attributes attributes) throws SAXException { 247 anyNamespace = true; 248 parseRule(Mode.ANY_NAMESPACE, attributes); 249 } 250 251 private void parseRule(String ns, Attributes attributes) throws SAXException { 252 match = toElementsOrAttributes(attributes.getValue("", "match"), 253 ElementsOrAttributes.ELEMENTS); 254 if (match.containsAttributes()) { 255 attributeActions = new AttributeActionSet(); 256 if (!currentMode.bindAttribute(ns, attributeActions)) { 257 if (ns.equals(Mode.ANY_NAMESPACE)) 258 error("duplicate_attribute_action_any_namespace"); 259 else 260 error("duplicate_attribute_action", ns); 261 } 262 } 263 if (match.containsElements()) { 264 actions = new ActionSet(); 265 if (!currentMode.bindElement(ns, actions)) { 266 if (ns.equals(Mode.ANY_NAMESPACE)) 267 error("duplicate_element_action_any_namespace"); 268 else 269 error("duplicate_element_action", ns); 270 } 271 } 272 else 273 actions = null; 274 } 275 276 private void parseValidate(Attributes attributes) throws SAXException { 277 schemaUri = getSchema(attributes); 278 schemaType = getSchemaType(attributes); 279 if (schemaType == null) 280 schemaType = defaultSchemaType; 281 if (actions != null) 282 modeUsage = getModeUsage(attributes); 283 else 284 modeUsage = null; 285 options = new PropertyMapBuilder(); 286 mustSupportOptions.clear(); 287 } 288 289 private void finishValidate() throws SAXException { 290 try { 291 if (attributeActions != null) { 292 Schema schema = createSubSchema(true); 293 attributeActions.addSchema(schema); 294 } 295 if (actions != null) { 296 Schema schema = createSubSchema(false); 297 actions.addNoResultAction(new ValidateAction(modeUsage, schema)); 298 } 299 } 300 catch (IncorrectSchemaException e) { 301 hadError = true; 302 } 303 catch (IOException e) { 304 throw new WrappedIOException(e); 305 } 306 } 307 308 private Schema createSubSchema(boolean isAttributesSchema) throws IOException, IncorrectSchemaException, SAXException { 309 PropertyMap requestedProperties = options.toPropertyMap(); 310 Schema schema = sr.createChildSchema(new InputSource(schemaUri), 311 schemaType, 312 requestedProperties, 313 isAttributesSchema); 314 PropertyMap actualProperties = schema.getProperties(); 315 for (Enumeration enum = mustSupportOptions.elements(); enum.hasMoreElements();) { 316 MustSupportOption mso = (MustSupportOption)enum.nextElement(); 317 Object actualValue = actualProperties.get(mso.pid); 318 if (actualValue == null) 319 error("unsupported_option", mso.name, mso.locator); 320 else if (!actualValue.equals(requestedProperties.get(mso.pid))) 321 error("unsupported_option_arg", mso.name, mso.locator); 322 } 323 return schema; 324 } 325 326 private void parseOption(Attributes attributes) throws SAXException { 327 boolean mustSupport; 328 String mustSupportValue = attributes.getValue("", "mustSupport"); 329 if (mustSupportValue != null) { 330 mustSupportValue = mustSupportValue.trim(); 331 mustSupport = mustSupportValue.equals("1") || mustSupportValue.equals("true"); 332 } 333 else 334 mustSupport = false; 335 String name = Uri.resolve(NRL_URI, attributes.getValue("", "name")); 336 Option option = sr.getOption(name); 337 if (option == null) { 338 if (mustSupport) 339 error("unknown_option", name); 340 } 341 else { 342 String arg = attributes.getValue("", "arg"); 343 try { 344 PropertyId pid = option.getPropertyId(); 345 Object value = option.valueOf(arg); 346 Object oldValue = options.get(pid); 347 if (oldValue != null) { 348 value = option.combine(new Object[]{oldValue, value}); 349 if (value == null) 350 error("duplicate_option", name); 351 else 352 options.put(pid, value); 353 } 354 else { 355 options.put(pid, value); 356 mustSupportOptions.addElement(new MustSupportOption(name, pid, 357 locator == null 358 ? null 359 : new LocatorImpl(locator))); 360 } 361 } 362 catch (OptionArgumentPresenceException e) { 363 error(arg == null ? "option_requires_argument" : "option_unexpected_argument", name); 364 } 365 catch (OptionArgumentException e) { 366 if (arg == null) 367 error("option_requires_argument", name); 368 else 369 error("option_bad_argument", name, arg); 370 } 371 } 372 } 373 374 private void parseAttach(Attributes attributes) { 375 if (attributeActions != null) 376 attributeActions.setAttach(true); 377 if (actions != null) { 378 modeUsage = getModeUsage(attributes); 379 actions.setResultAction(new AttachAction(modeUsage)); 380 } 381 else 382 modeUsage = null; 383 } 384 385 private void parseUnwrap(Attributes attributes) { 386 if (actions != null) { 387 modeUsage = getModeUsage(attributes); 388 actions.setResultAction(new UnwrapAction(modeUsage)); 389 } 390 else 391 modeUsage = null; 392 } 393 394 private void parseAllow(Attributes attributes) { 395 if (actions != null) { 396 modeUsage = getModeUsage(attributes); 397 actions.addNoResultAction(new AllowAction(modeUsage)); 398 } 399 else 400 modeUsage = null; 401 } 402 403 private void parseReject(Attributes attributes) { 404 if (actions != null) { 405 modeUsage = getModeUsage(attributes); 406 actions.addNoResultAction(new RejectAction(modeUsage)); 407 } 408 else 409 modeUsage = null; 410 if (attributeActions != null) 411 attributeActions.setReject(true); 412 } 413 414 private void parseContext(Attributes attributes) throws SAXException { 415 if (anyNamespace) { 416 error("context_any_namespace"); 417 return; 418 } 419 Mode mode = getUseMode(attributes); 420 try { 421 Vector paths = Path.parse(attributes.getValue("", "path")); 422 if (modeUsage != null) { 424 for (int i = 0, len = paths.size(); i < len; i++) { 425 Path path = (Path)paths.elementAt(i); 426 if (!modeUsage.addContext(path.isRoot(), path.getNames(), mode)) 427 error("duplicate_path", path.toString()); 428 } 429 } 430 } 431 catch (Path.ParseException e) { 432 error(e.getMessageKey()); 433 } 434 } 435 436 private String getSchema(Attributes attributes) throws SAXException { 437 String schemaUri = attributes.getValue("", "schema"); 438 if (Uri.hasFragmentId(schemaUri)) 439 error("schema_fragment_id"); 440 return Uri.resolve(xmlBaseHandler.getBaseUri(), 441 Uri.escapeDisallowedChars(schemaUri)); 442 } 443 444 private String getSchemaType(Attributes attributes) { 445 return attributes.getValue("", "schemaType"); 446 } 447 448 private ElementsOrAttributes toElementsOrAttributes(String value, ElementsOrAttributes defaultValue) { 449 if (value == null) 450 return defaultValue; 451 ElementsOrAttributes eoa = ElementsOrAttributes.NEITHER; 452 if (value.indexOf("elements") >= 0) 453 eoa = eoa.addElements(); 454 if (value.indexOf("attributes") >= 0) 455 eoa = eoa.addAttributes(); 456 return eoa; 457 } 458 459 private ModeUsage getModeUsage(Attributes attributes) { 460 return new ModeUsage(getUseMode(attributes), currentMode); 461 } 462 463 private Mode getUseMode(Attributes attributes) { 464 Mode mode = getModeAttribute(attributes, "useMode"); 465 if (mode == null) 466 return Mode.CURRENT; 467 mode.noteUsed(locator); 468 return mode; 469 } 470 471 private String getNs(Attributes attributes) throws SAXException { 472 String ns = attributes.getValue("", "ns"); 473 if (ns != null && !Uri.isAbsolute(ns) && !ns.equals("")) 474 error("ns_absolute"); 475 return ns; 476 } 477 478 void error(String key) throws SAXException { 479 hadError = true; 480 if (eh == null) 481 return; 482 eh.error(new SAXParseException(localizer.message(key), locator)); 483 } 484 485 void error(String key, String arg) throws SAXException { 486 hadError = true; 487 if (eh == null) 488 return; 489 eh.error(new SAXParseException(localizer.message(key, arg), locator)); 490 } 491 492 void error(String key, String arg, Locator locator) throws SAXException { 493 hadError = true; 494 if (eh == null) 495 return; 496 eh.error(new SAXParseException(localizer.message(key, arg), locator)); 497 } 498 499 void error(String key, String arg1, String arg2) throws SAXException { 500 hadError = true; 501 if (eh == null) 502 return; 503 eh.error(new SAXParseException(localizer.message(key, arg1, arg2), locator)); 504 } 505 506 } 507 508 SchemaImpl(PropertyMap properties) { 509 super(properties); 510 this.attributesSchema = properties.contains(NrlProperty.ATTRIBUTES_SCHEMA); 511 makeBuiltinMode("#allow", AllowAction.class); 512 makeBuiltinMode("#attach", AttachAction.class); 513 makeBuiltinMode("#unwrap", UnwrapAction.class); 514 defaultBaseMode = makeBuiltinMode("#reject", RejectAction.class); 515 } 516 517 private Mode makeBuiltinMode(String name, Class cls) { 518 Mode mode = lookupCreateMode(name); 519 ActionSet actions = new ActionSet(); 520 ModeUsage modeUsage = new ModeUsage(Mode.CURRENT, mode); 521 if (cls == AttachAction.class) 522 actions.setResultAction(new AttachAction(modeUsage)); 523 else if (cls == AllowAction.class) 524 actions.addNoResultAction(new AllowAction(modeUsage)); 525 else if (cls == UnwrapAction.class) 526 actions.setResultAction(new UnwrapAction(modeUsage)); 527 else 528 actions.addNoResultAction(new RejectAction(modeUsage)); 529 mode.bindElement(Mode.ANY_NAMESPACE, actions); 530 mode.noteDefined(null); 531 AttributeActionSet attributeActions = new AttributeActionSet(); 532 if (attributesSchema) 533 attributeActions.setReject(true); 534 else 535 attributeActions.setAttach(true); 536 mode.bindAttribute(Mode.ANY_NAMESPACE, attributeActions); 537 return mode; 538 } 539 540 SchemaFuture installHandlers(XMLReader in, SchemaReceiverImpl sr) { 541 Handler h = new Handler(sr); 542 in.setContentHandler(h); 543 return h; 544 } 545 546 public Validator createValidator(PropertyMap properties) { 547 return new ValidatorImpl(startMode, properties); 548 } 549 550 private Mode getModeAttribute(Attributes attributes, String localName) { 551 return lookupCreateMode(attributes.getValue("", localName)); 552 } 553 554 private Mode lookupCreateMode(String name) { 555 if (name == null) 556 return null; 557 name = name.trim(); 558 Mode mode = (Mode)modeMap.get(name); 559 if (mode == null) { 560 mode = new Mode(name, defaultBaseMode); 561 modeMap.put(name, mode); 562 } 563 return mode; 564 } 565 566 } 567 | Popular Tags |