001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/framework/xml/schema/XSDocument.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.framework.xml.schema;
037
038 import java.net.URI;
039 import java.util.List;
040
041 import org.deegree.datatypes.QualifiedName;
042 import org.deegree.framework.log.ILogger;
043 import org.deegree.framework.log.LoggerFactory;
044 import org.deegree.framework.xml.XMLFragment;
045 import org.deegree.framework.xml.XMLParsingException;
046 import org.deegree.framework.xml.XMLTools;
047 import org.w3c.dom.Element;
048 import org.w3c.dom.Node;
049
050 /**
051 * Parser for XML schema documents.
052 *
053 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
054 * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh </a>
055 * @author last edited by: $Author: mschneider $
056 *
057 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
058 */
059 public class XSDocument extends XMLFragment {
060
061 private static final long serialVersionUID = 4371672452129797159L;
062
063 private URI targetNamespace;
064
065 private final ILogger LOG = LoggerFactory.getLogger( XSDocument.class );
066
067 /**
068 * Returns the class representation of the underlying schema document.
069 *
070 * @return class representation of the underlying schema document
071 * @throws XMLParsingException
072 * @throws XMLSchemaException
073 */
074 public XMLSchema parseXMLSchema()
075 throws XMLParsingException, XMLSchemaException {
076 SimpleTypeDeclaration[] simpleTypes = extractSimpleTypeDeclarations();
077 ComplexTypeDeclaration[] complexTypes = extractComplexTypeDeclarations();
078 ElementDeclaration[] elementDeclarations = extractElementDeclarations();
079 return new XMLSchema( getTargetNamespace(), simpleTypes, complexTypes, elementDeclarations );
080 }
081
082 /**
083 * Returns the target namespace of the underlying schema document.
084 *
085 * @return target namespace of the underlying schema document
086 * @throws XMLParsingException
087 */
088 public synchronized URI getTargetNamespace()
089 throws XMLParsingException {
090 if ( this.targetNamespace == null ) {
091 this.targetNamespace = XMLTools.getNodeAsURI( this.getRootElement(), "@targetNamespace", nsContext, null );
092 }
093 return this.targetNamespace;
094 }
095
096 /**
097 * Extracts all global (top-level) simple type declarations from the underlying schema document.
098 *
099 * @return all global (top-level) simple type declarations
100 * @throws XMLParsingException
101 * if the document is not a valid XML Schema document or does not match the
102 * limitations of this class
103 */
104 public SimpleTypeDeclaration[] extractSimpleTypeDeclarations()
105 throws XMLParsingException {
106 List<Element> simpleTypeElements = XMLTools.getElements( this.getRootElement(), getFullName( "simpleType" ),
107 nsContext );
108 LOG.logDebug( "Found " + simpleTypeElements.size() + " simple type declarations." );
109 SimpleTypeDeclaration[] simpleTypeDeclarations = new SimpleTypeDeclaration[simpleTypeElements.size()];
110 for ( int i = 0; i < simpleTypeDeclarations.length; i++ ) {
111 simpleTypeDeclarations[i] = parseSimpleTypeDeclaration( simpleTypeElements.get( i ) );
112 }
113 return simpleTypeDeclarations;
114 }
115
116 /**
117 * Extracts all global (top-level) complex type declarations from the underlying schema
118 * document.
119 *
120 * @return all global (top-level) complex type declarations
121 * @throws XMLParsingException
122 * if the document is not a valid XML Schema document or does not match the
123 * limitations of this class
124 */
125 public ComplexTypeDeclaration[] extractComplexTypeDeclarations()
126 throws XMLParsingException {
127 List<Element> complexTypeElements = XMLTools.getElements( this.getRootElement(), getFullName( "complexType" ),
128 nsContext );
129 LOG.logDebug( "Found " + complexTypeElements.size() + " complex type declarations." );
130 ComplexTypeDeclaration[] complexTypeDeclarations = new ComplexTypeDeclaration[complexTypeElements.size()];
131 for ( int i = 0; i < complexTypeDeclarations.length; i++ ) {
132 complexTypeDeclarations[i] = parseComplexTypeDeclaration( complexTypeElements.get( i ) );
133 }
134 return complexTypeDeclarations;
135 }
136
137 /**
138 * Extracts all global (top-level) element declarations from the underlying schema document.
139 *
140 * @return all global (top-level) element declarations
141 * @throws XMLParsingException
142 * if the document is not a valid XML Schema document or does not match the
143 * limitations of this class
144 */
145 public ElementDeclaration[] extractElementDeclarations()
146 throws XMLParsingException {
147 List<Element> complexTypeElements = XMLTools.getElements( this.getRootElement(), getFullName( "element" ),
148 nsContext );
149 LOG.logDebug( "Found " + complexTypeElements.size() + " element declarations." );
150 ElementDeclaration[] elementDeclarations = new ElementDeclaration[complexTypeElements.size()];
151 for ( int i = 0; i < elementDeclarations.length; i++ ) {
152 elementDeclarations[i] = parseElementDeclaration( complexTypeElements.get( i ) );
153 }
154 return elementDeclarations;
155 }
156
157 /**
158 * Returns the root element of the complex type declaration for the given name.
159 *
160 * @param name
161 * the name of the complex type declaration to look up (w/o namespace)
162 * @return the root element of the complex type declaration or null, if the requested complex
163 * type is not declared
164 */
165 public Element getComplexTypeDeclaration( String name ) {
166 String xPath = getFullName( "complexType[name=\"]" ) + name + "\"]";
167 Element element = null;
168 try {
169 element = (Element) XMLTools.getNode( getRootElement(), xPath, nsContext );
170 } catch ( XMLParsingException e ) {
171 // happens if requested complex type is not declared
172 }
173 return element;
174 }
175
176 /**
177 * Parses the given <code>Element</code> as an 'xs:element' declaration.
178 *
179 * @param element
180 * 'xs:element' declaration to be parsed
181 * @return object representation of the declaration
182 * @throws XMLParsingException
183 * if the document is not a valid XML Schema document or does not match the
184 * limitations of this class
185 */
186 protected ElementDeclaration parseElementDeclaration( Element element )
187 throws XMLParsingException {
188
189 QualifiedName name = new QualifiedName( XMLTools.getRequiredNodeAsString( element, "@name", nsContext ),
190 getTargetNamespace() );
191
192 if ( name.getLocalName().length() == 0 ) {
193 String msg = "Error in schema document. Empty name (\"\") in element declaration " + "found.";
194 throw new XMLSchemaException( msg );
195 }
196
197 LOG.logDebug( "Parsing element declaration '" + name + "'." );
198
199 boolean isAbstract = XMLTools.getNodeAsBoolean( element, "@abstract", nsContext, false );
200
201 TypeReference typeReference = null;
202 Node typeNode = XMLTools.getNode( element,
203 "@type|xs:simpleType/xs:restriction/@base|xs:simpleType/xs:extension/@base",
204 nsContext );
205 if ( typeNode != null ) {
206 typeReference = new TypeReference( parseQualifiedName( typeNode ) );
207 } else {
208 // inline type declaration
209 Element elem = (Element) XMLTools.getRequiredNode( element, getFullName( "complexType" ), nsContext );
210 TypeDeclaration type = parseComplexTypeDeclaration( elem );
211 typeReference = new TypeReference( type );
212 }
213
214 int minOccurs = XMLTools.getNodeAsInt( element, "@minOccurs", nsContext, 1 );
215 int maxOccurs = -1;
216 String maxOccursString = XMLTools.getNodeAsString( element, "@maxOccurs", nsContext, "1" );
217 if ( !"unbounded".equals( maxOccursString ) ) {
218 try {
219 maxOccurs = Integer.parseInt( maxOccursString );
220 } catch ( NumberFormatException e ) {
221 throw new XMLParsingException( "Invalid value ('" + maxOccursString + "') in 'maxOccurs' attribute. "
222 + "Must be a valid integer value or 'unbounded'." );
223 }
224 }
225
226 QualifiedName substitutionGroup = null;
227 Node substitutionGroupNode = XMLTools.getNode( element, "@substitutionGroup", nsContext );
228 if ( substitutionGroupNode != null ) {
229 substitutionGroup = parseQualifiedName( substitutionGroupNode );
230 }
231
232 return new ElementDeclaration( name, isAbstract, typeReference, minOccurs, maxOccurs, substitutionGroup );
233 }
234
235 /**
236 * Parses the given <code>Element</code> as an 'xs:simpleType' declaration.
237 * <p>
238 * The following limitations apply:
239 * <ul>
240 * <li>the type must be defined using 'restriction' (of a basic xsd type)</li>
241 * <li>the content model (enumeration, ...) is not evaluated</li>
242 * </ul>
243 * </p>
244 *
245 * @param element
246 * 'xs:simpleType' declaration to be parsed
247 * @return object representation of the declaration
248 * @throws XMLParsingException
249 * if the document is not a valid XML Schema document or does not match the
250 * limitations of this class
251 */
252 protected SimpleTypeDeclaration parseSimpleTypeDeclaration( Element element )
253 throws XMLParsingException {
254
255 QualifiedName name = null;
256 String localName = XMLTools.getNodeAsString( element, "@name", nsContext, null );
257 if ( localName != null ) {
258 name = new QualifiedName( localName, getTargetNamespace() );
259 if ( localName.length() == 0 ) {
260 String msg = "Error in schema document. Empty name (\"\") in simpleType " + "declaration found.";
261 throw new XMLSchemaException( msg );
262 }
263 }
264
265 LOG.logDebug( "Parsing simple type declaration '" + name + "'." );
266
267 Node restrictionBaseNode = XMLTools.getRequiredNode( element, getFullName( "restriction/@base" ), nsContext );
268 TypeReference restrictionBase = new TypeReference( parseQualifiedName( restrictionBaseNode ) );
269
270 return new SimpleTypeDeclaration( name, restrictionBase );
271 }
272
273 /**
274 * Parses the given <code>Element</code> as an 'xs:complexType' declaration.
275 *
276 * @param element
277 * 'xs:complexType' declaration to be parsed
278 * @return object representation of the declaration
279 * @throws XMLParsingException
280 * if the document is not a valid XML Schema document or does not match the
281 * limitations of this class
282 */
283 protected ComplexTypeDeclaration parseComplexTypeDeclaration( Element element )
284 throws XMLParsingException {
285
286 QualifiedName name = null;
287 String localName = XMLTools.getNodeAsString( element, "@name", nsContext, null );
288 if ( localName != null ) {
289 name = new QualifiedName( localName, getTargetNamespace() );
290 if ( localName.length() == 0 ) {
291 String msg = "Error in schema document. Empty name (\"\") for complexType " + "declaration found.";
292 throw new XMLSchemaException( msg );
293 }
294 }
295 LOG.logDebug( "Parsing complex type declaration '" + name + "'." );
296
297 List<Element> subElementList = null;
298 TypeReference extensionBase = null;
299 Node extensionBaseNode = XMLTools.getNode( element, getFullName( "complexContent/" )
300 + getFullName( "extension/@base" ), nsContext );
301 if ( extensionBaseNode != null ) {
302 extensionBase = new TypeReference( parseQualifiedName( extensionBaseNode ) );
303 subElementList = XMLTools.getElements( element, getFullName( "complexContent/" )
304 + getFullName( "extension/" ) + getFullName( "sequence/" )
305 + getFullName( "element" ), nsContext );
306 } else {
307 subElementList = XMLTools.getRequiredElements( element, getFullName( "sequence/" )
308 + getFullName( "element" ), nsContext );
309 }
310
311 ElementDeclaration[] subElements = new ElementDeclaration[subElementList.size()];
312 for ( int i = 0; i < subElements.length; i++ ) {
313 Element subElement = subElementList.get( i );
314 subElements[i] = parseElementDeclaration( subElement );
315 }
316
317 return new ComplexTypeDeclaration( name, extensionBase, subElements );
318 }
319
320 /**
321 * Prepends the prefix of the RootElement to the given local name.
322 * <p>
323 * If the prefix of the RootElement is empty, "xs:" is prepended.
324 *
325 * @param localName
326 * to this the prefix will be prepended
327 * @return prefix + localName
328 */
329 protected String getFullName( String localName ) {
330 String ret;
331 Element root = this.getRootElement();
332 String prefix = root.getPrefix();
333
334 if ( prefix != null && prefix.length() > 0 ) {
335 URI uri = nsContext.getURI( prefix );
336 if ( null == uri ) {
337 String nsUri = root.lookupNamespaceURI( prefix );
338 try {
339 nsContext.addNamespace( prefix, new URI( nsUri ) ); // synchronized ???
340 } catch ( Exception exc ) {
341 LOG.logError( "failed to add namespace: " + nsUri, exc );
342 }
343 }
344 ret = prefix + ':' + localName;
345 } else {
346 // fallback
347 ret = "xs:" + localName;
348 }
349 return ret;
350 }
351 }