001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/graphics/sld/SLDFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003     
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve 
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     
043     ---------------------------------------------------------------------------*/
044    package org.deegree.graphics.sld;
045    
046    import java.io.IOException;
047    import java.io.StringReader;
048    import java.net.MalformedURLException;
049    import java.net.URI;
050    import java.net.URL;
051    import java.util.ArrayList;
052    import java.util.HashMap;
053    import java.util.Iterator;
054    import java.util.LinkedList;
055    import java.util.List;
056    
057    import org.deegree.datatypes.QualifiedName;
058    import org.deegree.framework.log.ILogger;
059    import org.deegree.framework.log.LoggerFactory;
060    import org.deegree.framework.xml.ElementList;
061    import org.deegree.framework.xml.NamespaceContext;
062    import org.deegree.framework.xml.XMLFragment;
063    import org.deegree.framework.xml.XMLParsingException;
064    import org.deegree.framework.xml.XMLTools;
065    import org.deegree.model.filterencoding.AbstractFilter;
066    import org.deegree.model.filterencoding.ComplexFilter;
067    import org.deegree.model.filterencoding.Expression;
068    import org.deegree.model.filterencoding.FalseFilter;
069    import org.deegree.model.filterencoding.Filter;
070    import org.deegree.model.filterencoding.FilterEvaluationException;
071    import org.deegree.model.filterencoding.LogicalOperation;
072    import org.deegree.model.filterencoding.Operation;
073    import org.deegree.model.filterencoding.OperationDefines;
074    import org.deegree.ogcbase.CommonNamespaces;
075    import org.deegree.ogcbase.OGCDocument;
076    import org.deegree.ogcbase.PropertyPath;
077    import org.w3c.dom.Element;
078    import org.w3c.dom.Node;
079    import org.w3c.dom.NodeList;
080    import org.w3c.dom.Text;
081    import org.xml.sax.SAXException;
082    
083    /**
084     * Factory class for all mapped SLD-elements.
085     * <p>
086     * TODO: Default values for omitted elements (such as fill color) should better not be used in the
087     * construction of the corresponding objects (Fill), but marked as left out (to make it possible to
088     * differentiate between explicitly given values and default values).
089     * <p>
090     * 
091     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
092     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
093     * @version $Revision: 12138 $ $Date: 2008-06-04 10:34:15 +0200 (Mi, 04 Jun 2008) $
094     */
095    public class SLDFactory {
096    
097        private static final ILogger LOG = LoggerFactory.getLogger( SLDFactory.class );
098    
099        private static URI ogcNS = CommonNamespaces.OGCNS;
100    
101        private static URI xlnNS = CommonNamespaces.XLNNS;
102    
103        private static final String PSE = CommonNamespaces.SE_PREFIX + ":";
104    
105        private static NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
106    
107        private static XMLFragment sldDoc = null;
108    
109        /**
110         * Creates a <tt>StyledLayerDescriptor</tt>-instance from the given XML-representation.
111         * <p>
112         * 
113         * @param s
114         *            contains the XML document
115         * @throws XMLParsingException
116         *             if a syntactic or semantic error in the XML document is encountered
117         * @return the constructed <tt>StyledLayerDescriptor</tt>-instance
118         */
119        public static synchronized StyledLayerDescriptor createSLD( String s )
120                                throws XMLParsingException {
121            StyledLayerDescriptor sld = null;
122            try {
123                sldDoc = new XMLFragment();
124                sldDoc.load( new StringReader( s ), XMLFragment.DEFAULT_URL );
125                sld = createSLD( sldDoc );
126            } catch ( IOException e ) {
127                LOG.logDebug( e.getMessage(), e );
128                throw new XMLParsingException( "IOException encountered while parsing SLD-Document" );
129            } catch ( SAXException e ) {
130                LOG.logDebug( e.getMessage(), e );
131                throw new XMLParsingException( "SAXException encountered while parsing SLD-Document" );
132            }
133    
134            return sld;
135        }
136    
137        /**
138         * Creates a <tt>StyledLayerDescriptor</tt>-instance from a SLD document read from the passed
139         * URL
140         * 
141         * @param url
142         * @return
143         * @throws XMLParsingException
144         */
145        public static synchronized StyledLayerDescriptor createSLD( URL url )
146                                throws XMLParsingException {
147            StyledLayerDescriptor sld = null;
148    
149            try {
150                sldDoc = new XMLFragment();
151                sldDoc.load( url );
152                sld = createSLD( sldDoc );
153            } catch ( IOException e ) {
154                LOG.logError( e.getMessage(), e );
155                throw new XMLParsingException( "IOException encountered while parsing SLD-Document" );
156            } catch ( SAXException e ) {
157                LOG.logError( e.getMessage(), e );
158                throw new XMLParsingException( "SAXException encountered while parsing SLD-Document" );
159            }
160    
161            return sld;
162        }
163    
164        /**
165         * Creates a <tt>StyledLayerDescriptor</tt>-instance according to the contents of the
166         * DOM-subtree starting at the given 'StyledLayerDescriptor'-<tt>Element</tt>.
167         * <p>
168         * 
169         * @param element
170         *            the 'StyledLayerDescriptor'-<tt>Element</tt>
171         * @throws XMLParsingException
172         *             if a syntactic or semantic error in the DOM-subtree is encountered
173         * @return the constructed <tt>StyledLayerDescriptor</tt>-instance
174         */
175        public static StyledLayerDescriptor createSLD( XMLFragment sldDoc )
176                                throws XMLParsingException {
177    
178            Element element = sldDoc.getRootElement();
179    
180            // optional: <Name>
181            String name = XMLTools.getStringValue( "Name", CommonNamespaces.SLDNS, element, null );
182    
183            // optional: <Title>
184            String title = XMLTools.getStringValue( "Title", CommonNamespaces.SLDNS, element, null );
185            // optional: <Abstract>
186            String abstract_ = XMLTools.getStringValue( "Abstract", CommonNamespaces.SLDNS, element, null );
187            // required: version-Attribute
188            String version = XMLTools.getRequiredAttrValue( "version", null, element );
189    
190            // optional: <NamedLayer>(s) / <UserLayer>(s)
191            NodeList nodelist = element.getChildNodes();
192            ArrayList layerList = new ArrayList( 100 );
193    
194            for ( int i = 0; i < nodelist.getLength(); i++ ) {
195                if ( nodelist.item( i ) instanceof Element ) {
196                    Element child = (Element) nodelist.item( i );
197                    String namespace = child.getNamespaceURI();
198                    if ( !CommonNamespaces.SLDNS.toASCIIString().equals( namespace ) ) {
199                        continue;
200                    }
201    
202                    String childName = child.getLocalName();
203                    if ( childName.equals( "NamedLayer" ) ) {
204                        layerList.add( createNamedLayer( child ) );
205                    } else if ( childName.equals( "UserLayer" ) ) {
206                        layerList.add( createUserLayer( child ) );
207                    }
208                }
209            }
210    
211            AbstractLayer[] al = new AbstractLayer[layerList.size()];
212            AbstractLayer[] layers = (AbstractLayer[]) layerList.toArray( al );
213    
214            return new StyledLayerDescriptor( name, title, version, abstract_, layers );
215        }
216    
217        private static Categorize createCategorize( Element root )
218                                throws XMLParsingException {
219            // ignore fallback value, we really implement it
220    //        String fallbackValue = root.getAttribute( "fallbackValue" );
221    
222            Categorize categorize = new Categorize();
223    
224            // ignore lookup value element, should be set to "Rasterdata"
225    //        Element lv = XMLTools.getElement( root, PSE + "LookupValue", nsContext );
226    //        ParameterValueType lookupValue = lv == null ? null : createParameterValueType( lv );
227    //
228    //        if ( lookupValue != null ) {
229    //            categorize.setLookupValue( lookupValue );
230    //        }
231    
232            List<Element> valueElements = XMLTools.getElements( root, PSE + "Value", nsContext );
233            List<Element> thresholdElements = XMLTools.getElements( root, PSE + "Threshold", nsContext );
234    
235            LinkedList<ParameterValueType> values = new LinkedList<ParameterValueType>();
236            LinkedList<ParameterValueType> thresholds = new LinkedList<ParameterValueType>();
237    
238            for ( Element e : valueElements ) {
239                values.add( createParameterValueType( e ) );
240            }
241    
242            for ( Element e : thresholdElements ) {
243                thresholds.add( createParameterValueType( e ) );
244            }
245    
246            categorize.setValues( values );
247            categorize.setThresholds( thresholds );
248    
249            String tbt = root.getAttribute( "threshholdsBelongTo" );
250            if ( tbt == null ) {
251                tbt = root.getAttribute( "thresholdsBelongTo" );
252            }
253    
254            ThresholdsBelongTo thresholdsBelongTo = null;
255    
256            if ( tbt != null ) {
257                if ( tbt.equalsIgnoreCase( "succeeding" ) ) {
258                    thresholdsBelongTo = ThresholdsBelongTo.SUCCEEDING;
259                }
260                if ( tbt.equalsIgnoreCase( "preceding" ) ) {
261                    thresholdsBelongTo = ThresholdsBelongTo.PRECEDING;
262                }
263            }
264    
265            if ( thresholdsBelongTo != null ) {
266                categorize.setThresholdsBelongTo( thresholdsBelongTo );
267            }
268    
269            return categorize;
270        }
271    
272        /**
273         * 
274         * @param root
275         * @param min
276         * @param max
277         * @return a raster symbolizer
278         * @throws XMLParsingException
279         */
280        private static RasterSymbolizer createRasterSymbolizer( Element root, double min, double max )
281                                throws XMLParsingException {
282            RasterSymbolizer symbolizer = new RasterSymbolizer( min, max );
283    
284            Element opacity = XMLTools.getElement( root, PSE + "Opacity", nsContext );
285            if ( opacity != null ) {
286                symbolizer.setOpacity( createParameterValueType( opacity ) );
287            }
288    
289            Element colorMap = XMLTools.getElement( root, PSE + "ColorMap", nsContext );
290            if ( colorMap != null ) {
291                Element categorize = XMLTools.getElement( colorMap, PSE + "Categorize", nsContext );
292    
293                if ( categorize != null ) {
294                    symbolizer.setCategorize( createCategorize( categorize ) );
295                }
296    
297                Element interpolate = XMLTools.getElement( colorMap, PSE + "Interpolate", nsContext );
298    
299                if ( interpolate != null ) {
300                    symbolizer.setInterpolate( createInterpolate( interpolate ) );
301                }
302            }
303    
304            return symbolizer;
305        }
306    
307        /**
308         * @param root
309         * @return an Interpolate object
310         * @throws XMLParsingException
311         */
312        private static Interpolate createInterpolate( Element root )
313                                throws XMLParsingException {
314            String fallbackValue = root.getAttribute( "fallbackValue" );
315    
316            Interpolate interpolate = new Interpolate( fallbackValue );
317    
318            Element elem = XMLTools.getElement( root, PSE + "lookupValue", nsContext );
319            if ( elem != null ) {
320                interpolate.setLookupValue( createParameterValueType( elem ) );
321            }
322    
323            String mode = root.getAttribute( "mode" );
324            if ( mode != null ) {
325                if ( mode.equalsIgnoreCase( "linear" ) ) {
326                    interpolate.setMode( Mode.LINEAR );
327                }
328                if ( mode.equalsIgnoreCase( "cosine" ) ) {
329                    LOG.logWarning( "Cosine interpolation is not supported." );
330                    interpolate.setMode( Mode.COSINE );
331                }
332                if ( mode.equalsIgnoreCase( "cubic" ) ) {
333                    LOG.logWarning( "Cubic interpolation is not supported." );
334                    interpolate.setMode( Mode.CUBIC );
335                }
336            }
337    
338            String method = root.getAttribute( "method" );
339            if ( method != null ) {
340                if ( method.equalsIgnoreCase( "numeric" ) ) {
341                    LOG.logWarning( "Numeric method is not supported, using color method anyway." );
342                    interpolate.setMethod( Method.NUMERIC );
343                }
344                if ( method.equalsIgnoreCase( "color" ) ) {
345                    interpolate.setMethod( Method.COLOR );
346                }
347            }
348    
349            List<Element> ips = XMLTools.getElements( root, PSE + "InterpolationPoint", nsContext );
350    
351            interpolate.setInterpolationPoints( createInterpolationPoints( ips ) );
352    
353            return interpolate;
354        }
355    
356        private static List<InterpolationPoint> createInterpolationPoints( List<Element> ips )
357                                throws XMLParsingException {
358            List<InterpolationPoint> ps = new ArrayList<InterpolationPoint>( ips.size() );
359    
360            for ( Element elem : ips ) {
361                double data = XMLTools.getRequiredNodeAsDouble( elem, PSE + "Data", nsContext );
362                Element e = XMLTools.getRequiredElement( elem, PSE + "Value", nsContext );
363                try {
364                    String val = createParameterValueType( e ).evaluate( null ).substring( 1 );
365                    ps.add( new InterpolationPoint( data, val ) );
366                } catch ( NumberFormatException e1 ) {
367                    LOG.logError( "A 'Value' in an 'InterpolationPoint' could not be parsed.", e1 );
368                } catch ( FilterEvaluationException e1 ) {
369                    LOG.logError( "A 'Value' in an 'InterpolationPoint' could not be parsed.", e1 );
370                }
371            }
372    
373            return ps;
374        }
375    
376        /**
377         * Creates a <tt>TextSymbolizer</tt>-instance according to the contents of the DOM-subtree
378         * starting at the given 'TextSymbolizer'-<tt>Element</tt>.
379         * <p>
380         * 
381         * @param element
382         *            the 'TextSymbolizer'-<tt>Element</tt>
383         * @param min
384         *            scale-constraint to be used
385         * @param max
386         *            scale-constraint to be used
387         * @throws XMLParsingException
388         *             if a syntactic or semantic error in the DOM-subtree is encountered
389         * @return the constructed <tt>TextSymbolizer</tt>-instance
390         */
391        private static TextSymbolizer createTextSymbolizer( Element element, double min, double max )
392                                throws XMLParsingException {
393    
394            // optional: <Geometry>
395            Geometry geometry = null;
396            Element geometryElement = XMLTools.getChildElement( "Geometry", CommonNamespaces.SLDNS, element );
397    
398            if ( geometryElement != null ) {
399                geometry = createGeometry( geometryElement );
400            }
401    
402            // optional: <Label>
403            ParameterValueType label = null;
404            Element labelElement = XMLTools.getChildElement( "Label", CommonNamespaces.SLDNS, element );
405    
406            if ( labelElement != null ) {
407                label = createParameterValueType( labelElement );
408            }
409    
410            // optional: <Font>
411            Font font = null;
412            Element fontElement = XMLTools.getChildElement( "Font", CommonNamespaces.SLDNS, element );
413    
414            if ( fontElement != null ) {
415                font = createFont( fontElement );
416            }
417    
418            // optional: <LabelPlacement>
419            LabelPlacement labelPlacement = null;
420            Element lpElement = XMLTools.getChildElement( "LabelPlacement", CommonNamespaces.SLDNS, element );
421    
422            if ( lpElement != null ) {
423                labelPlacement = createLabelPlacement( lpElement );
424            } else {
425                PointPlacement pp = StyleFactory.createPointPlacement();
426                labelPlacement = StyleFactory.createLabelPlacement( pp );
427            }
428    
429            // optional: <Halo>
430            Halo halo = null;
431            Element haloElement = XMLTools.getChildElement( "Halo", CommonNamespaces.SLDNS, element );
432    
433            if ( haloElement != null ) {
434                halo = createHalo( haloElement );
435            }
436    
437            // optional: <Fill>
438            Fill fill = null;
439    
440            TextSymbolizer ps = null;
441            String respClass = XMLTools.getAttrValue( element, null, "responsibleClass", null );
442            if ( respClass == null ) {
443                ps = new TextSymbolizer( geometry, label, font, labelPlacement, halo, fill, min, max );
444            } else {
445                ps = new TextSymbolizer( geometry, respClass, label, font, labelPlacement, halo, fill, min, max );
446            }
447    
448            return ps;
449        }
450    
451        /**
452         * Creates a <tt>Halo</tt>-instance according to the contents of the DOM-subtree starting at
453         * the given 'Halo'-<tt>Element</tt>.
454         * <p>
455         * 
456         * @param element
457         *            the 'Halo'-<tt>Element</tt>
458         * @throws XMLParsingException
459         *             if a syntactic or semantic error in the DOM-subtree is encountered
460         * @return the constructed <tt>Halo</tt>-instance
461         */
462        private static Halo createHalo( Element element )
463                                throws XMLParsingException {
464            // optional: <Radius>
465            ParameterValueType radius = null;
466            Element radiusElement = XMLTools.getChildElement( "Radius", CommonNamespaces.SLDNS, element );
467    
468            if ( radiusElement != null ) {
469                radius = createParameterValueType( radiusElement );
470            }
471    
472            // optional: <Fill>
473            Fill fill = null;
474            Element fillElement = XMLTools.getChildElement( "Fill", CommonNamespaces.SLDNS, element );
475    
476            if ( fillElement != null ) {
477                fill = createFill( fillElement );
478            }
479    
480            // optional: <Stroke>
481            Stroke stroke = null;
482            Element strokeElement = XMLTools.getChildElement( "Stroke", CommonNamespaces.SLDNS, element );
483    
484            if ( strokeElement != null ) {
485                stroke = createStroke( strokeElement );
486            }
487    
488            return new Halo( radius, fill, stroke );
489        }
490    
491        /**
492         * Creates a <tt>LabelPlacement</tt>-instance according to the contents of the DOM-subtree
493         * starting at the given 'LabelPlacement'-<tt>Element</tt>.
494         * <p>
495         * 
496         * @param element
497         *            the 'LabelPlacement'-<tt>Element</tt>
498         * @throws XMLParsingException
499         *             if a syntactic or semantic error in the DOM-subtree is encountered
500         * @return the constructed <tt>LabelPlacement</tt>-instance
501         */
502        private static LabelPlacement createLabelPlacement( Element element )
503                                throws XMLParsingException {
504            LabelPlacement labelPlacement = null;
505    
506            // required: <PointPlacement> / <LinePlacement>
507            NodeList nodelist = element.getChildNodes();
508            PointPlacement pPlacement = null;
509            LinePlacement lPlacement = null;
510    
511            for ( int i = 0; i < nodelist.getLength(); i++ ) {
512                if ( nodelist.item( i ) instanceof Element ) {
513                    Element child = (Element) nodelist.item( i );
514                    String namespace = child.getNamespaceURI();
515    
516                    if ( !CommonNamespaces.SLDNS.toASCIIString().equals( namespace ) ) {
517                        continue;
518                    }
519    
520                    String childName = child.getLocalName();
521    
522                    if ( childName.equals( "PointPlacement" ) ) {
523                        pPlacement = createPointPlacement( child );
524                    } else if ( childName.equals( "LinePlacement" ) ) {
525                        lPlacement = createLinePlacement( child );
526                    }
527                }
528            }
529    
530            if ( ( pPlacement != null ) && ( lPlacement == null ) ) {
531                labelPlacement = new LabelPlacement( pPlacement );
532            } else if ( ( pPlacement == null ) && ( lPlacement != null ) ) {
533                labelPlacement = new LabelPlacement( lPlacement );
534            } else {
535                throw new XMLParsingException( "Element 'LabelPlacement' must contain exactly one "
536                                               + "'PointPlacement'- or one 'LinePlacement'-element!" );
537            }
538    
539            return labelPlacement;
540        }
541    
542        /**
543         * Creates a <tt>PointPlacement</tt>-instance according to the contents of the DOM-subtree
544         * starting at the given 'PointPlacement'-<tt>Element</tt>.
545         * <p>
546         * 
547         * @param element
548         *            the 'PointPlacement'-<tt>Element</tt>
549         * @throws XMLParsingException
550         *             if a syntactic or semantic error in the DOM-subtree is encountered
551         * @return the constructed <tt>PointPlacement</tt>-instance
552         */
553        private static PointPlacement createPointPlacement( Element element )
554                                throws XMLParsingException {
555    
556            // optional: auto-Attribute (this is deegree-specific)
557            boolean auto = false;
558            String autoStr = XMLTools.getAttrValue( element, null, "auto", null );
559    
560            if ( autoStr != null && autoStr.equals( "true" ) ) {
561                auto = true;
562            }
563    
564            // optional: <AnchorPoint>
565            ParameterValueType[] anchorPoint = null;
566            Element apElement = XMLTools.getChildElement( "AnchorPoint", CommonNamespaces.SLDNS, element );
567    
568            if ( apElement != null ) {
569                anchorPoint = new ParameterValueType[2];
570    
571                Element apXElement = XMLTools.getChildElement( "AnchorPointX", CommonNamespaces.SLDNS, apElement );
572                Element apYElement = XMLTools.getChildElement( "AnchorPointY", CommonNamespaces.SLDNS, apElement );
573    
574                if ( ( apXElement == null ) || ( apYElement == null ) ) {
575                    throw new XMLParsingException( "Element 'AnchorPoint' must contain exactly one "
576                                                   + "'AnchorPointX'- and one 'AnchorPointY'-element!" );
577                }
578    
579                anchorPoint[0] = createParameterValueType( apXElement );
580                anchorPoint[1] = createParameterValueType( apYElement );
581            }
582    
583            // optional: <Displacement>
584            ParameterValueType[] displacement = null;
585            Element dElement = XMLTools.getChildElement( "Displacement", CommonNamespaces.SLDNS, element );
586    
587            if ( dElement != null ) {
588                displacement = new ParameterValueType[2];
589    
590                Element dXElement = XMLTools.getChildElement( "DisplacementX", CommonNamespaces.SLDNS, dElement );
591                Element dYElement = XMLTools.getChildElement( "DisplacementY", CommonNamespaces.SLDNS, dElement );
592    
593                if ( ( dXElement == null ) || ( dYElement == null ) ) {
594                    throw new XMLParsingException( "Element 'Displacement' must contain exactly one "
595                                                   + "'DisplacementX'- and one 'DisplacementY'-element!" );
596                }
597    
598                displacement[0] = createParameterValueType( dXElement );
599                displacement[1] = createParameterValueType( dYElement );
600            }
601    
602            // optional: <Rotation>
603            ParameterValueType rotation = null;
604            Element rElement = XMLTools.getChildElement( "Rotation", CommonNamespaces.SLDNS, element );
605    
606            if ( rElement != null ) {
607                rotation = createParameterValueType( rElement );
608            }
609    
610            return new PointPlacement( anchorPoint, displacement, rotation, auto );
611        }
612    
613        /**
614         * Creates a <tt>LinePlacement</tt>-instance according to the contents of the DOM-subtree
615         * starting at the given 'LinePlacement'-<tt>Element</tt>.
616         * <p>
617         * 
618         * @param element
619         *            the 'LinePlacement'-<tt>Element</tt>
620         * @throws XMLParsingException
621         *             if a syntactic or semantic error in the DOM-subtree is encountered
622         * @return the constructed <tt>LinePlacement</tt>-instance
623         */
624        private static LinePlacement createLinePlacement( Element element )
625                                throws XMLParsingException {
626    
627            // optional: <PerpendicularOffset>
628            ParameterValueType pOffset = null;
629            Element pOffsetElement = XMLTools.getChildElement( "PerpendicularOffset", CommonNamespaces.SLDNS, element );
630    
631            if ( pOffsetElement != null ) {
632                pOffset = createParameterValueType( pOffsetElement );
633            }
634    
635            // optional: <Gap> (this is deegree-specific)
636            ParameterValueType gap = null;
637            Element gapElement = XMLTools.getChildElement( "Gap", CommonNamespaces.SLDNS, element );
638    
639            if ( gapElement != null ) {
640                gap = createParameterValueType( gapElement );
641            }
642    
643            // optional: <LineWidth> (this is deegree-specific)
644            ParameterValueType lineWidth = null;
645            Element lineWidthElement = XMLTools.getChildElement( "LineWidth", CommonNamespaces.SLDNS, element );
646    
647            if ( lineWidthElement != null ) {
648                lineWidth = createParameterValueType( lineWidthElement );
649            }
650    
651            return new LinePlacement( pOffset, lineWidth, gap );
652        }
653    
654        /**
655         * Creates a <tt>Font</tt>-instance according to the contents of the DOM-subtree starting at
656         * the given 'Font'-<tt>Element</tt>.
657         * <p>
658         * 
659         * @param element
660         *            the 'Font'-<tt>Element</tt>
661         * @throws XMLParsingException
662         *             if a syntactic or semantic error in the DOM-subtree is encountered
663         * @return the constructed <tt>Font</tt>-instance
664         */
665        private static Font createFont( Element element )
666                                throws XMLParsingException {
667    
668            // optional: <CssParameter>s
669            ElementList nl = XMLTools.getChildElements( "CssParameter", CommonNamespaces.SLDNS, element );
670            HashMap cssParams = new HashMap( nl.getLength() );
671    
672            for ( int i = 0; i < nl.getLength(); i++ ) {
673                CssParameter cssParam = createCssParameter( nl.item( i ) );
674                cssParams.put( cssParam.getName(), cssParam );
675            }
676    
677            return new Font( cssParams );
678        }
679    
680        /**
681         * Creates a <tt>ParameterValueType</tt>-instance according to the contents of the
682         * DOM-subtree starting at the given <tt>Element</tt>.
683         * <p>
684         * 
685         * @param element
686         *            the <tt>Element</tt> (must be of the type sld:ParameterValueType)
687         * @throws XMLParsingException
688         *             if a syntactic or semantic error in the DOM-subtree is encountered
689         * @return the constructed <tt>ParameterValueType</tt>-instance
690         */
691        private static ParameterValueType createParameterValueType( Element element )
692                                throws XMLParsingException {
693            // mix of text nodes and <wfs:Expression>-elements
694            ArrayList componentList = new ArrayList();
695            NodeList nl = element.getChildNodes();
696    
697            for ( int i = 0; i < nl.getLength(); i++ ) {
698                Node node = nl.item( i );
699    
700                switch ( node.getNodeType() ) {
701                case Node.TEXT_NODE: {
702                    componentList.add( node.getNodeValue() );
703                    break;
704                }
705                case Node.ELEMENT_NODE: {
706                    Expression expression = Expression.buildFromDOM( (Element) node );
707                    componentList.add( expression );
708                    break;
709                }
710                default:
711                    throw new XMLParsingException( "Elements of type 'ParameterValueType' may only "
712                                                   + "consist of CDATA and 'ogc:Expression'-elements!" );
713                }
714            }
715    
716            Object[] components = componentList.toArray( new Object[componentList.size()] );
717            return new ParameterValueType( components );
718        }
719    
720        /**
721         * Creates a <tt>NamedStyle</tt>-instance according to the contents of the DOM-subtree
722         * starting at the given 'NamedStyle'-<tt>Element</tt>.
723         * <p>
724         * 
725         * @param element
726         *            the 'NamedStyle'-<tt>Element</tt>
727         * @throws XMLParsingException
728         *             if a syntactic or semantic error in the DOM-subtree is encountered
729         * @return the constructed <tt>NamedStyle</tt>-instance
730         */
731        private static NamedStyle createNamedStyle( Element element )
732                                throws XMLParsingException {
733            // required: <Name>
734            String name = XMLTools.getRequiredStringValue( "Name", CommonNamespaces.SLDNS, element );
735    
736            return new NamedStyle( name );
737        }
738    
739        /**
740         * 
741         */
742        public static NamedStyle createNamedStyle( String name ) {
743            return new NamedStyle( name );
744        }
745    
746        /**
747         * Creates a <tt>RemoteOWS</tt>-instance according to the contents of the DOM-subtree
748         * starting at the given 'RemoteOWS'-<tt>Element</tt>.
749         * <p>
750         * 
751         * @param element
752         *            the 'RemoteOWS'-<tt>Element</tt>
753         * @throws XMLParsingException
754         *             if a syntactic or semantic error in the DOM-subtree is encountered
755         * @return the constructed <tt>RemoteOWS</tt>-instance
756         */
757        private static RemoteOWS createRemoteOWS( Element element )
758                                throws XMLParsingException {
759            // required: <Service>
760            String service = XMLTools.getRequiredStringValue( "Service", CommonNamespaces.SLDNS, element );
761    
762            if ( !( service.equals( "WFS" ) || service.equals( "WCS" ) ) ) {
763                throw new XMLParsingException( "Value ('" + service + "') of element 'service' is invalid. "
764                                               + "Allowed values are: 'WFS' and 'WCS'." );
765            }
766    
767            // required: <OnlineResource>
768            Element onlineResourceElement = XMLTools.getRequiredChildElement( "OnlineResource", CommonNamespaces.SLDNS,
769                                                                              element );
770            String href = XMLTools.getRequiredAttrValue( "xlink:href", null, onlineResourceElement );
771            URL url = null;
772    
773            try {
774                url = new URL( href );
775            } catch ( MalformedURLException e ) {
776                LOG.logDebug( e.getMessage(), e );
777                throw new XMLParsingException( "Value ('" + href + "') of attribute 'href' of "
778                                               + "element 'OnlineResoure' does not denote a valid URL" );
779            }
780    
781            return new RemoteOWS( service, url );
782        }
783    
784        /**
785         * Creates a <tt>NamedLayer</tt>-instance according to the contents of the DOM-subtree
786         * starting at the given 'UserLayer'-<tt>Element</tt>.
787         * <p>
788         * 
789         * @param element
790         *            the 'NamedLayer'-<tt>Element</tt>
791         * @throws XMLParsingException
792         *             if a syntactic or semantic error in the DOM-subtree is encountered
793         * @return the constructed <tt>NamedLayer</tt>-instance
794         */
795        private static NamedLayer createNamedLayer( Element element )
796                                throws XMLParsingException {
797            // required: <Name>
798            String name = XMLTools.getRequiredStringValue( "Name", CommonNamespaces.SLDNS, element );
799    
800            // optional: <LayerFeatureConstraints>
801            LayerFeatureConstraints lfc = null;
802            Element lfcElement = XMLTools.getChildElement( "LayerFeatureConstraints", CommonNamespaces.SLDNS, element );
803    
804            if ( lfcElement != null ) {
805                lfc = createLayerFeatureConstraints( lfcElement );
806            }
807    
808            // optional: <NamedStyle>(s) / <UserStyle>(s)
809            NodeList nodelist = element.getChildNodes();
810            ArrayList styleList = new ArrayList();
811    
812            for ( int i = 0; i < nodelist.getLength(); i++ ) {
813                if ( nodelist.item( i ) instanceof Element ) {
814                    Element child = (Element) nodelist.item( i );
815                    String namespace = child.getNamespaceURI();
816    
817                    if ( !CommonNamespaces.SLDNS.toASCIIString().equals( namespace ) ) {
818                        continue;
819                    }
820    
821                    String childName = child.getLocalName();
822    
823                    if ( childName.equals( "NamedStyle" ) ) {
824                        styleList.add( createNamedStyle( child ) );
825                    } else if ( childName.equals( "UserStyle" ) ) {
826                        styleList.add( createUserStyle( child ) );
827                    }
828                }
829            }
830    
831            AbstractStyle[] styles = (AbstractStyle[]) styleList.toArray( new AbstractStyle[styleList.size()] );
832    
833            return new NamedLayer( name, lfc, styles );
834        }
835    
836        /**
837         * 
838         */
839        public static NamedLayer createNamedLayer( String name, LayerFeatureConstraints layerFeatureConstraints,
840                                                   AbstractStyle[] styles ) {
841            return new NamedLayer( name, layerFeatureConstraints, styles );
842        }
843    
844        /**
845         * Creates a <tt>UserLayer</tt>-instance according to the contents of the DOM-subtree
846         * starting at the given 'UserLayer'-<tt>Element</tt>.
847         * <p>
848         * 
849         * @param element
850         *            the 'UserLayer'-<tt>Element</tt>
851         * @throws XMLParsingException
852         *             if a syntactic or semantic error in the DOM-subtree is encountered
853         * @return the constructed <tt>UserLayer</tt>-instance
854         */
855        private static UserLayer createUserLayer( Element element )
856                                throws XMLParsingException {
857            // optional: <Name>
858            String name = XMLTools.getStringValue( "Name", CommonNamespaces.SLDNS, element, null );
859    
860            // optional: <RemoteOWS>
861            RemoteOWS remoteOWS = null;
862            Element remoteOWSElement = XMLTools.getChildElement( "RemoteOWS", CommonNamespaces.SLDNS, element );
863    
864            if ( remoteOWSElement != null ) {
865                remoteOWS = createRemoteOWS( remoteOWSElement );
866            }
867    
868            // required: <LayerFeatureConstraints>
869            LayerFeatureConstraints lfc = null;
870            Element lfcElement = XMLTools.getRequiredChildElement( "LayerFeatureConstraints", CommonNamespaces.SLDNS,
871                                                                   element );
872            lfc = createLayerFeatureConstraints( lfcElement );
873    
874            // optional: <UserStyle>(s)
875            ElementList nodelist = XMLTools.getChildElements( "UserStyle", CommonNamespaces.SLDNS, element );
876            UserStyle[] styles = new UserStyle[nodelist.getLength()];
877            for ( int i = 0; i < nodelist.getLength(); i++ ) {
878                styles[i] = createUserStyle( nodelist.item( i ) );
879            }
880    
881            return new UserLayer( name, lfc, styles, remoteOWS );
882        }
883    
884        /**
885         * Creates a <tt>FeatureTypeConstraint</tt>-instance according to the contents of the
886         * DOM-subtree starting at the given 'FeatureTypeConstraint'-<tt>Element</tt>.
887         * <p>
888         * 
889         * @param element
890         *            the 'FeatureTypeConstraint'-<tt>Element</tt>
891         * @throws XMLParsingException
892         *             if a syntactic or semantic error in the DOM-subtree is encountered
893         * @return the constructed <tt>FeatureTypeConstraint</tt>-instance
894         */
895        private static FeatureTypeConstraint createFeatureTypeConstraint( Element element )
896                                throws XMLParsingException {
897            // optional: <Name>
898            Node node = XMLTools.getNode( element, "sld:FeatureTypeName/text()", CommonNamespaces.getNamespaceContext() );
899    
900            QualifiedName name;
901            if ( node == null ) {
902                name = null;
903            } else {
904                name = XMLTools.getQualifiedNameValue( node );
905            }
906    
907            // optional: <Filter>
908            Filter filter = null;
909            Element filterElement = XMLTools.getChildElement( "Filter", ogcNS, element );
910    
911            if ( filterElement != null ) {
912                filter = AbstractFilter.buildFromDOM( filterElement );
913            }
914    
915            // optional: <Extent>(s)
916            ElementList nodelist = XMLTools.getChildElements( "Extent", CommonNamespaces.SLDNS, element );
917            Extent[] extents = new Extent[nodelist.getLength()];
918    
919            for ( int i = 0; i < nodelist.getLength(); i++ ) {
920                extents[i] = createExtent( nodelist.item( i ) );
921            }
922    
923            return new FeatureTypeConstraint( name, filter, extents );
924        }
925    
926        /**
927         * Creates an <tt>Extent</tt>-instance according to the contents of the DOM-subtree starting
928         * at the given 'Extent'-<tt>Element</tt>.
929         * <p>
930         * 
931         * @param element
932         *            the 'Extent'-<tt>Element</tt>
933         * @throws XMLParsingException
934         *             if a syntactic or semantic error in the DOM-subtree is encountered
935         * @return the constructed <tt>Extent</tt>-instance
936         */
937        private static Extent createExtent( Element element )
938                                throws XMLParsingException {
939            // required: <Name>
940            String name = XMLTools.getRequiredStringValue( "Name", CommonNamespaces.SLDNS, element );
941            // required: <Value>
942            String value = XMLTools.getRequiredStringValue( "Value", CommonNamespaces.SLDNS, element );
943    
944            return new Extent( name, value );
945        }
946    
947        /**
948         * Creates a <tt>LayerFeatureConstraints</tt>-instance according to the contents of the
949         * DOM-subtree starting at the given 'LayerFeatureConstraints'-<tt>Element</tt>.
950         * <p>
951         * 
952         * @param element
953         *            the 'LayerFeatureConstraints'-<tt>Element</tt>
954         * @throws XMLParsingException
955         *             if a syntactic or semantic error in the DOM-subtree is encountered
956         * @return the constructed <tt>LayerFeatureConstraints</tt>-instance
957         */
958        public static LayerFeatureConstraints createLayerFeatureConstraints( Element element )
959                                throws XMLParsingException {
960            // required: <FeatureTypeConstraint>(s)
961            ElementList nodelist = XMLTools.getChildElements( "FeatureTypeConstraint", CommonNamespaces.SLDNS, element );
962            FeatureTypeConstraint[] ftcs = new FeatureTypeConstraint[nodelist.getLength()];
963    
964            for ( int i = 0; i < nodelist.getLength(); i++ ) {
965                ftcs[i] = createFeatureTypeConstraint( nodelist.item( i ) );
966            }
967    
968            return new LayerFeatureConstraints( ftcs );
969        }
970    
971        /**
972         * Creates a <tt>UserStyle</tt>-instance according to the contents of the DOM-subtree
973         * starting at the given 'UserStyle'-<tt>Element</tt>.
974         * <p>
975         * 
976         * @param element
977         *            the 'UserStyle'-<tt>Element</tt>
978         * @throws XMLParsingException
979         *             if a syntactic or semantic error in the DOM-subtree is encountered
980         * @return the constructed <tt>UserStyle</tt>-instance
981         */
982        private static UserStyle createUserStyle( Element element )
983                                throws XMLParsingException {
984            // optional: <Name>
985            String name = XMLTools.getStringValue( "Name", CommonNamespaces.SLDNS, element, null );
986            // optional: <Title>
987            String title = XMLTools.getStringValue( "Title", CommonNamespaces.SLDNS, element, null );
988            // optional: <Abstract>
989            String abstract_ = XMLTools.getStringValue( "Abstract", CommonNamespaces.SLDNS, element, null );
990    
991            // optional: <IsDefault>
992            String defaultString = XMLTools.getStringValue( "IsDefault", CommonNamespaces.SLDNS, element, null );
993            boolean isDefault = false;
994    
995            if ( defaultString != null ) {
996                if ( defaultString.equals( "1" ) ) {
997                    isDefault = true;
998                }
999            }
1000    
1001            // required: <FeatureTypeStyle> (s)
1002            ElementList nl = XMLTools.getChildElements( "FeatureTypeStyle", CommonNamespaces.SLDNS, element );
1003            FeatureTypeStyle[] styles = new FeatureTypeStyle[nl.getLength()];
1004    
1005            if ( styles.length == 0 ) {
1006                throw new XMLParsingException( "Required child-element 'FeatureTypeStyle' of element "
1007                                               + "'UserStyle' is missing!" );
1008            }
1009    
1010            for ( int i = 0; i < nl.getLength(); i++ ) {
1011                styles[i] = createFeatureTypeStyle( nl.item( i ) );
1012            }
1013    
1014            return new UserStyle( name, title, abstract_, isDefault, styles );
1015        }
1016    
1017        /**
1018         * Creates a <tt>FeatureTypeStyle</tt>-instance according to the contents of the DOM-subtree
1019         * starting at the given 'FeatureTypeStyle'-<tt>Element</tt>.
1020         * <p>
1021         * TODO: The ElseFilter currently does not work correctly with FeatureFilters.
1022         * <p>
1023         * 
1024         * @param element
1025         *            the 'FeatureTypeStyle'-<tt>Element</tt>
1026         * @throws XMLParsingException
1027         *             if a syntactic or semantic error in the DOM-subtree is encountered
1028         * @return the constructed <tt>FeatureTypeStyle</tt>-instance
1029         */
1030        public static FeatureTypeStyle createFeatureTypeStyle( Element element )
1031                                throws XMLParsingException {
1032            // optional: <Name>
1033            String name = XMLTools.getStringValue( "Name", CommonNamespaces.SLDNS, element, null );
1034            // optional: <Title>
1035            String title = XMLTools.getStringValue( "Title", CommonNamespaces.SLDNS, element, null );
1036            // optional: <Abstract>
1037            String abstract_ = XMLTools.getStringValue( "Abstract", CommonNamespaces.SLDNS, element, null );
1038            // optional: <FeatureTypeName>
1039            String featureTypeName = XMLTools.getStringValue( "FeatureTypeName", CommonNamespaces.SLDNS, element, null );
1040    
1041            // optional: several <Rule> / <SemanticTypeIdentifier>
1042            NodeList nodelist = element.getChildNodes();
1043            ArrayList<Rule> ruleList = new ArrayList<Rule>();
1044            ArrayList<String> typeIdentifierList = new ArrayList<String>();
1045    
1046            // collect Filters of all Rules
1047            ArrayList<Filter> filters = new ArrayList<Filter>();
1048            // collect all Rules that have an ElseFilter
1049            ArrayList<Rule> elseRules = new ArrayList<Rule>();
1050    
1051            for ( int i = 0; i < nodelist.getLength(); i++ ) {
1052                if ( nodelist.item( i ) instanceof Element ) {
1053                    Element child = (Element) nodelist.item( i );
1054                    String namespace = child.getNamespaceURI();
1055                    if ( !CommonNamespaces.SLDNS.toString().equals( namespace ) ) {
1056                        continue;
1057                    }
1058    
1059                    String childName = child.getLocalName();
1060    
1061                    if ( childName.equals( "Rule" ) ) {
1062                        Rule rule = createRule( child );
1063                        if ( rule.hasElseFilter() ) {
1064                            elseRules.add( rule );
1065                        } else if ( rule.getFilter() == null || rule.getFilter() instanceof ComplexFilter ) {
1066                            filters.add( rule.getFilter() );
1067                        }
1068                        ruleList.add( rule );
1069                    } else if ( childName.equals( "SemanticTypeIdentifier" ) ) {
1070                        typeIdentifierList.add( XMLTools.getStringValue( child ) );
1071                    }
1072                }
1073            }
1074    
1075            // compute and set the ElseFilter for all ElseFilter-Rules
1076            Filter elseFilter = null;
1077            // a Rule exists with no Filter at all -> elseFilter = false
1078            if ( filters.contains( null ) ) {
1079                elseFilter = new FalseFilter();
1080                // one Rule with a Filter exists -> elseFilter = NOT Filter
1081            } else if ( filters.size() == 1 ) {
1082                elseFilter = new ComplexFilter( OperationDefines.NOT );
1083                List<Operation> arguments = ( (LogicalOperation) ( (ComplexFilter) elseFilter ).getOperation() ).getArguments();
1084                ComplexFilter complexFilter = (ComplexFilter) filters.get( 0 );
1085                arguments.add( complexFilter.getOperation() );
1086                // several Rules with Filters exist -> elseFilter = NOT (Filter1 OR Filter2 OR...)
1087            } else if ( filters.size() > 1 ) {
1088                ComplexFilter innerFilter = new ComplexFilter( OperationDefines.OR );
1089                elseFilter = new ComplexFilter( innerFilter, null, OperationDefines.NOT );
1090                List<Operation> arguments = ( (LogicalOperation) innerFilter.getOperation() ).getArguments();
1091                Iterator it = filters.iterator();
1092                while ( it.hasNext() ) {
1093                    ComplexFilter complexFilter = (ComplexFilter) it.next();
1094                    arguments.add( complexFilter.getOperation() );
1095                }
1096            }
1097            Iterator it = elseRules.iterator();
1098            while ( it.hasNext() ) {
1099                ( (Rule) it.next() ).setFilter( elseFilter );
1100            }
1101    
1102            Rule[] rules = ruleList.toArray( new Rule[ruleList.size()] );
1103            String[] typeIdentifiers = typeIdentifierList.toArray( new String[typeIdentifierList.size()] );
1104    
1105            return new FeatureTypeStyle( name, title, abstract_, featureTypeName, typeIdentifiers, rules );
1106        }
1107    
1108        /**
1109         * Creates a <tt>Rule</tt>-instance according to the contents of the DOM-subtree starting at
1110         * the given 'Rule'-<tt>Element</tt>.
1111         * <p>
1112         * 
1113         * @param element
1114         *            the 'Rule'-<tt>Element</tt>
1115         * @throws XMLParsingException
1116         *             if a syntactic or semantic error in the DOM-subtree is encountered
1117         * @return the constructed <tt>Rule</tt>-instance
1118         */
1119        private static Rule createRule( Element element )
1120                                throws XMLParsingException {
1121            // optional: <Name>
1122            String name = XMLTools.getStringValue( "Name", CommonNamespaces.SLDNS, element, null );
1123            // optional: <Title>
1124            String title = XMLTools.getStringValue( "Title", CommonNamespaces.SLDNS, element, null );
1125            // optional: <Abstract>
1126            String abstract_ = XMLTools.getStringValue( "Abstract", CommonNamespaces.SLDNS, element, null );
1127    
1128            // optional: <LegendGraphic>
1129            LegendGraphic legendGraphic = null;
1130            Element legendGraphicElement = XMLTools.getChildElement( "LegendGraphic", CommonNamespaces.SLDNS, element );
1131    
1132            if ( legendGraphicElement != null ) {
1133                legendGraphic = createLegendGraphic( legendGraphicElement );
1134            }
1135    
1136            // optional: <Filter>
1137            boolean isAnElseFilter = false;
1138            Filter filter = null;
1139            Element filterElement = XMLTools.getChildElement( "Filter", ogcNS, element );
1140            if ( filterElement != null ) {
1141                filter = AbstractFilter.buildFromDOM( filterElement );
1142            }
1143    
1144            // optional: <ElseFilter>
1145            Element elseFilterElement = XMLTools.getChildElement( "ElseFilter", CommonNamespaces.SLDNS, element );
1146            if ( elseFilterElement != null ) {
1147                isAnElseFilter = true;
1148            }
1149    
1150            if ( ( filterElement != null ) && ( elseFilterElement != null ) ) {
1151                throw new XMLParsingException( "Element 'Rule' may contain a 'Filter'- or "
1152                                               + "an 'ElseFilter'-element, but not both!" );
1153            }
1154    
1155            // optional: <MinScaleDenominator>
1156            double min = XMLTools.getNodeAsDouble( element, "sld:MinScaleDenominator", nsContext, 0.0 );
1157            // optional: <MaxScaleDenominator>
1158            double max = XMLTools.getNodeAsDouble( element, "sld:MaxScaleDenominator", nsContext, 9E99 );
1159    
1160            // optional: different Symbolizer-elements
1161            NodeList symbolizerNL = element.getChildNodes();
1162            ArrayList<Symbolizer> symbolizerList = new ArrayList<Symbolizer>( symbolizerNL.getLength() );
1163    
1164            for ( int i = 0; i < symbolizerNL.getLength(); i++ ) {
1165                if ( symbolizerNL.item( i ) instanceof Element ) {
1166                    Element symbolizerElement = (Element) symbolizerNL.item( i );
1167                    String namespace = symbolizerElement.getNamespaceURI();
1168    
1169                    if ( !CommonNamespaces.SLDNS.toString().equals( namespace )
1170                         && !CommonNamespaces.SENS.toString().equals( namespace ) ) {
1171                        continue;
1172                    }
1173    
1174                    String symbolizerName = symbolizerElement.getLocalName();
1175    
1176                    if ( symbolizerName.equals( "LineSymbolizer" ) ) {
1177                        symbolizerList.add( createLineSymbolizer( symbolizerElement, min, max ) );
1178                    } else if ( symbolizerName.equals( "PointSymbolizer" ) ) {
1179                        symbolizerList.add( createPointSymbolizer( symbolizerElement, min, max ) );
1180                    } else if ( symbolizerName.equals( "PolygonSymbolizer" ) ) {
1181                        symbolizerList.add( createPolygonSymbolizer( symbolizerElement, min, max ) );
1182                    } else if ( symbolizerName.equals( "TextSymbolizer" ) ) {
1183                        symbolizerList.add( createTextSymbolizer( symbolizerElement, min, max ) );
1184                    } else if ( symbolizerName.equals( "RasterSymbolizer" ) ) {
1185                        symbolizerList.add( createRasterSymbolizer( symbolizerElement, min, max ) );
1186                    }
1187                }
1188            }
1189    
1190            Symbolizer[] symbolizers = symbolizerList.toArray( new Symbolizer[symbolizerList.size()] );
1191    
1192            return new Rule( symbolizers, name, title, abstract_, legendGraphic, filter, isAnElseFilter, min, max );
1193        }
1194    
1195        /**
1196         * Creates a <tt>PointSymbolizer</tt>-instance according to the contents of the DOM-subtree
1197         * starting at the given 'PointSymbolizer'-<tt>Element</tt>.
1198         * <p>
1199         * 
1200         * @param element
1201         *            the 'PointSymbolizer'-<tt>Element</tt>
1202         * @param min
1203         *            scale-constraint to be used
1204         * @param max
1205         *            scale-constraint to be used
1206         * @throws XMLParsingException
1207         *             if a syntactic or semantic error in the DOM-subtree is encountered
1208         * @return the constructed <tt>PointSymbolizer</tt>-instance
1209         */
1210        private static PointSymbolizer createPointSymbolizer( Element element, double min, double max )
1211                                throws XMLParsingException {
1212    
1213            // optional: <Geometry>
1214            Geometry geometry = null;
1215            Element geometryElement = XMLTools.getChildElement( "Geometry", CommonNamespaces.SLDNS, element );
1216    
1217            if ( geometryElement != null ) {
1218                geometry = createGeometry( geometryElement );
1219            }
1220    
1221            // optional: <Graphic>
1222            Graphic graphic = null;
1223            Element graphicElement = XMLTools.getChildElement( "Graphic", CommonNamespaces.SLDNS, element );
1224    
1225            if ( graphicElement != null ) {
1226                graphic = createGraphic( graphicElement );
1227            }
1228    
1229            PointSymbolizer ps = null;
1230            String respClass = XMLTools.getAttrValue( element, null, "responsibleClass", null );
1231            if ( respClass == null ) {
1232                ps = new PointSymbolizer( graphic, geometry, min, max );
1233            } else {
1234                ps = new PointSymbolizer( graphic, geometry, respClass, min, max );
1235            }
1236    
1237            return ps;
1238        }
1239    
1240        /**
1241         * Creates a <tt>LineSymbolizer</tt>-instance according to the contents of the DOM-subtree
1242         * starting at the given 'LineSymbolizer'-<tt>Element</tt>.
1243         * <p>
1244         * 
1245         * @param element
1246         *            the 'LineSymbolizer'-<tt>Element</tt>
1247         * @param min
1248         *            scale-constraint to be used
1249         * @param max
1250         *            scale-constraint to be used
1251         * @throws XMLParsingException
1252         *             if a syntactic or semantic error in the DOM-subtree is encountered
1253         * @return the constructed <tt>LineSymbolizer</tt>-instance
1254         */
1255        private static LineSymbolizer createLineSymbolizer( Element element, double min, double max )
1256                                throws XMLParsingException {
1257    
1258            // optional: <Geometry>
1259            Geometry geometry = null;
1260            Element geometryElement = XMLTools.getChildElement( "Geometry", CommonNamespaces.SLDNS, element );
1261    
1262            if ( geometryElement != null ) {
1263                geometry = createGeometry( geometryElement );
1264            }
1265    
1266            // optional: <Stroke>
1267            Stroke stroke = null;
1268            Element strokeElement = XMLTools.getChildElement( "Stroke", CommonNamespaces.SLDNS, element );
1269    
1270            if ( strokeElement != null ) {
1271                stroke = createStroke( strokeElement );
1272            }
1273    
1274            LineSymbolizer ls = null;
1275            String respClass = XMLTools.getAttrValue( element, null, "responsibleClass", null );
1276            if ( respClass == null ) {
1277                ls = new LineSymbolizer( stroke, geometry, min, max );
1278            } else {
1279                ls = new LineSymbolizer( stroke, geometry, respClass, min, max );
1280            }
1281            return ls;
1282        }
1283    
1284        /**
1285         * Creates a <tt>PolygonSymbolizer</tt>-instance according to the contents of the DOM-subtree
1286         * starting at the given 'PolygonSymbolizer'-<tt>Element</tt>.
1287         * <p>
1288         * 
1289         * @param element
1290         *            the 'PolygonSymbolizer'-<tt>Element</tt>
1291         * @param min
1292         *            scale-constraint to be used
1293         * @param max
1294         *            scale-constraint to be used
1295         * @throws XMLParsingException
1296         *             if a syntactic or semantic error in the DOM-subtree is encountered
1297         * @return the constructed <tt>PolygonSymbolizer</tt>-instance
1298         */
1299        private static PolygonSymbolizer createPolygonSymbolizer( Element element, double min, double max )
1300                                throws XMLParsingException {
1301            // optional: <Geometry>
1302            Geometry geometry = null;
1303            Element geometryElement = XMLTools.getChildElement( "Geometry", CommonNamespaces.SLDNS, element );
1304    
1305            if ( geometryElement != null ) {
1306                geometry = createGeometry( geometryElement );
1307            }
1308    
1309            // optional: <Fill>
1310            Fill fill = null;
1311            Element fillElement = XMLTools.getChildElement( "Fill", CommonNamespaces.SLDNS, element );
1312    
1313            if ( fillElement != null ) {
1314                fill = createFill( fillElement );
1315            }
1316    
1317            // optional: <Stroke>
1318            Stroke stroke = null;
1319            Element strokeElement = XMLTools.getChildElement( "Stroke", CommonNamespaces.SLDNS, element );
1320    
1321            if ( strokeElement != null ) {
1322                stroke = createStroke( strokeElement );
1323            }
1324    
1325            PolygonSymbolizer ps = null;
1326            String respClass = XMLTools.getAttrValue( element, null, "responsibleClass", null );
1327            if ( respClass == null ) {
1328                ps = new PolygonSymbolizer( fill, stroke, geometry, min, max );
1329            } else {
1330                ps = new PolygonSymbolizer( fill, stroke, geometry, respClass, min, max );
1331            }
1332    
1333            return ps;
1334        }
1335    
1336        /**
1337         * Creates a <tt>Geometry</tt>-instance according to the contents of the DOM-subtree starting
1338         * at the given 'Geometry'-<tt>Element</tt>.
1339         * <p>
1340         * FIXME: Add support for 'Function'-Elements.
1341         * <p>
1342         * 
1343         * @param element
1344         *            the 'Geometry'-<tt>Element</tt>
1345         * @throws XMLParsingException
1346         *             if a syntactic or semantic error in the DOM-subtree is encountered
1347         * @return the constructed <tt>Geometry</tt>-instance
1348         */
1349        private static Geometry createGeometry( Element element )
1350                                throws XMLParsingException {
1351            Geometry geometry = null;
1352    
1353            // required: <PropertyName>
1354            Element propertyNameElement = XMLTools.getRequiredChildElement( "PropertyName", ogcNS, element );
1355    
1356            // optional: <Function>
1357            Element functionElement = XMLTools.getChildElement( "Function", ogcNS, propertyNameElement );
1358    
1359            // just a property name exists
1360            if ( functionElement == null ) {
1361                Node node = XMLTools.getNode( propertyNameElement, "/text()", nsContext );
1362                PropertyPath pp = OGCDocument.parsePropertyPath( (Text) node );
1363                geometry = new Geometry( pp, null );
1364            } else {
1365                // FIXME:
1366                // the property probably contains a wfs:Function expression
1367            }
1368    
1369            return geometry;
1370        }
1371    
1372        /**
1373         * Creates a <tt>Fill</tt>-instance according to the contents of the DOM-subtree starting at
1374         * the given 'Fill'-<tt>Element</tt>.
1375         * <p>
1376         * 
1377         * @param element
1378         *            the 'Fill'-<tt>Element</tt>
1379         * @throws XMLParsingException
1380         *             if a syntactic or semantic error in the DOM-subtree is encountered
1381         * @return the constructed <tt>Fill</tt>-instance
1382         */
1383        private static Fill createFill( Element element )
1384                                throws XMLParsingException {
1385            // optional: <GraphicFill>
1386            GraphicFill graphicFill = null;
1387            Element graphicFillElement = XMLTools.getChildElement( "GraphicFill", CommonNamespaces.SLDNS, element );
1388    
1389            if ( graphicFillElement != null ) {
1390                graphicFill = createGraphicFill( graphicFillElement );
1391            }
1392    
1393            // optional: <CssParameter>s
1394            ElementList nl = XMLTools.getChildElements( "CssParameter", CommonNamespaces.SLDNS, element );
1395            HashMap<String, Object> cssParams = new HashMap<String, Object>( nl.getLength() );
1396    
1397            for ( int i = 0; i < nl.getLength(); i++ ) {
1398                CssParameter cssParam = createCssParameter( nl.item( i ) );
1399                cssParams.put( cssParam.getName(), cssParam );
1400            }
1401    
1402            return new Fill( cssParams, graphicFill );
1403        }
1404    
1405        /**
1406         * Creates a <tt>LegendGraphic</tt>-instance according to the contents of the DOM-subtree
1407         * starting at the given 'LegendGraphic'-element.
1408         * <p>
1409         * 
1410         * @param element
1411         *            the 'LegendGraphic'-<tt>Element</tt>
1412         * @throws XMLParsingException
1413         *             if a syntactic or semantic error in the DOM-subtree is encountered
1414         * @return the constructed <tt>Graphic</tt>-instance
1415         */
1416        private static LegendGraphic createLegendGraphic( Element element )
1417                                throws XMLParsingException {
1418            // required: <Graphic>
1419            Element graphicElement = XMLTools.getRequiredChildElement( "Graphic", CommonNamespaces.SLDNS, element );
1420            Graphic graphic = createGraphic( graphicElement );
1421    
1422            return new LegendGraphic( graphic );
1423        }
1424    
1425        /**
1426         * Creates an <tt>ExternalGraphic</tt>-instance according to the contents of the DOM-subtree
1427         * starting at the given 'ExternalGraphic'-<tt>Element</tt>.
1428         * <p>
1429         * 
1430         * @param element
1431         *            the 'ExternalGraphic'-<tt>Element</tt>
1432         * @throws XMLParsingException
1433         *             if a syntactic or semantic error in the DOM-subtree is encountered
1434         * @return the constructed <tt>ExternalGraphic</tt>-instance
1435         */
1436        private static ExternalGraphic createExternalGraphic( Element element )
1437                                throws XMLParsingException {
1438            // required: <OnlineResource>
1439            Element onlineResourceElement = XMLTools.getRequiredChildElement( "OnlineResource", CommonNamespaces.SLDNS,
1440                                                                              element );
1441    
1442            // required: href-Attribute (in <OnlineResource>)
1443            String href = XMLTools.getRequiredAttrValue( "href", xlnNS, onlineResourceElement );
1444            URL url = null;
1445            try {
1446                url = sldDoc.resolve( href );
1447            } catch ( MalformedURLException e ) {
1448                LOG.logDebug( e.getMessage(), e );
1449                throw new XMLParsingException( "Value ('" + href + "') of attribute 'href' of "
1450                                               + "element 'OnlineResoure' does not denote a valid URL" );
1451            }
1452    
1453            // required: <Format> (in <OnlineResource>)
1454            String format = XMLTools.getRequiredStringValue( "Format", CommonNamespaces.SLDNS, element );
1455    
1456            return new ExternalGraphic( format, url );
1457        }
1458    
1459        /**
1460         * Creates a <tt>Mark</tt>-instance according to the contents of the DOM-subtree starting at
1461         * the given 'Mark'-<tt>Element</tt>.
1462         * <p>
1463         * 
1464         * @param element
1465         *            the 'Mark'-<tt>Element</tt>
1466         * @throws XMLParsingException
1467         *             if a syntactic or semantic error in the DOM-subtree is encountered
1468         * @return the constructed <tt>Mark</tt>-instance
1469         */
1470        private static Mark createMark( Element element )
1471                                throws XMLParsingException {
1472            Stroke stroke = null;
1473            Fill fill = null;
1474    
1475            // optional: <WellKnownName>
1476            String wkn = XMLTools.getStringValue( "WellKnownName", CommonNamespaces.SLDNS, element, null );
1477    
1478            // optional: <Stroke>
1479            Element strokeElement = XMLTools.getChildElement( "Stroke", CommonNamespaces.SLDNS, element );
1480    
1481            if ( strokeElement != null ) {
1482                stroke = createStroke( strokeElement );
1483            }
1484    
1485            // optional: <Fill>
1486            Element fillElement = XMLTools.getChildElement( "Fill", CommonNamespaces.SLDNS, element );
1487    
1488            if ( fillElement != null ) {
1489                fill = createFill( fillElement );
1490            }
1491    
1492            return new Mark( wkn, stroke, fill );
1493        }
1494    
1495        /**
1496         * Creates a <tt>Stroke</tt>-instance according to the contents of the DOM-subtree starting
1497         * at the given 'Stroke'-<tt>Element</tt>.
1498         * <p>
1499         * 
1500         * @param element
1501         *            the 'Stroke'-<tt>Element</tt>
1502         * @throws XMLParsingException
1503         *             if a syntactic or semantic error in the DOM-subtree is encountered
1504         * @return the constructed <tt>Stroke</tt>-instance
1505         */
1506        private static Stroke createStroke( Element element )
1507                                throws XMLParsingException {
1508            GraphicFill gf = null;
1509            GraphicStroke gs = null;
1510    
1511            // optional: <GraphicFill>
1512            Element gfElement = XMLTools.getChildElement( "GraphicFill", CommonNamespaces.SLDNS, element );
1513    
1514            if ( gfElement != null ) {
1515                gf = createGraphicFill( gfElement );
1516            }
1517    
1518            // optional: <GraphicStroke>
1519            Element gsElement = XMLTools.getChildElement( "GraphicStroke", CommonNamespaces.SLDNS, element );
1520    
1521            if ( gsElement != null ) {
1522                gs = createGraphicStroke( gsElement );
1523            }
1524    
1525            // optional: <CssParameter>s
1526            ElementList nl = XMLTools.getChildElements( "CssParameter", CommonNamespaces.SLDNS, element );
1527            HashMap<String, Object> cssParams = new HashMap<String, Object>( nl.getLength() );
1528    
1529            for ( int i = 0; i < nl.getLength(); i++ ) {
1530                CssParameter cssParam = createCssParameter( nl.item( i ) );
1531                cssParams.put( cssParam.getName(), cssParam );
1532            }
1533    
1534            return new Stroke( cssParams, gs, gf );
1535        }
1536    
1537        /**
1538         * Creates a <tt>GraphicFill</tt>-instance according to the contents of the DOM-subtree
1539         * starting at the given 'GraphicFill'-<tt>Element</tt>.
1540         * <p>
1541         * 
1542         * @param element
1543         *            the 'GraphicFill'-<tt>Element</tt>
1544         * @throws XMLParsingException
1545         *             if a syntactic or semantic error in the DOM-subtree is encountered
1546         * @return the constructed <tt>GraphicFill</tt>-instance
1547         */
1548        private static GraphicFill createGraphicFill( Element element )
1549                                throws XMLParsingException {
1550            // required: <Graphic>
1551            Element graphicElement = XMLTools.getRequiredChildElement( "Graphic", CommonNamespaces.SLDNS, element );
1552            Graphic graphic = createGraphic( graphicElement );
1553    
1554            return new GraphicFill( graphic );
1555        }
1556    
1557        /**
1558         * Creates a <tt>GraphicStroke</tt>-instance according to the contents of the DOM-subtree
1559         * starting at the given 'GraphicStroke'-<tt>Element</tt>.
1560         * <p>
1561         * 
1562         * @param element
1563         *            the 'GraphicStroke'-<tt>Element</tt>
1564         * @throws XMLParsingException
1565         *             if a syntactic or semantic error in the DOM-subtree is encountered
1566         * @return the constructed <tt>GraphicStroke</tt>-instance
1567         */
1568        private static GraphicStroke createGraphicStroke( Element element )
1569                                throws XMLParsingException {
1570            // required: <Graphic>
1571            Element graphicElement = XMLTools.getRequiredChildElement( "Graphic", CommonNamespaces.SLDNS, element );
1572            Graphic graphic = createGraphic( graphicElement );
1573    
1574            return new GraphicStroke( graphic );
1575        }
1576    
1577        /**
1578         * Creates a <tt>Graphic</tt>-instance according to the contents of the DOM-subtree starting
1579         * at the given 'Graphic'-element.
1580         * <p>
1581         * 
1582         * @param element
1583         *            the 'Graphic'-<tt>Element</tt>
1584         * @throws XMLParsingException
1585         *             if a syntactic or semantic error in the DOM-subtree is encountered
1586         * @return the constructed <tt>Graphic</tt>-instance
1587         */
1588        private static Graphic createGraphic( Element element )
1589                                throws XMLParsingException {
1590    
1591            // optional: <Opacity>
1592            ParameterValueType opacity = null;
1593            // optional: <Size>
1594            ParameterValueType size = null;
1595            // optional: <Rotation>
1596            ParameterValueType rotation = null;
1597    
1598            // optional: <ExternalGraphic>s / <Mark>s
1599            NodeList nodelist = element.getChildNodes();
1600            ArrayList<Object> marksAndExtGraphicsList = new ArrayList<Object>();
1601    
1602            for ( int i = 0; i < nodelist.getLength(); i++ ) {
1603                if ( nodelist.item( i ) instanceof Element ) {
1604                    Element child = (Element) nodelist.item( i );
1605                    String namespace = child.getNamespaceURI();
1606    
1607                    if ( !CommonNamespaces.SLDNS.toString().equals( namespace ) ) {
1608                        continue;
1609                    }
1610    
1611                    String childName = child.getLocalName();
1612    
1613                    if ( childName.equals( "ExternalGraphic" ) ) {
1614                        marksAndExtGraphicsList.add( createExternalGraphic( child ) );
1615                    } else if ( childName.equals( "Mark" ) ) {
1616                        marksAndExtGraphicsList.add( createMark( child ) );
1617                    } else if ( childName.equals( "Opacity" ) ) {
1618                        opacity = createParameterValueType( child );
1619                    } else if ( childName.equals( "Size" ) ) {
1620                        size = createParameterValueType( child );
1621                    } else if ( childName.equals( "Rotation" ) ) {
1622                        rotation = createParameterValueType( child );
1623                    }
1624                }
1625            }
1626    
1627            Object[] marksAndExtGraphics = marksAndExtGraphicsList.toArray( new Object[marksAndExtGraphicsList.size()] );
1628    
1629            return new Graphic( marksAndExtGraphics, opacity, size, rotation );
1630        }
1631    
1632        /**
1633         * Creates a <tt>CssParameter</tt>-instance according to the contents of the DOM-subtree
1634         * starting at the given 'CssParameter'-<tt>Element</tt>.
1635         * <p>
1636         * 
1637         * @param element
1638         *            the 'CssParamter'-<tt>Element</tt>
1639         * @throws XMLParsingException
1640         *             if a syntactic or semantic error in the DOM-subtree is encountered
1641         * @return the constructed <tt>CssParameter</tt>-instance
1642         */
1643        private static CssParameter createCssParameter( Element element )
1644                                throws XMLParsingException {
1645            // required: name-Attribute
1646            String name = XMLTools.getRequiredAttrValue( "name", null, element );
1647            ParameterValueType pvt = createParameterValueType( element );
1648    
1649            return ( new CssParameter( name, pvt ) );
1650        }
1651    
1652        /**
1653         * <code>TresholdsBelongTo</code> enumerates values possibly belonging to
1654         * <code>ThreshholdsBelongToType</code>.
1655         * 
1656         * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
1657         * @author last edited by: $Author: aschmitz $
1658         * 
1659         * @version $Revision: 12138 $, $Date: 2008-06-04 10:34:15 +0200 (Mi, 04 Jun 2008) $
1660         */
1661        public enum ThresholdsBelongTo {
1662            /**
1663             * <code>"succeeding"</code>
1664             */
1665            SUCCEEDING,
1666            /**
1667             * <code>"preceding"</code>
1668             */
1669            PRECEDING
1670        }
1671    
1672        /**
1673         * <code>Mode</code> is the ModeType from the Symbology Encoding Schema.
1674         * 
1675         * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
1676         * @author last edited by: $Author: aschmitz $
1677         * 
1678         * @version $Revision: 12138 $, $Date: 2008-06-04 10:34:15 +0200 (Mi, 04 Jun 2008) $
1679         */
1680        public enum Mode {
1681            /**
1682             * <code>"linear"</code>
1683             */
1684            LINEAR,
1685            /**
1686             * <code>"cosine"</code>
1687             */
1688            COSINE,
1689            /**
1690             * <code>"cubic"</code>
1691             */
1692            CUBIC
1693        }
1694    
1695        /**
1696         * <code>Method</code> is the MethodType from the Symbology encoding Schema.
1697         * 
1698         * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
1699         * @author last edited by: $Author: aschmitz $
1700         * 
1701         * @version $Revision: 12138 $, $Date: 2008-06-04 10:34:15 +0200 (Mi, 04 Jun 2008) $
1702         */
1703        public enum Method {
1704            /**
1705             * <code>"numeric"</code>
1706             */
1707            NUMERIC,
1708            /**
1709             * <code>"color"</code>
1710             */
1711            COLOR
1712        }
1713    
1714    }