1 /* 2 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. 3 * 4 * This software is open source. 5 * See the bottom of this file for the licence. 6 */ 7 8 package org.dom4j.datatype; 9 10 import com.sun.msv.datatype.xsd.DatatypeFactory; 11 import com.sun.msv.datatype.xsd.TypeIncubator; 12 import com.sun.msv.datatype.xsd.XSDatatype; 13 14 import java.util.HashMap; 15 import java.util.Iterator; 16 import java.util.Map; 17 18 import org.dom4j.Attribute; 19 import org.dom4j.Document; 20 import org.dom4j.DocumentFactory; 21 import org.dom4j.Element; 22 import org.dom4j.Namespace; 23 import org.dom4j.QName; 24 import org.dom4j.io.SAXReader; 25 import org.dom4j.util.AttributeHelper; 26 27 import org.relaxng.datatype.DatatypeException; 28 import org.relaxng.datatype.ValidationContext; 29 30 import org.xml.sax.EntityResolver; 31 import org.xml.sax.InputSource; 32 33 /** 34 * <p> 35 * <code>SchemaParser</code> reads an XML Schema Document. 36 * </p> 37 * 38 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a> 39 * @author Yuxin Ruan 40 * @version $Revision: 1.19 $ 41 */ 42 public class SchemaParser { 43 private static final Namespace XSD_NAMESPACE = Namespace.get("xsd", 44 "http://www.w3.org/2001/XMLSchema"); 45 46 // Use QNames for the elements 47 private static final QName XSD_ELEMENT = QName 48 .get("element", XSD_NAMESPACE); 49 50 private static final QName XSD_ATTRIBUTE = QName.get("attribute", 51 XSD_NAMESPACE); 52 53 private static final QName XSD_SIMPLETYPE = QName.get("simpleType", 54 XSD_NAMESPACE); 55 56 private static final QName XSD_COMPLEXTYPE = QName.get("complexType", 57 XSD_NAMESPACE); 58 59 private static final QName XSD_RESTRICTION = QName.get("restriction", 60 XSD_NAMESPACE); 61 62 private static final QName XSD_SEQUENCE = QName.get("sequence", 63 XSD_NAMESPACE); 64 65 private static final QName XSD_CHOICE = QName.get("choice", XSD_NAMESPACE); 66 67 private static final QName XSD_ALL = QName.get("all", XSD_NAMESPACE); 68 69 private static final QName XSD_INCLUDE = QName 70 .get("include", XSD_NAMESPACE); 71 72 /** Document factory used to register Element specific factories */ 73 private DatatypeDocumentFactory documentFactory; 74 75 /** 76 * Cache of <code>XSDatatype</code> instances loaded or created during 77 * this build 78 */ 79 private Map dataTypeCache = new HashMap(); 80 81 /** NamedTypeResolver */ 82 private NamedTypeResolver namedTypeResolver; 83 84 /** target namespace */ 85 private Namespace targetNamespace; 86 SchemaParser()87 public SchemaParser() { 88 this(DatatypeDocumentFactory.singleton); 89 } 90 SchemaParser(DatatypeDocumentFactory documentFactory)91 public SchemaParser(DatatypeDocumentFactory documentFactory) { 92 this.documentFactory = documentFactory; 93 this.namedTypeResolver = new NamedTypeResolver(documentFactory); 94 } 95 96 /** 97 * Parses the given schema document 98 * 99 * @param schemaDocument 100 * is the document of the XML Schema 101 */ build(Document schemaDocument)102 public void build(Document schemaDocument) { 103 this.targetNamespace = null; 104 internalBuild(schemaDocument); 105 } 106 build(Document schemaDocument, Namespace namespace)107 public void build(Document schemaDocument, Namespace namespace) { 108 this.targetNamespace = namespace; 109 internalBuild(schemaDocument); 110 } 111 internalBuild(Document schemaDocument)112 private synchronized void internalBuild(Document schemaDocument) { 113 Element root = schemaDocument.getRootElement(); 114 115 if (root != null) { 116 // handle schema includes 117 Iterator includeIter = root.elementIterator(XSD_INCLUDE); 118 119 while (includeIter.hasNext()) { 120 Element includeElement = (Element) includeIter.next(); 121 String inclSchemaInstanceURI = includeElement 122 .attributeValue("schemaLocation"); 123 EntityResolver resolver = schemaDocument.getEntityResolver(); 124 125 try { 126 if (resolver == null) { 127 String msg = "No EntityResolver available"; 128 throw new InvalidSchemaException(msg); 129 } 130 131 InputSource inputSource = resolver.resolveEntity(null, 132 inclSchemaInstanceURI); 133 134 if (inputSource == null) { 135 String msg = "Could not resolve the schema URI: " 136 + inclSchemaInstanceURI; 137 throw new InvalidSchemaException(msg); 138 } 139 140 SAXReader reader = new SAXReader(); 141 Document inclSchemaDocument = reader.read(inputSource); 142 build(inclSchemaDocument); 143 } catch (Exception e) { 144 System.out.println("Failed to load schema: " 145 + inclSchemaInstanceURI); 146 System.out.println("Caught: " + e); 147 e.printStackTrace(); 148 throw new InvalidSchemaException("Failed to load schema: " 149 + inclSchemaInstanceURI); 150 } 151 } 152 153 // handle elements 154 Iterator iter = root.elementIterator(XSD_ELEMENT); 155 156 while (iter.hasNext()) { 157 onDatatypeElement((Element) iter.next(), documentFactory); 158 } 159 160 // handle named simple types 161 iter = root.elementIterator(XSD_SIMPLETYPE); 162 163 while (iter.hasNext()) { 164 onNamedSchemaSimpleType((Element) iter.next()); 165 } 166 167 // hanlde named complex types 168 iter = root.elementIterator(XSD_COMPLEXTYPE); 169 170 while (iter.hasNext()) { 171 onNamedSchemaComplexType((Element) iter.next()); 172 } 173 174 namedTypeResolver.resolveNamedTypes(); 175 } 176 } 177 178 // Implementation methods 179 // ------------------------------------------------------------------------- 180 181 /** 182 * processes an XML Schema <element> tag 183 * 184 * @param xsdElement 185 * DOCUMENT ME! 186 * @param parentFactory 187 * DOCUMENT ME! 188 */ onDatatypeElement(Element xsdElement, DocumentFactory parentFactory)189 private void onDatatypeElement(Element xsdElement, 190 DocumentFactory parentFactory) { 191 String name = xsdElement.attributeValue("name"); 192 String type = xsdElement.attributeValue("type"); 193 QName qname = getQName(name); 194 195 DatatypeElementFactory factory = getDatatypeElementFactory(qname); 196 197 if (type != null) { 198 // register type with this element name 199 XSDatatype dataType = getTypeByName(type); 200 201 if (dataType != null) { 202 factory.setChildElementXSDatatype(qname, dataType); 203 } else { 204 QName typeQName = getQName(type); 205 namedTypeResolver.registerTypedElement(xsdElement, typeQName, 206 parentFactory); 207 } 208 209 return; 210 } 211 212 // handle element types derrived from simpleTypes 213 Element xsdSimpleType = xsdElement.element(XSD_SIMPLETYPE); 214 215 if (xsdSimpleType != null) { 216 XSDatatype dataType = loadXSDatatypeFromSimpleType(xsdSimpleType); 217 218 if (dataType != null) { 219 factory.setChildElementXSDatatype(qname, dataType); 220 } 221 } 222 223 Element schemaComplexType = xsdElement.element(XSD_COMPLEXTYPE); 224 225 if (schemaComplexType != null) { 226 onSchemaComplexType(schemaComplexType, factory); 227 } 228 229 Iterator iter = xsdElement.elementIterator(XSD_ATTRIBUTE); 230 231 if (iter.hasNext()) { 232 do { 233 onDatatypeAttribute(xsdElement, factory, (Element) iter 234 .next()); 235 } while (iter.hasNext()); 236 } 237 } 238 239 /** 240 * processes an named XML Schema <complexTypegt; tag 241 * 242 * @param schemaComplexType 243 * DOCUMENT ME! 244 */ onNamedSchemaComplexType(Element schemaComplexType)245 private void onNamedSchemaComplexType(Element schemaComplexType) { 246 Attribute nameAttr = schemaComplexType.attribute("name"); 247 248 if (nameAttr == null) { 249 return; 250 } 251 252 String name = nameAttr.getText(); 253 QName qname = getQName(name); 254 255 DatatypeElementFactory factory = getDatatypeElementFactory(qname); 256 257 onSchemaComplexType(schemaComplexType, factory); 258 namedTypeResolver.registerComplexType(qname, factory); 259 } 260 261 /** 262 * processes an XML Schema <complexTypegt; tag 263 * 264 * @param schemaComplexType 265 * DOCUMENT ME! 266 * @param elementFactory 267 * DOCUMENT ME! 268 */ onSchemaComplexType(Element schemaComplexType, DatatypeElementFactory elementFactory)269 private void onSchemaComplexType(Element schemaComplexType, 270 DatatypeElementFactory elementFactory) { 271 Iterator iter = schemaComplexType.elementIterator(XSD_ATTRIBUTE); 272 273 while (iter.hasNext()) { 274 Element xsdAttribute = (Element) iter.next(); 275 String name = xsdAttribute.attributeValue("name"); 276 QName qname = getQName(name); 277 278 XSDatatype dataType = dataTypeForXsdAttribute(xsdAttribute); 279 280 if (dataType != null) { 281 // register the XSDatatype for the given Attribute 282 // #### should both these be done? 283 // elementFactory.setChildElementXSDatatype( qname, dataType ); 284 elementFactory.setAttributeXSDatatype(qname, dataType); 285 } 286 } 287 288 // handle sequence definition 289 Element schemaSequence = schemaComplexType.element(XSD_SEQUENCE); 290 291 if (schemaSequence != null) { 292 onChildElements(schemaSequence, elementFactory); 293 } 294 295 // handle choice definition 296 Element schemaChoice = schemaComplexType.element(XSD_CHOICE); 297 298 if (schemaChoice != null) { 299 onChildElements(schemaChoice, elementFactory); 300 } 301 302 // handle all definition 303 Element schemaAll = schemaComplexType.element(XSD_ALL); 304 305 if (schemaAll != null) { 306 onChildElements(schemaAll, elementFactory); 307 } 308 } 309 onChildElements(Element element, DatatypeElementFactory fact)310 private void onChildElements(Element element, DatatypeElementFactory fact) { 311 Iterator iter = element.elementIterator(XSD_ELEMENT); 312 313 while (iter.hasNext()) { 314 Element xsdElement = (Element) iter.next(); 315 onDatatypeElement(xsdElement, fact); 316 } 317 } 318 319 /** 320 * processes an XML Schema <attribute> tag 321 * 322 * @param xsdElement 323 * DOCUMENT ME! 324 * @param elementFactory 325 * DOCUMENT ME! 326 * @param xsdAttribute 327 * DOCUMENT ME! 328 */ onDatatypeAttribute(Element xsdElement, DatatypeElementFactory elementFactory, Element xsdAttribute)329 private void onDatatypeAttribute(Element xsdElement, 330 DatatypeElementFactory elementFactory, Element xsdAttribute) { 331 String name = xsdAttribute.attributeValue("name"); 332 QName qname = getQName(name); 333 XSDatatype dataType = dataTypeForXsdAttribute(xsdAttribute); 334 335 if (dataType != null) { 336 // register the XSDatatype for the given Attribute 337 elementFactory.setAttributeXSDatatype(qname, dataType); 338 } else { 339 String type = xsdAttribute.attributeValue("type"); 340 System.out.println("Warning: Couldn't find XSDatatype for type: " 341 + type + " attribute: " + name); 342 } 343 } 344 345 /** 346 * processes an XML Schema <attribute> tag 347 * 348 * @param xsdAttribute 349 * DOCUMENT ME! 350 * 351 * @return DOCUMENT ME! 352 * 353 * @throws InvalidSchemaException 354 * DOCUMENT ME! 355 */ dataTypeForXsdAttribute(Element xsdAttribute)356 private XSDatatype dataTypeForXsdAttribute(Element xsdAttribute) { 357 String type = xsdAttribute.attributeValue("type"); 358 XSDatatype dataType = null; 359 360 if (type != null) { 361 dataType = getTypeByName(type); 362 } else { 363 // must parse the <simpleType> element 364 Element xsdSimpleType = xsdAttribute.element(XSD_SIMPLETYPE); 365 366 if (xsdSimpleType == null) { 367 String name = xsdAttribute.attributeValue("name"); 368 String msg = "The attribute: " + name 369 + " has no type attribute and does not contain a " 370 + "<simpleType/> element"; 371 throw new InvalidSchemaException(msg); 372 } 373 374 dataType = loadXSDatatypeFromSimpleType(xsdSimpleType); 375 } 376 377 return dataType; 378 } 379 380 /** 381 * processes an named XML Schema <simpleTypegt; tag 382 * 383 * @param schemaSimpleType 384 * DOCUMENT ME! 385 */ onNamedSchemaSimpleType(Element schemaSimpleType)386 private void onNamedSchemaSimpleType(Element schemaSimpleType) { 387 Attribute nameAttr = schemaSimpleType.attribute("name"); 388 389 if (nameAttr == null) { 390 return; 391 } 392 393 String name = nameAttr.getText(); 394 QName qname = getQName(name); 395 XSDatatype datatype = loadXSDatatypeFromSimpleType(schemaSimpleType); 396 namedTypeResolver.registerSimpleType(qname, datatype); 397 } 398 399 /** 400 * Loads a XSDatatype object from a <simpleType> attribute schema 401 * element 402 * 403 * @param xsdSimpleType 404 * DOCUMENT ME! 405 * 406 * @return DOCUMENT ME! 407 */ loadXSDatatypeFromSimpleType(Element xsdSimpleType)408 private XSDatatype loadXSDatatypeFromSimpleType(Element xsdSimpleType) { 409 Element xsdRestriction = xsdSimpleType.element(XSD_RESTRICTION); 410 411 if (xsdRestriction != null) { 412 String base = xsdRestriction.attributeValue("base"); 413 414 if (base != null) { 415 XSDatatype baseType = getTypeByName(base); 416 417 if (baseType == null) { 418 onSchemaError("Invalid base type: " + base 419 + " when trying to build restriction: " 420 + xsdRestriction); 421 } else { 422 return deriveSimpleType(baseType, xsdRestriction); 423 } 424 } else { 425 // simpleType and base are mutually exclusive and you 426 // must have one within a <restriction> tag 427 Element xsdSubType = xsdSimpleType.element(XSD_SIMPLETYPE); 428 429 if (xsdSubType == null) { 430 String msg = "The simpleType element: " + xsdSimpleType 431 + " must contain a base attribute or simpleType" 432 + " element"; 433 onSchemaError(msg); 434 } else { 435 return loadXSDatatypeFromSimpleType(xsdSubType); 436 } 437 } 438 } else { 439 onSchemaError("No <restriction>. Could not create XSDatatype for" 440 + " simpleType: " + xsdSimpleType); 441 } 442 443 return null; 444 } 445 446 /** 447 * Derives a new type from a base type and a set of restrictions 448 * 449 * @param baseType 450 * DOCUMENT ME! 451 * @param xsdRestriction 452 * DOCUMENT ME! 453 * 454 * @return DOCUMENT ME! 455 */ deriveSimpleType(XSDatatype baseType, Element xsdRestriction)456 private XSDatatype deriveSimpleType(XSDatatype baseType, 457 Element xsdRestriction) { 458 TypeIncubator incubator = new TypeIncubator(baseType); 459 ValidationContext context = null; 460 461 try { 462 for (Iterator iter = xsdRestriction.elementIterator(); iter 463 .hasNext();) { 464 Element element = (Element) iter.next(); 465 String name = element.getName(); 466 String value = element.attributeValue("value"); 467 boolean fixed = AttributeHelper.booleanValue(element, "fixed"); 468 469 // add facet 470 incubator.addFacet(name, value, fixed, context); 471 } 472 473 // derive a new type by those facets 474 String newTypeName = null; 475 476 return incubator.derive("", newTypeName); 477 } catch (DatatypeException e) { 478 onSchemaError("Invalid restriction: " + e.getMessage() 479 + " when trying to build restriction: " + xsdRestriction); 480 481 return null; 482 } 483 } 484 485 /** 486 * DOCUMENT ME! 487 * 488 * @param name 489 * The name of the element 490 * 491 * @return the <code>DatatypeElementFactory</code> for the given element 492 * QName, creating one if it does not already exist 493 */ getDatatypeElementFactory(QName name)494 private DatatypeElementFactory getDatatypeElementFactory(QName name) { 495 DatatypeElementFactory factory = documentFactory 496 .getElementFactory(name); 497 498 if (factory == null) { 499 factory = new DatatypeElementFactory(name); 500 name.setDocumentFactory(factory); 501 } 502 503 return factory; 504 } 505 getTypeByName(String type)506 private XSDatatype getTypeByName(String type) { 507 XSDatatype dataType = (XSDatatype) dataTypeCache.get(type); 508 509 if (dataType == null) { 510 // first check to see if it is a built-in type 511 // maybe a prefix is being used 512 int idx = type.indexOf(':'); 513 514 if (idx >= 0) { 515 String localName = type.substring(idx + 1); 516 517 try { 518 dataType = DatatypeFactory.getTypeByName(localName); 519 } catch (DatatypeException e) { 520 } 521 } 522 523 if (dataType == null) { 524 try { 525 dataType = DatatypeFactory.getTypeByName(type); 526 } catch (DatatypeException e) { 527 } 528 } 529 530 if (dataType == null) { 531 // it's no built-in type, maybe it's a type we defined ourself 532 QName typeQName = getQName(type); 533 dataType = (XSDatatype) namedTypeResolver.simpleTypeMap 534 .get(typeQName); 535 } 536 537 if (dataType != null) { 538 // store in cache for later 539 dataTypeCache.put(type, dataType); 540 } 541 } 542 543 return dataType; 544 } 545 getQName(String name)546 private QName getQName(String name) { 547 if (targetNamespace == null) { 548 return documentFactory.createQName(name); 549 } else { 550 return documentFactory.createQName(name, targetNamespace); 551 } 552 } 553 554 /** 555 * Called when there is a problem with the schema and the builder cannot 556 * handle the XML Schema Data Types correctly 557 * 558 * @param message 559 * DOCUMENT ME! 560 * 561 * @throws InvalidSchemaException 562 * DOCUMENT ME! 563 */ onSchemaError(String message)564 private void onSchemaError(String message) { 565 // Some users may wish to disable exception throwing 566 // and instead use some kind of listener for errors and continue 567 // System.out.println( "WARNING: " + message ); 568 throw new InvalidSchemaException(message); 569 } 570 } 571 572 /* 573 * Redistribution and use of this software and associated documentation 574 * ("Software"), with or without modification, are permitted provided that the 575 * following conditions are met: 576 * 577 * 1. Redistributions of source code must retain copyright statements and 578 * notices. Redistributions must also contain a copy of this document. 579 * 580 * 2. Redistributions in binary form must reproduce the above copyright notice, 581 * this list of conditions and the following disclaimer in the documentation 582 * and/or other materials provided with the distribution. 583 * 584 * 3. The name "DOM4J" must not be used to endorse or promote products derived 585 * from this Software without prior written permission of MetaStuff, Ltd. For 586 * written permission, please contact dom4j-info@metastuff.com. 587 * 588 * 4. Products derived from this Software may not be called "DOM4J" nor may 589 * "DOM4J" appear in their names without prior written permission of MetaStuff, 590 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd. 591 * 592 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org 593 * 594 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND 595 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 596 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 597 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE 598 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 599 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 600 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 601 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 602 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 603 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 604 * POSSIBILITY OF SUCH DAMAGE. 605 * 606 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. 607 */ 608