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