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 &lt;element&gt; 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 &lt;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 &lt;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 &lt;attribute&gt; 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 &lt;attribute&gt; 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 &lt;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 */