001/* 002 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 003 * 004 * This software is open source. 005 * See the bottom of this file for the licence. 006 * 007 * $Id: SchemaParser.java,v 1.11 2002/02/01 10:54:30 jstrachan Exp $ 008 */ 009 010package org.dom4j.datatype; 011 012import com.sun.msv.datatype.xsd.DatatypeFactory; 013import com.sun.msv.datatype.xsd.TypeIncubator; 014import com.sun.msv.datatype.xsd.XSDatatype; 015 016import java.util.HashMap; 017import java.util.Iterator; 018import java.util.Map; 019 020import org.dom4j.Attribute; 021import org.dom4j.Document; 022import org.dom4j.Element; 023import org.dom4j.Namespace; 024import org.dom4j.QName; 025import org.dom4j.io.SAXReader; 026import org.dom4j.util.AttributeHelper; 027import org.dom4j.DocumentFactory; 028 029import org.relaxng.datatype.DatatypeException; 030import org.relaxng.datatype.ValidationContext; 031 032import org.xml.sax.EntityResolver; 033import org.xml.sax.InputSource; 034 035/** <p><code>SchemaParser</code> reads an XML Schema Document.</p> 036 * 037 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 038 * @author Yuxin Ruan 039 * @version $Revision: 1.11 $ 040 */ 041public class SchemaParser { 042 043 private static final Namespace XSD_NAMESPACE = Namespace.get( "xsd", "http://www.w3.org/2001/XMLSchema" ); 044 045 // Use QNames for the elements 046 private static final QName XSD_ELEMENT = QName.get( "element", XSD_NAMESPACE ); 047 private static final QName XSD_ATTRIBUTE = QName.get( "attribute", XSD_NAMESPACE ); 048 private static final QName XSD_SIMPLETYPE = QName.get( "simpleType", XSD_NAMESPACE ); 049 private static final QName XSD_COMPLEXTYPE = QName.get( "complexType", XSD_NAMESPACE ); 050 private static final QName XSD_RESTRICTION = QName.get( "restriction", XSD_NAMESPACE ); 051 private static final QName XSD_SEQUENCE = QName.get( "sequence", XSD_NAMESPACE ); 052 private static final QName XSD_CHOICE = QName.get( "choice", XSD_NAMESPACE ); 053 private static final QName XSD_ALL = QName.get( "all", XSD_NAMESPACE ); 054 private static final QName XSD_INCLUDE = QName.get("include", XSD_NAMESPACE); 055 056 /** Document factory used to register Element specific factories*/ 057 private DatatypeDocumentFactory documentFactory; 058 059 /** Cache of <code>XSDatatype</code> instances loaded or created during this build */ 060 private Map dataTypeCache = new HashMap(); 061 062 /** NamedTypeResolver */ 063 private NamedTypeResolver namedTypeResolver; 064 065 public SchemaParser() { 066 this(DatatypeDocumentFactory.singleton); 067 } 068 069 public SchemaParser(DatatypeDocumentFactory documentFactory) { 070 this.documentFactory = documentFactory; 071 this.namedTypeResolver=new NamedTypeResolver(documentFactory); 072 } 073 074 075 /** Parses the given schema document 076 * 077 * @param schemaDocument is the document of the XML Schema 078 */ 079 public void build( Document schemaDocument ) { 080 Element root = schemaDocument.getRootElement(); 081 if ( root != null ) { 082 //handle schema includes 083 Iterator includeIter = root.elementIterator( XSD_INCLUDE); 084 while (includeIter.hasNext()) { 085 Element includeElement = (Element) includeIter.next(); 086 String inclSchemaInstanceURI = includeElement.attributeValue("schemaLocation"); 087 EntityResolver resolver = schemaDocument.getEntityResolver(); 088 try { 089 if ( resolver == null ) { 090 throw new InvalidSchemaException( "No EntityResolver available so could not resolve the schema URI: " + 091 inclSchemaInstanceURI ); 092 } 093 InputSource inputSource = resolver.resolveEntity( null, inclSchemaInstanceURI ); 094 if ( inputSource == null ) { 095 throw new InvalidSchemaException( "Could not resolve the schema URI: " + inclSchemaInstanceURI ); 096 } 097 SAXReader reader = new SAXReader(); 098 Document inclSchemaDocument = reader.read( inputSource ); 099 build( inclSchemaDocument ); 100 } 101 catch (Exception e) { 102 System.out.println( "Failed to load schema: " + inclSchemaInstanceURI ); 103 System.out.println( "Caught: " + e ); 104 e.printStackTrace(); 105 throw new InvalidSchemaException( "Failed to load schema: " + inclSchemaInstanceURI ); 106 } 107 } 108 109 //handle elements 110 Iterator iter = root.elementIterator( XSD_ELEMENT ); 111 while ( iter.hasNext() ) { 112 onDatatypeElement( (Element) iter.next() , documentFactory); 113 } 114 115 //hanlde named complex types 116 iter = root.elementIterator( XSD_COMPLEXTYPE ); 117 while ( iter.hasNext() ) { 118 onNamedSchemaComplexType((Element) iter.next()); 119 } 120 121 //handle named simple types 122 iter = root.elementIterator( XSD_SIMPLETYPE ); 123 while ( iter.hasNext() ) { 124 onNamedSchemaSimpleType((Element) iter.next()); 125 } 126 127 namedTypeResolver.resolveNamedTypes(); 128 129 } 130 } 131 132 133 // Implementation methods 134 //------------------------------------------------------------------------- 135 136 /** processes an XML Schema <element> tag 137 */ 138 protected void onDatatypeElement( Element xsdElement , DocumentFactory parentFactory ) { 139 String name = xsdElement.attributeValue( "name" ); 140 String type = xsdElement.attributeValue( "type" ); 141 QName qname = getQName( name ); 142 143 DatatypeElementFactory elementFactory = getDatatypeElementFactory( qname ); 144 145 if ( type != null ) { 146 // register type with this element name 147 XSDatatype dataType = getTypeByName(type); 148 if (dataType!=null) { 149 elementFactory.setChildElementXSDatatype( qname, dataType ); 150 } 151 else { 152 QName typeQName=getQName(type); 153 namedTypeResolver.registerTypedElement(xsdElement,typeQName,parentFactory); 154 } 155 return; 156 } 157 158 // handle element types derrived from simpleTypes 159 Element xsdSimpleType = xsdElement.element( XSD_SIMPLETYPE ); 160 if ( xsdSimpleType != null ) { 161 System.out.println("Agfa-sg: handle element types derrived from simpleTypes for element: " + name); 162 XSDatatype dataType = loadXSDatatypeFromSimpleType( xsdSimpleType ); 163 if (dataType != null) { 164 System.out.println("dataType (from loadXSDatatypeFromSimpleType) = " + dataType); 165 elementFactory.setChildElementXSDatatype( qname, dataType ); 166 } 167 } 168 169 Element schemaComplexType = xsdElement.element( XSD_COMPLEXTYPE ); 170 if ( schemaComplexType != null ) { 171 onSchemaComplexType( schemaComplexType, elementFactory ); 172 } 173 174 Iterator iter = xsdElement.elementIterator( XSD_ATTRIBUTE ); 175 if ( iter.hasNext() ) { 176 do { 177 onDatatypeAttribute( 178 xsdElement, 179 elementFactory, 180 (Element) iter.next() 181 ); 182 } 183 while ( iter.hasNext() ); 184 } 185 } 186 187 /** processes an named XML Schema <complexTypegt; tag 188 */ 189 protected void onNamedSchemaComplexType(Element schemaComplexType) { 190 Attribute nameAttr=schemaComplexType.attribute("name"); 191 if (nameAttr==null) return; 192 String name=nameAttr.getText(); 193 QName qname=getQName(name); 194 195 DatatypeElementFactory elementFactory = getDatatypeElementFactory( qname ); 196 //DatatypeElementFactory elementFactory=new DatatypeElementFactory(qname); 197 198 onSchemaComplexType(schemaComplexType,elementFactory); 199 namedTypeResolver.registerComplexType(qname,elementFactory); 200 } 201 202 /** processes an XML Schema <complexTypegt; tag 203 */ 204 protected void onSchemaComplexType( Element schemaComplexType, DatatypeElementFactory elementFactory ) { 205 Iterator iter = schemaComplexType.elementIterator( XSD_ATTRIBUTE ); 206 while ( iter.hasNext() ) { 207 Element xsdAttribute = (Element) iter.next(); 208 String name = xsdAttribute.attributeValue( "name" ); 209 QName qname = getQName( name ); 210 211 XSDatatype dataType = dataTypeForXsdAttribute( xsdAttribute ); 212 if ( dataType != null ) { 213 // register the XSDatatype for the given Attribute 214 // #### should both these be done? 215 //elementFactory.setChildElementXSDatatype( qname, dataType ); 216 elementFactory.setAttributeXSDatatype( qname, dataType ); 217 } 218 else { 219 String type = xsdAttribute.attributeValue( "type" ); 220 System.out.println( "Warning: Couldn't find XSDatatype for type: " + type + " attribute: " + name ); 221 } 222 } 223 224 //handle sequence definition 225 Element schemaSequence = schemaComplexType.element( XSD_SEQUENCE ); 226 if (schemaSequence!=null) { 227 onChildElements(schemaSequence,elementFactory); 228 } 229 230 //handle choice definition 231 Element schemaChoice = schemaComplexType.element( XSD_CHOICE ); 232 if (schemaChoice!=null) { 233 onChildElements(schemaChoice,elementFactory); 234 } 235 236 //handle all definition 237 Element schemaAll = schemaComplexType.element( XSD_ALL ); 238 if (schemaAll!=null) { 239 onChildElements(schemaAll,elementFactory); 240 } 241 } 242 243 protected void onChildElements(Element element,DatatypeElementFactory factory) { 244 Iterator iter = element.elementIterator( XSD_ELEMENT ); 245 while ( iter.hasNext() ) { 246 Element xsdElement = (Element) iter.next(); 247 onDatatypeElement(xsdElement,factory); 248 } 249 } 250 251 /** processes an XML Schema <attribute> tag 252 */ 253 protected void onDatatypeAttribute( 254 Element xsdElement, 255 DatatypeElementFactory elementFactory, 256 Element xsdAttribute 257 ) { 258 String name = xsdAttribute.attributeValue( "name" ); 259 QName qname = getQName( name ); 260 XSDatatype dataType = dataTypeForXsdAttribute( xsdAttribute ); 261 if ( dataType != null ) { 262 // register the XSDatatype for the given Attribute 263 elementFactory.setAttributeXSDatatype( qname, dataType ); 264 } 265 else { 266 String type = xsdAttribute.attributeValue( "type" ); 267 System.out.println( "Warning: Couldn't find XSDatatype for type: " + type + " attribute: " + name ); 268 } 269 } 270 271 /** processes an XML Schema <attribute> tag 272 */ 273 protected XSDatatype dataTypeForXsdAttribute( Element xsdAttribute ) { 274 String type = xsdAttribute.attributeValue( "type" ); 275 XSDatatype dataType = null; 276 if ( type != null ) { 277 dataType = getTypeByName( type ); 278 } 279 else { 280 // must parse the <simpleType> element 281 Element xsdSimpleType = xsdAttribute.element( XSD_SIMPLETYPE ); 282 if ( xsdSimpleType == null ) { 283 String name = xsdAttribute.attributeValue( "name" ); 284 throw new InvalidSchemaException( 285 "The attribute: " + name + " has no type attribute and does not contain a <simpleType/> element" 286 ); 287 } 288 dataType = loadXSDatatypeFromSimpleType( xsdSimpleType ); 289 } 290 return dataType; 291 } 292 293 /** processes an named XML Schema <complexTypegt; tag 294 */ 295 protected void onNamedSchemaSimpleType(Element schemaSimpleType) { 296 Attribute nameAttr=schemaSimpleType.attribute("name"); 297 if (nameAttr==null) return; 298 String name=nameAttr.getText(); 299 QName qname=getQName(name); 300 XSDatatype datatype=loadXSDatatypeFromSimpleType(schemaSimpleType); 301 namedTypeResolver.registerSimpleType(qname,datatype); 302 } 303 304 /** Loads a XSDatatype object from a <simpleType> attribute schema element */ 305 protected XSDatatype loadXSDatatypeFromSimpleType( Element xsdSimpleType ) { 306 Element xsdRestriction = xsdSimpleType.element( XSD_RESTRICTION ); 307 if ( xsdRestriction != null ) { 308 String base = xsdRestriction.attributeValue( "base" ); 309 if ( base != null ) { 310 XSDatatype baseType = getTypeByName( base ); 311 if ( baseType == null ) { 312 onSchemaError( 313 "Invalid base type: " + base 314 + " when trying to build restriction: " + xsdRestriction 315 ); 316 } 317 else { 318 return deriveSimpleType( baseType, xsdRestriction ); 319 } 320 } 321 else { 322 // simpleType and base are mutually exclusive and you 323 // must have one within a <restriction> tag 324 Element xsdSubType = xsdSimpleType.element( XSD_SIMPLETYPE ); 325 if ( xsdSubType == null ) { 326 onSchemaError( 327 "The simpleType element: "+ xsdSimpleType 328 + " must contain a base attribute or simpleType element" 329 ); 330 } 331 else { 332 return loadXSDatatypeFromSimpleType( xsdSubType ); 333 } 334 } 335 } 336 else { 337 onSchemaError( 338 "No <restriction>. Could not create XSDatatype for simpleType: " 339 + xsdSimpleType 340 ); 341 } 342 return null; 343 } 344 345 /** Derives a new type from a base type and a set of restrictions */ 346 protected XSDatatype deriveSimpleType( XSDatatype baseType, Element xsdRestriction ) { 347 TypeIncubator incubator = new TypeIncubator(baseType); 348 ValidationContext context = null; 349 350 try { 351 for ( Iterator iter = xsdRestriction.elementIterator(); iter.hasNext(); ) { 352 Element element = (Element) iter.next(); 353 String name = element.getName(); 354 String value = element.attributeValue( "value" ); 355 boolean fixed = AttributeHelper.booleanValue( element, "fixed" ); 356 357 // add facet 358 incubator.addFacet( name, value, fixed, context ); 359 } 360 // derive a new type by those facets 361 String newTypeName = null; 362 return incubator.derive( newTypeName ); 363 } 364 catch (DatatypeException e) { 365 onSchemaError( 366 "Invalid restriction: " + e.getMessage() 367 + " when trying to build restriction: " + xsdRestriction 368 ); 369 return null; 370 } 371 } 372 373 /** @return the <code>DatatypeElementFactory</code> for the given 374 * element QName, creating one if it does not already exist 375 */ 376 protected DatatypeElementFactory getDatatypeElementFactory( QName elementQName ) { 377 DatatypeElementFactory factory = documentFactory.getElementFactory( elementQName ); 378 if ( factory == null ) { 379 factory = new DatatypeElementFactory( elementQName ); 380 elementQName.setDocumentFactory(factory); 381 } 382 return factory; 383 } 384 385 protected XSDatatype getTypeByName( String type ) { 386 XSDatatype dataType = (XSDatatype) dataTypeCache.get( type ); 387 if ( dataType == null ) { 388 try { 389 // maybe a prefix is being used 390 int idx = type.indexOf(':'); 391 if (idx >= 0 ) { 392 String localName = type.substring(idx + 1); 393 dataType = DatatypeFactory.getTypeByName( localName ); 394 } 395 if ( dataType == null ) { 396 dataType = DatatypeFactory.getTypeByName( type ); 397 } 398 } 399 catch (DatatypeException e) { 400 } 401 if ( dataType != null ) { 402 // store in cache for later 403 dataTypeCache.put( type, dataType ); 404 } 405 } 406 return dataType; 407 } 408 409 protected QName getQName( String name ) { 410 return documentFactory.createQName(name); 411 } 412 413 /** Called when there is a problem with the schema and the builder cannot 414 * handle the XML Schema Data Types correctly 415 */ 416 protected void onSchemaError( String message ) { 417 // Some users may wish to disable exception throwing 418 // and instead use some kind of listener for errors and continue 419 //System.out.println( "WARNING: " + message ); 420 421 throw new InvalidSchemaException( message ); 422 } 423} 424 425 426 427 428/* 429 * Redistribution and use of this software and associated documentation 430 * ("Software"), with or without modification, are permitted provided 431 * that the following conditions are met: 432 * 433 * 1. Redistributions of source code must retain copyright 434 * statements and notices. Redistributions must also contain a 435 * copy of this document. 436 * 437 * 2. Redistributions in binary form must reproduce the 438 * above copyright notice, this list of conditions and the 439 * following disclaimer in the documentation and/or other 440 * materials provided with the distribution. 441 * 442 * 3. The name "DOM4J" must not be used to endorse or promote 443 * products derived from this Software without prior written 444 * permission of MetaStuff, Ltd. For written permission, 445 * please contact dom4j-info@metastuff.com. 446 * 447 * 4. Products derived from this Software may not be called "DOM4J" 448 * nor may "DOM4J" appear in their names without prior written 449 * permission of MetaStuff, Ltd. DOM4J is a registered 450 * trademark of MetaStuff, Ltd. 451 * 452 * 5. Due credit should be given to the DOM4J Project 453 * (http://dom4j.org/). 454 * 455 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS 456 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 457 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 458 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 459 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 460 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 461 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 462 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 463 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 464 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 465 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 466 * OF THE POSSIBILITY OF SUCH DAMAGE. 467 * 468 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved. 469 * 470 * $Id: SchemaParser.java,v 1.11 2002/02/01 10:54:30 jstrachan Exp $ 471 */