1   
2   
3   
4   
5   
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      
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  
87      public SchemaParser() {
88          this(DatatypeDocumentFactory.singleton);
89      }
90  
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      */
102     public void build(Document schemaDocument) {
103         this.targetNamespace = null;
104         internalBuild(schemaDocument);
105     }
106 
107     public void build(Document schemaDocument, Namespace namespace) {
108         this.targetNamespace = namespace;
109         internalBuild(schemaDocument);
110     }
111 
112     private synchronized void internalBuild(Document schemaDocument) {
113         Element root = schemaDocument.getRootElement();
114 
115         if (root != null) {
116             
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             
154             Iterator iter = root.elementIterator(XSD_ELEMENT);
155 
156             while (iter.hasNext()) {
157                 onDatatypeElement((Element) iter.next(), documentFactory);
158             }
159 
160             
161             iter = root.elementIterator(XSD_SIMPLETYPE);
162 
163             while (iter.hasNext()) {
164                 onNamedSchemaSimpleType((Element) iter.next());
165             }
166 
167             
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     
179     
180 
181     /***
182      * processes an XML Schema <element> tag
183      * 
184      * @param xsdElement
185      *            DOCUMENT ME!
186      * @param parentFactory
187      *            DOCUMENT ME!
188      */
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             
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         
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      */
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      */
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                 
282                 
283                 
284                 elementFactory.setAttributeXSDatatype(qname, dataType);
285             }
286         }
287 
288         
289         Element schemaSequence = schemaComplexType.element(XSD_SEQUENCE);
290 
291         if (schemaSequence != null) {
292             onChildElements(schemaSequence, elementFactory);
293         }
294 
295         
296         Element schemaChoice = schemaComplexType.element(XSD_CHOICE);
297 
298         if (schemaChoice != null) {
299             onChildElements(schemaChoice, elementFactory);
300         }
301 
302         
303         Element schemaAll = schemaComplexType.element(XSD_ALL);
304 
305         if (schemaAll != null) {
306             onChildElements(schemaAll, elementFactory);
307         }
308     }
309 
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      */
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             
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      */
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             
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      */
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      */
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                 
426                 
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      */
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                 
470                 incubator.addFacet(name, value, fixed, context);
471             }
472 
473             
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      */
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 
506     private XSDatatype getTypeByName(String type) {
507         XSDatatype dataType = (XSDatatype) dataTypeCache.get(type);
508 
509         if (dataType == null) {
510             
511             
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                 
532                 QName typeQName = getQName(type);
533                 dataType = (XSDatatype) namedTypeResolver.simpleTypeMap
534                         .get(typeQName);
535             }
536 
537             if (dataType != null) {
538                 
539                 dataTypeCache.put(type, dataType);
540             }
541         }
542 
543         return dataType;
544     }
545 
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      */
564     private void onSchemaError(String message) {
565         
566         
567         
568         throw new InvalidSchemaException(message);
569     }
570 }
571 
572 
573 
574 
575 
576 
577 
578 
579 
580 
581 
582 
583 
584 
585 
586 
587 
588 
589 
590 
591 
592 
593 
594 
595 
596 
597 
598 
599 
600 
601 
602 
603 
604 
605 
606 
607