001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/graphics/sld/SLDFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003     
004     This file is part of deegree.
005     Copyright (C) 2001-2007 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: 7824 $ $Date: 2007-07-24 11:02:18 +0200 (Di, 24 Jul 2007) $
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.getRequiredNode( element, "sld:FeatureTypeName/text()",
899                                                  CommonNamespaces.getNamespaceContext() );
900    
901            QualifiedName name = XMLTools.getQualifiedNameValue( node );
902    
903            // optional: <Filter>
904            Filter filter = null;
905            Element filterElement = XMLTools.getChildElement( "Filter", ogcNS, element );
906    
907            if ( filterElement != null ) {
908                filter = AbstractFilter.buildFromDOM( filterElement );
909            }
910    
911            // optional: <Extent>(s)
912            ElementList nodelist = XMLTools.getChildElements( "Extent", CommonNamespaces.SLDNS, element );
913            Extent[] extents = new Extent[nodelist.getLength()];
914    
915            for ( int i = 0; i < nodelist.getLength(); i++ ) {
916                extents[i] = createExtent( nodelist.item( i ) );
917            }
918    
919            return new FeatureTypeConstraint( name, filter, extents );
920        }
921    
922        /**
923         * Creates an <tt>Extent</tt>-instance according to the contents of the DOM-subtree starting
924         * at the given 'Extent'-<tt>Element</tt>.
925         * <p>
926         * 
927         * @param element
928         *            the 'Extent'-<tt>Element</tt>
929         * @throws XMLParsingException
930         *             if a syntactic or semantic error in the DOM-subtree is encountered
931         * @return the constructed <tt>Extent</tt>-instance
932         */
933        private static Extent createExtent( Element element )
934                                throws XMLParsingException {
935            // required: <Name>
936            String name = XMLTools.getRequiredStringValue( "Name", CommonNamespaces.SLDNS, element );
937            // required: <Value>
938            String value = XMLTools.getRequiredStringValue( "Value", CommonNamespaces.SLDNS, element );
939    
940            return new Extent( name, value );
941        }
942    
943        /**
944         * Creates a <tt>LayerFeatureConstraints</tt>-instance according to the contents of the
945         * DOM-subtree starting at the given 'LayerFeatureConstraints'-<tt>Element</tt>.
946         * <p>
947         * 
948         * @param element
949         *            the 'LayerFeatureConstraints'-<tt>Element</tt>
950         * @throws XMLParsingException
951         *             if a syntactic or semantic error in the DOM-subtree is encountered
952         * @return the constructed <tt>LayerFeatureConstraints</tt>-instance
953         */
954        public static LayerFeatureConstraints createLayerFeatureConstraints( Element element )
955                                throws XMLParsingException {
956            // required: <FeatureTypeConstraint>(s)
957            ElementList nodelist = XMLTools.getChildElements( "FeatureTypeConstraint", CommonNamespaces.SLDNS, element );
958            FeatureTypeConstraint[] ftcs = new FeatureTypeConstraint[nodelist.getLength()];
959    
960            for ( int i = 0; i < nodelist.getLength(); i++ ) {
961                ftcs[i] = createFeatureTypeConstraint( nodelist.item( i ) );
962            }
963    
964            return new LayerFeatureConstraints( ftcs );
965        }
966    
967        /**
968         * Creates a <tt>UserStyle</tt>-instance according to the contents of the DOM-subtree
969         * starting at the given 'UserStyle'-<tt>Element</tt>.
970         * <p>
971         * 
972         * @param element
973         *            the 'UserStyle'-<tt>Element</tt>
974         * @throws XMLParsingException
975         *             if a syntactic or semantic error in the DOM-subtree is encountered
976         * @return the constructed <tt>UserStyle</tt>-instance
977         */
978        private static UserStyle createUserStyle( Element element )
979                                throws XMLParsingException {
980            // optional: <Name>
981            String name = XMLTools.getStringValue( "Name", CommonNamespaces.SLDNS, element, null );
982            // optional: <Title>
983            String title = XMLTools.getStringValue( "Title", CommonNamespaces.SLDNS, element, null );
984            // optional: <Abstract>
985            String abstract_ = XMLTools.getStringValue( "Abstract", CommonNamespaces.SLDNS, element, null );
986    
987            // optional: <IsDefault>
988            String defaultString = XMLTools.getStringValue( "IsDefault", CommonNamespaces.SLDNS, element, null );
989            boolean isDefault = false;
990    
991            if ( defaultString != null ) {
992                if ( defaultString.equals( "1" ) ) {
993                    isDefault = true;
994                }
995            }
996    
997            // required: <FeatureTypeStyle> (s)
998            ElementList nl = XMLTools.getChildElements( "FeatureTypeStyle", CommonNamespaces.SLDNS, element );
999            FeatureTypeStyle[] styles = new FeatureTypeStyle[nl.getLength()];
1000    
1001            if ( styles.length == 0 ) {
1002                throw new XMLParsingException( "Required child-element 'FeatureTypeStyle' of element "
1003                                               + "'UserStyle' is missing!" );
1004            }
1005    
1006            for ( int i = 0; i < nl.getLength(); i++ ) {
1007                styles[i] = createFeatureTypeStyle( nl.item( i ) );
1008            }
1009    
1010            return new UserStyle( name, title, abstract_, isDefault, styles );
1011        }
1012    
1013        /**
1014         * Creates a <tt>FeatureTypeStyle</tt>-instance according to the contents of the DOM-subtree
1015         * starting at the given 'FeatureTypeStyle'-<tt>Element</tt>.
1016         * <p>
1017         * TODO: The ElseFilter currently does not work correctly with FeatureFilters.
1018         * <p>
1019         * 
1020         * @param element
1021         *            the 'FeatureTypeStyle'-<tt>Element</tt>
1022         * @throws XMLParsingException
1023         *             if a syntactic or semantic error in the DOM-subtree is encountered
1024         * @return the constructed <tt>FeatureTypeStyle</tt>-instance
1025         */
1026        public static FeatureTypeStyle createFeatureTypeStyle( Element element )
1027                                throws XMLParsingException {
1028            // optional: <Name>
1029            String name = XMLTools.getStringValue( "Name", CommonNamespaces.SLDNS, element, null );
1030            // optional: <Title>
1031            String title = XMLTools.getStringValue( "Title", CommonNamespaces.SLDNS, element, null );
1032            // optional: <Abstract>
1033            String abstract_ = XMLTools.getStringValue( "Abstract", CommonNamespaces.SLDNS, element, null );
1034            // optional: <FeatureTypeName>
1035            String featureTypeName = XMLTools.getStringValue( "FeatureTypeName", CommonNamespaces.SLDNS, element, null );
1036    
1037            // optional: several <Rule> / <SemanticTypeIdentifier>
1038            NodeList nodelist = element.getChildNodes();
1039            ArrayList<Rule> ruleList = new ArrayList<Rule>();
1040            ArrayList<String> typeIdentifierList = new ArrayList<String>();
1041    
1042            // collect Filters of all Rules
1043            ArrayList<Filter> filters = new ArrayList<Filter>();
1044            // collect all Rules that have an ElseFilter
1045            ArrayList<Rule> elseRules = new ArrayList<Rule>();
1046    
1047            for ( int i = 0; i < nodelist.getLength(); i++ ) {
1048                if ( nodelist.item( i ) instanceof Element ) {
1049                    Element child = (Element) nodelist.item( i );
1050                    String namespace = child.getNamespaceURI();
1051                    if ( !CommonNamespaces.SLDNS.toString().equals( namespace ) ) {
1052                        continue;
1053                    }
1054    
1055                    String childName = child.getLocalName();
1056    
1057                    if ( childName.equals( "Rule" ) ) {
1058                        Rule rule = createRule( child );
1059                        if ( rule.hasElseFilter() ) {
1060                            elseRules.add( rule );
1061                        } else if ( rule.getFilter() == null || rule.getFilter() instanceof ComplexFilter ) {
1062                            filters.add( rule.getFilter() );
1063                        }
1064                        ruleList.add( rule );
1065                    } else if ( childName.equals( "SemanticTypeIdentifier" ) ) {
1066                        typeIdentifierList.add( XMLTools.getStringValue( child ) );
1067                    }
1068                }
1069            }
1070    
1071            // compute and set the ElseFilter for all ElseFilter-Rules
1072            Filter elseFilter = null;
1073            // a Rule exists with no Filter at all -> elseFilter = false
1074            if ( filters.contains( null ) ) {
1075                elseFilter = new FalseFilter();
1076                // one Rule with a Filter exists -> elseFilter = NOT Filter
1077            } else if ( filters.size() == 1 ) {
1078                elseFilter = new ComplexFilter( OperationDefines.NOT );
1079                List<Operation> arguments = ( (LogicalOperation) ( (ComplexFilter) elseFilter ).getOperation() ).getArguments();
1080                ComplexFilter complexFilter = (ComplexFilter) filters.get( 0 );
1081                arguments.add( complexFilter.getOperation() );
1082                // several Rules with Filters exist -> elseFilter = NOT (Filter1 OR Filter2 OR...)
1083            } else if ( filters.size() > 1 ) {
1084                ComplexFilter innerFilter = new ComplexFilter( OperationDefines.OR );
1085                elseFilter = new ComplexFilter( innerFilter, null, OperationDefines.NOT );
1086                List<Operation> arguments = ( (LogicalOperation) innerFilter.getOperation() ).getArguments();
1087                Iterator it = filters.iterator();
1088                while ( it.hasNext() ) {
1089                    ComplexFilter complexFilter = (ComplexFilter) it.next();
1090                    arguments.add( complexFilter.getOperation() );
1091                }
1092            }
1093            Iterator it = elseRules.iterator();
1094            while ( it.hasNext() ) {
1095                ( (Rule) it.next() ).setFilter( elseFilter );
1096            }
1097    
1098            Rule[] rules = ruleList.toArray( new Rule[ruleList.size()] );
1099            String[] typeIdentifiers = typeIdentifierList.toArray( new String[typeIdentifierList.size()] );
1100    
1101            return new FeatureTypeStyle( name, title, abstract_, featureTypeName, typeIdentifiers, rules );
1102        }
1103    
1104        /**
1105         * Creates a <tt>Rule</tt>-instance according to the contents of the DOM-subtree starting at
1106         * the given 'Rule'-<tt>Element</tt>.
1107         * <p>
1108         * 
1109         * @param element
1110         *            the 'Rule'-<tt>Element</tt>
1111         * @throws XMLParsingException
1112         *             if a syntactic or semantic error in the DOM-subtree is encountered
1113         * @return the constructed <tt>Rule</tt>-instance
1114         */
1115        private static Rule createRule( Element element )
1116                                throws XMLParsingException {
1117            // optional: <Name>
1118            String name = XMLTools.getStringValue( "Name", CommonNamespaces.SLDNS, element, null );
1119            // optional: <Title>
1120            String title = XMLTools.getStringValue( "Title", CommonNamespaces.SLDNS, element, null );
1121            // optional: <Abstract>
1122            String abstract_ = XMLTools.getStringValue( "Abstract", CommonNamespaces.SLDNS, element, null );
1123    
1124            // optional: <LegendGraphic>
1125            LegendGraphic legendGraphic = null;
1126            Element legendGraphicElement = XMLTools.getChildElement( "LegendGraphic", CommonNamespaces.SLDNS, element );
1127    
1128            if ( legendGraphicElement != null ) {
1129                legendGraphic = createLegendGraphic( legendGraphicElement );
1130            }
1131    
1132            // optional: <Filter>
1133            boolean isAnElseFilter = false;
1134            Filter filter = null;
1135            Element filterElement = XMLTools.getChildElement( "Filter", ogcNS, element );
1136            if ( filterElement != null ) {
1137                filter = AbstractFilter.buildFromDOM( filterElement );
1138            }
1139    
1140            // optional: <ElseFilter>
1141            Element elseFilterElement = XMLTools.getChildElement( "ElseFilter", CommonNamespaces.SLDNS, element );
1142            if ( elseFilterElement != null ) {
1143                isAnElseFilter = true;
1144            }
1145    
1146            if ( ( filterElement != null ) && ( elseFilterElement != null ) ) {
1147                throw new XMLParsingException( "Element 'Rule' may contain a 'Filter'- or "
1148                                               + "an 'ElseFilter'-element, but not both!" );
1149            }
1150    
1151            // optional: <MinScaleDenominator>
1152            double min = XMLTools.getNodeAsDouble( element, "sld:MinScaleDenominator", nsContext, 0.0 );
1153            // optional: <MaxScaleDenominator>
1154            double max = XMLTools.getNodeAsDouble( element, "sld:MaxScaleDenominator", nsContext, 9E99 );
1155    
1156            // optional: different Symbolizer-elements
1157            NodeList symbolizerNL = element.getChildNodes();
1158            ArrayList<Symbolizer> symbolizerList = new ArrayList<Symbolizer>( symbolizerNL.getLength() );
1159    
1160            for ( int i = 0; i < symbolizerNL.getLength(); i++ ) {
1161                if ( symbolizerNL.item( i ) instanceof Element ) {
1162                    Element symbolizerElement = (Element) symbolizerNL.item( i );
1163                    String namespace = symbolizerElement.getNamespaceURI();
1164    
1165                    if ( !CommonNamespaces.SLDNS.toString().equals( namespace )
1166                         && !CommonNamespaces.SENS.toString().equals( namespace ) ) {
1167                        continue;
1168                    }
1169    
1170                    String symbolizerName = symbolizerElement.getLocalName();
1171    
1172                    if ( symbolizerName.equals( "LineSymbolizer" ) ) {
1173                        symbolizerList.add( createLineSymbolizer( symbolizerElement, min, max ) );
1174                    } else if ( symbolizerName.equals( "PointSymbolizer" ) ) {
1175                        symbolizerList.add( createPointSymbolizer( symbolizerElement, min, max ) );
1176                    } else if ( symbolizerName.equals( "PolygonSymbolizer" ) ) {
1177                        symbolizerList.add( createPolygonSymbolizer( symbolizerElement, min, max ) );
1178                    } else if ( symbolizerName.equals( "TextSymbolizer" ) ) {
1179                        symbolizerList.add( createTextSymbolizer( symbolizerElement, min, max ) );
1180                    } else if ( symbolizerName.equals( "RasterSymbolizer" ) ) {
1181                        symbolizerList.add( createRasterSymbolizer( symbolizerElement, min, max ) );
1182                    }
1183                }
1184            }
1185    
1186            Symbolizer[] symbolizers = symbolizerList.toArray( new Symbolizer[symbolizerList.size()] );
1187    
1188            return new Rule( symbolizers, name, title, abstract_, legendGraphic, filter, isAnElseFilter, min, max );
1189        }
1190    
1191        /**
1192         * Creates a <tt>PointSymbolizer</tt>-instance according to the contents of the DOM-subtree
1193         * starting at the given 'PointSymbolizer'-<tt>Element</tt>.
1194         * <p>
1195         * 
1196         * @param element
1197         *            the 'PointSymbolizer'-<tt>Element</tt>
1198         * @param min
1199         *            scale-constraint to be used
1200         * @param max
1201         *            scale-constraint to be used
1202         * @throws XMLParsingException
1203         *             if a syntactic or semantic error in the DOM-subtree is encountered
1204         * @return the constructed <tt>PointSymbolizer</tt>-instance
1205         */
1206        private static PointSymbolizer createPointSymbolizer( Element element, double min, double max )
1207                                throws XMLParsingException {
1208    
1209            // optional: <Geometry>
1210            Geometry geometry = null;
1211            Element geometryElement = XMLTools.getChildElement( "Geometry", CommonNamespaces.SLDNS, element );
1212    
1213            if ( geometryElement != null ) {
1214                geometry = createGeometry( geometryElement );
1215            }
1216    
1217            // optional: <Graphic>
1218            Graphic graphic = null;
1219            Element graphicElement = XMLTools.getChildElement( "Graphic", CommonNamespaces.SLDNS, element );
1220    
1221            if ( graphicElement != null ) {
1222                graphic = createGraphic( graphicElement );
1223            }
1224    
1225            PointSymbolizer ps = null;
1226            String respClass = XMLTools.getAttrValue( element, null, "responsibleClass", null );
1227            if ( respClass == null ) {
1228                ps = new PointSymbolizer( graphic, geometry, min, max );
1229            } else {
1230                ps = new PointSymbolizer( graphic, geometry, respClass, min, max );
1231            }
1232    
1233            return ps;
1234        }
1235    
1236        /**
1237         * Creates a <tt>LineSymbolizer</tt>-instance according to the contents of the DOM-subtree
1238         * starting at the given 'LineSymbolizer'-<tt>Element</tt>.
1239         * <p>
1240         * 
1241         * @param element
1242         *            the 'LineSymbolizer'-<tt>Element</tt>
1243         * @param min
1244         *            scale-constraint to be used
1245         * @param max
1246         *            scale-constraint to be used
1247         * @throws XMLParsingException
1248         *             if a syntactic or semantic error in the DOM-subtree is encountered
1249         * @return the constructed <tt>LineSymbolizer</tt>-instance
1250         */
1251        private static LineSymbolizer createLineSymbolizer( Element element, double min, double max )
1252                                throws XMLParsingException {
1253    
1254            // optional: <Geometry>
1255            Geometry geometry = null;
1256            Element geometryElement = XMLTools.getChildElement( "Geometry", CommonNamespaces.SLDNS, element );
1257    
1258            if ( geometryElement != null ) {
1259                geometry = createGeometry( geometryElement );
1260            }
1261    
1262            // optional: <Stroke>
1263            Stroke stroke = null;
1264            Element strokeElement = XMLTools.getChildElement( "Stroke", CommonNamespaces.SLDNS, element );
1265    
1266            if ( strokeElement != null ) {
1267                stroke = createStroke( strokeElement );
1268            }
1269    
1270            LineSymbolizer ls = null;
1271            String respClass = XMLTools.getAttrValue( element, null, "responsibleClass", null );
1272            if ( respClass == null ) {
1273                ls = new LineSymbolizer( stroke, geometry, min, max );
1274            } else {
1275                ls = new LineSymbolizer( stroke, geometry, respClass, min, max );
1276            }
1277            return ls;
1278        }
1279    
1280        /**
1281         * Creates a <tt>PolygonSymbolizer</tt>-instance according to the contents of the DOM-subtree
1282         * starting at the given 'PolygonSymbolizer'-<tt>Element</tt>.
1283         * <p>
1284         * 
1285         * @param element
1286         *            the 'PolygonSymbolizer'-<tt>Element</tt>
1287         * @param min
1288         *            scale-constraint to be used
1289         * @param max
1290         *            scale-constraint to be used
1291         * @throws XMLParsingException
1292         *             if a syntactic or semantic error in the DOM-subtree is encountered
1293         * @return the constructed <tt>PolygonSymbolizer</tt>-instance
1294         */
1295        private static PolygonSymbolizer createPolygonSymbolizer( Element element, double min, double max )
1296                                throws XMLParsingException {
1297            // optional: <Geometry>
1298            Geometry geometry = null;
1299            Element geometryElement = XMLTools.getChildElement( "Geometry", CommonNamespaces.SLDNS, element );
1300    
1301            if ( geometryElement != null ) {
1302                geometry = createGeometry( geometryElement );
1303            }
1304    
1305            // optional: <Fill>
1306            Fill fill = null;
1307            Element fillElement = XMLTools.getChildElement( "Fill", CommonNamespaces.SLDNS, element );
1308    
1309            if ( fillElement != null ) {
1310                fill = createFill( fillElement );
1311            }
1312    
1313            // optional: <Stroke>
1314            Stroke stroke = null;
1315            Element strokeElement = XMLTools.getChildElement( "Stroke", CommonNamespaces.SLDNS, element );
1316    
1317            if ( strokeElement != null ) {
1318                stroke = createStroke( strokeElement );
1319            }
1320    
1321            PolygonSymbolizer ps = null;
1322            String respClass = XMLTools.getAttrValue( element, null, "responsibleClass", null );
1323            if ( respClass == null ) {
1324                ps = new PolygonSymbolizer( fill, stroke, geometry, min, max );
1325            } else {
1326                ps = new PolygonSymbolizer( fill, stroke, geometry, respClass, min, max );
1327            }
1328    
1329            return ps;
1330        }
1331    
1332        /**
1333         * Creates a <tt>Geometry</tt>-instance according to the contents of the DOM-subtree starting
1334         * at the given 'Geometry'-<tt>Element</tt>.
1335         * <p>
1336         * FIXME: Add support for 'Function'-Elements.
1337         * <p>
1338         * 
1339         * @param element
1340         *            the 'Geometry'-<tt>Element</tt>
1341         * @throws XMLParsingException
1342         *             if a syntactic or semantic error in the DOM-subtree is encountered
1343         * @return the constructed <tt>Geometry</tt>-instance
1344         */
1345        private static Geometry createGeometry( Element element )
1346                                throws XMLParsingException {
1347            Geometry geometry = null;
1348    
1349            // required: <PropertyName>
1350            Element propertyNameElement = XMLTools.getRequiredChildElement( "PropertyName", ogcNS, element );
1351    
1352            // optional: <Function>
1353            Element functionElement = XMLTools.getChildElement( "Function", ogcNS, propertyNameElement );
1354    
1355            // just a property name exists
1356            if ( functionElement == null ) {
1357                Node node = XMLTools.getNode( propertyNameElement, "/text()", nsContext );
1358                PropertyPath pp = OGCDocument.parsePropertyPath( (Text) node );
1359                geometry = new Geometry( pp, null );
1360            } else {
1361                // FIXME:
1362                // the property probably contains a wfs:Function expression
1363            }
1364    
1365            return geometry;
1366        }
1367    
1368        /**
1369         * Creates a <tt>Fill</tt>-instance according to the contents of the DOM-subtree starting at
1370         * the given 'Fill'-<tt>Element</tt>.
1371         * <p>
1372         * 
1373         * @param element
1374         *            the 'Fill'-<tt>Element</tt>
1375         * @throws XMLParsingException
1376         *             if a syntactic or semantic error in the DOM-subtree is encountered
1377         * @return the constructed <tt>Fill</tt>-instance
1378         */
1379        private static Fill createFill( Element element )
1380                                throws XMLParsingException {
1381            // optional: <GraphicFill>
1382            GraphicFill graphicFill = null;
1383            Element graphicFillElement = XMLTools.getChildElement( "GraphicFill", CommonNamespaces.SLDNS, element );
1384    
1385            if ( graphicFillElement != null ) {
1386                graphicFill = createGraphicFill( graphicFillElement );
1387            }
1388    
1389            // optional: <CssParameter>s
1390            ElementList nl = XMLTools.getChildElements( "CssParameter", CommonNamespaces.SLDNS, element );
1391            HashMap<String, Object> cssParams = new HashMap<String, Object>( nl.getLength() );
1392    
1393            for ( int i = 0; i < nl.getLength(); i++ ) {
1394                CssParameter cssParam = createCssParameter( nl.item( i ) );
1395                cssParams.put( cssParam.getName(), cssParam );
1396            }
1397    
1398            return new Fill( cssParams, graphicFill );
1399        }
1400    
1401        /**
1402         * Creates a <tt>LegendGraphic</tt>-instance according to the contents of the DOM-subtree
1403         * starting at the given 'LegendGraphic'-element.
1404         * <p>
1405         * 
1406         * @param element
1407         *            the 'LegendGraphic'-<tt>Element</tt>
1408         * @throws XMLParsingException
1409         *             if a syntactic or semantic error in the DOM-subtree is encountered
1410         * @return the constructed <tt>Graphic</tt>-instance
1411         */
1412        private static LegendGraphic createLegendGraphic( Element element )
1413                                throws XMLParsingException {
1414            // required: <Graphic>
1415            Element graphicElement = XMLTools.getRequiredChildElement( "Graphic", CommonNamespaces.SLDNS, element );
1416            Graphic graphic = createGraphic( graphicElement );
1417    
1418            return new LegendGraphic( graphic );
1419        }
1420    
1421        /**
1422         * Creates an <tt>ExternalGraphic</tt>-instance according to the contents of the DOM-subtree
1423         * starting at the given 'ExternalGraphic'-<tt>Element</tt>.
1424         * <p>
1425         * 
1426         * @param element
1427         *            the 'ExternalGraphic'-<tt>Element</tt>
1428         * @throws XMLParsingException
1429         *             if a syntactic or semantic error in the DOM-subtree is encountered
1430         * @return the constructed <tt>ExternalGraphic</tt>-instance
1431         */
1432        private static ExternalGraphic createExternalGraphic( Element element )
1433                                throws XMLParsingException {
1434            // required: <OnlineResource>
1435            Element onlineResourceElement = XMLTools.getRequiredChildElement( "OnlineResource", CommonNamespaces.SLDNS,
1436                                                                              element );
1437    
1438            // required: href-Attribute (in <OnlineResource>)
1439            String href = XMLTools.getRequiredAttrValue( "href", xlnNS, onlineResourceElement );
1440            URL url = null;
1441            try {
1442                url = sldDoc.resolve( href );
1443            } catch ( MalformedURLException e ) {
1444                LOG.logDebug( e.getMessage(), e );
1445                throw new XMLParsingException( "Value ('" + href + "') of attribute 'href' of "
1446                                               + "element 'OnlineResoure' does not denote a valid URL" );
1447            }
1448    
1449            // required: <Format> (in <OnlineResource>)
1450            String format = XMLTools.getRequiredStringValue( "Format", CommonNamespaces.SLDNS, element );
1451    
1452            return new ExternalGraphic( format, url );
1453        }
1454    
1455        /**
1456         * Creates a <tt>Mark</tt>-instance according to the contents of the DOM-subtree starting at
1457         * the given 'Mark'-<tt>Element</tt>.
1458         * <p>
1459         * 
1460         * @param element
1461         *            the 'Mark'-<tt>Element</tt>
1462         * @throws XMLParsingException
1463         *             if a syntactic or semantic error in the DOM-subtree is encountered
1464         * @return the constructed <tt>Mark</tt>-instance
1465         */
1466        private static Mark createMark( Element element )
1467                                throws XMLParsingException {
1468            Stroke stroke = null;
1469            Fill fill = null;
1470    
1471            // optional: <WellKnownName>
1472            String wkn = XMLTools.getStringValue( "WellKnownName", CommonNamespaces.SLDNS, element, null );
1473    
1474            // optional: <Stroke>
1475            Element strokeElement = XMLTools.getChildElement( "Stroke", CommonNamespaces.SLDNS, element );
1476    
1477            if ( strokeElement != null ) {
1478                stroke = createStroke( strokeElement );
1479            }
1480    
1481            // optional: <Fill>
1482            Element fillElement = XMLTools.getChildElement( "Fill", CommonNamespaces.SLDNS, element );
1483    
1484            if ( fillElement != null ) {
1485                fill = createFill( fillElement );
1486            }
1487    
1488            return new Mark( wkn, stroke, fill );
1489        }
1490    
1491        /**
1492         * Creates a <tt>Stroke</tt>-instance according to the contents of the DOM-subtree starting
1493         * at the given 'Stroke'-<tt>Element</tt>.
1494         * <p>
1495         * 
1496         * @param element
1497         *            the 'Stroke'-<tt>Element</tt>
1498         * @throws XMLParsingException
1499         *             if a syntactic or semantic error in the DOM-subtree is encountered
1500         * @return the constructed <tt>Stroke</tt>-instance
1501         */
1502        private static Stroke createStroke( Element element )
1503                                throws XMLParsingException {
1504            GraphicFill gf = null;
1505            GraphicStroke gs = null;
1506    
1507            // optional: <GraphicFill>
1508            Element gfElement = XMLTools.getChildElement( "GraphicFill", CommonNamespaces.SLDNS, element );
1509    
1510            if ( gfElement != null ) {
1511                gf = createGraphicFill( gfElement );
1512            }
1513    
1514            // optional: <GraphicStroke>
1515            Element gsElement = XMLTools.getChildElement( "GraphicStroke", CommonNamespaces.SLDNS, element );
1516    
1517            if ( gsElement != null ) {
1518                gs = createGraphicStroke( gsElement );
1519            }
1520    
1521            // optional: <CssParameter>s
1522            ElementList nl = XMLTools.getChildElements( "CssParameter", CommonNamespaces.SLDNS, element );
1523            HashMap<String, Object> cssParams = new HashMap<String, Object>( nl.getLength() );
1524    
1525            for ( int i = 0; i < nl.getLength(); i++ ) {
1526                CssParameter cssParam = createCssParameter( nl.item( i ) );
1527                cssParams.put( cssParam.getName(), cssParam );
1528            }
1529    
1530            return new Stroke( cssParams, gs, gf );
1531        }
1532    
1533        /**
1534         * Creates a <tt>GraphicFill</tt>-instance according to the contents of the DOM-subtree
1535         * starting at the given 'GraphicFill'-<tt>Element</tt>.
1536         * <p>
1537         * 
1538         * @param element
1539         *            the 'GraphicFill'-<tt>Element</tt>
1540         * @throws XMLParsingException
1541         *             if a syntactic or semantic error in the DOM-subtree is encountered
1542         * @return the constructed <tt>GraphicFill</tt>-instance
1543         */
1544        private static GraphicFill createGraphicFill( Element element )
1545                                throws XMLParsingException {
1546            // required: <Graphic>
1547            Element graphicElement = XMLTools.getRequiredChildElement( "Graphic", CommonNamespaces.SLDNS, element );
1548            Graphic graphic = createGraphic( graphicElement );
1549    
1550            return new GraphicFill( graphic );
1551        }
1552    
1553        /**
1554         * Creates a <tt>GraphicStroke</tt>-instance according to the contents of the DOM-subtree
1555         * starting at the given 'GraphicStroke'-<tt>Element</tt>.
1556         * <p>
1557         * 
1558         * @param element
1559         *            the 'GraphicStroke'-<tt>Element</tt>
1560         * @throws XMLParsingException
1561         *             if a syntactic or semantic error in the DOM-subtree is encountered
1562         * @return the constructed <tt>GraphicStroke</tt>-instance
1563         */
1564        private static GraphicStroke createGraphicStroke( Element element )
1565                                throws XMLParsingException {
1566            // required: <Graphic>
1567            Element graphicElement = XMLTools.getRequiredChildElement( "Graphic", CommonNamespaces.SLDNS, element );
1568            Graphic graphic = createGraphic( graphicElement );
1569    
1570            return new GraphicStroke( graphic );
1571        }
1572    
1573        /**
1574         * Creates a <tt>Graphic</tt>-instance according to the contents of the DOM-subtree starting
1575         * at the given 'Graphic'-element.
1576         * <p>
1577         * 
1578         * @param element
1579         *            the 'Graphic'-<tt>Element</tt>
1580         * @throws XMLParsingException
1581         *             if a syntactic or semantic error in the DOM-subtree is encountered
1582         * @return the constructed <tt>Graphic</tt>-instance
1583         */
1584        private static Graphic createGraphic( Element element )
1585                                throws XMLParsingException {
1586    
1587            // optional: <Opacity>
1588            ParameterValueType opacity = null;
1589            // optional: <Size>
1590            ParameterValueType size = null;
1591            // optional: <Rotation>
1592            ParameterValueType rotation = null;
1593    
1594            // optional: <ExternalGraphic>s / <Mark>s
1595            NodeList nodelist = element.getChildNodes();
1596            ArrayList<Object> marksAndExtGraphicsList = new ArrayList<Object>();
1597    
1598            for ( int i = 0; i < nodelist.getLength(); i++ ) {
1599                if ( nodelist.item( i ) instanceof Element ) {
1600                    Element child = (Element) nodelist.item( i );
1601                    String namespace = child.getNamespaceURI();
1602    
1603                    if ( !CommonNamespaces.SLDNS.toString().equals( namespace ) ) {
1604                        continue;
1605                    }
1606    
1607                    String childName = child.getLocalName();
1608    
1609                    if ( childName.equals( "ExternalGraphic" ) ) {
1610                        marksAndExtGraphicsList.add( createExternalGraphic( child ) );
1611                    } else if ( childName.equals( "Mark" ) ) {
1612                        marksAndExtGraphicsList.add( createMark( child ) );
1613                    } else if ( childName.equals( "Opacity" ) ) {
1614                        opacity = createParameterValueType( child );
1615                    } else if ( childName.equals( "Size" ) ) {
1616                        size = createParameterValueType( child );
1617                    } else if ( childName.equals( "Rotation" ) ) {
1618                        rotation = createParameterValueType( child );
1619                    }
1620                }
1621            }
1622    
1623            Object[] marksAndExtGraphics = marksAndExtGraphicsList.toArray( new Object[marksAndExtGraphicsList.size()] );
1624    
1625            return new Graphic( marksAndExtGraphics, opacity, size, rotation );
1626        }
1627    
1628        /**
1629         * Creates a <tt>CssParameter</tt>-instance according to the contents of the DOM-subtree
1630         * starting at the given 'CssParameter'-<tt>Element</tt>.
1631         * <p>
1632         * 
1633         * @param element
1634         *            the 'CssParamter'-<tt>Element</tt>
1635         * @throws XMLParsingException
1636         *             if a syntactic or semantic error in the DOM-subtree is encountered
1637         * @return the constructed <tt>CssParameter</tt>-instance
1638         */
1639        private static CssParameter createCssParameter( Element element )
1640                                throws XMLParsingException {
1641            // required: name-Attribute
1642            String name = XMLTools.getRequiredAttrValue( "name", null, element );
1643            ParameterValueType pvt = createParameterValueType( element );
1644    
1645            return ( new CssParameter( name, pvt ) );
1646        }
1647    
1648        /**
1649         * <code>TresholdsBelongTo</code> enumerates values possibly belonging to
1650         * <code>ThreshholdsBelongToType</code>.
1651         * 
1652         * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
1653         * @author last edited by: $Author: aschmitz $
1654         * 
1655         * @version $Revision: 7824 $, $Date: 2007-07-24 11:02:18 +0200 (Di, 24 Jul 2007) $
1656         */
1657        public enum ThresholdsBelongTo {
1658            /**
1659             * <code>"succeeding"</code>
1660             */
1661            SUCCEEDING,
1662            /**
1663             * <code>"preceding"</code>
1664             */
1665            PRECEDING
1666        }
1667    
1668        /**
1669         * <code>Mode</code> is the ModeType from the Symbology Encoding Schema.
1670         * 
1671         * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
1672         * @author last edited by: $Author: aschmitz $
1673         * 
1674         * @version $Revision: 7824 $, $Date: 2007-07-24 11:02:18 +0200 (Di, 24 Jul 2007) $
1675         */
1676        public enum Mode {
1677            /**
1678             * <code>"linear"</code>
1679             */
1680            LINEAR,
1681            /**
1682             * <code>"cosine"</code>
1683             */
1684            COSINE,
1685            /**
1686             * <code>"cubic"</code>
1687             */
1688            CUBIC
1689        }
1690    
1691        /**
1692         * <code>Method</code> is the MethodType from the Symbology encoding Schema.
1693         * 
1694         * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
1695         * @author last edited by: $Author: aschmitz $
1696         * 
1697         * @version $Revision: 7824 $, $Date: 2007-07-24 11:02:18 +0200 (Di, 24 Jul 2007) $
1698         */
1699        public enum Method {
1700            /**
1701             * <code>"numeric"</code>
1702             */
1703            NUMERIC,
1704            /**
1705             * <code>"color"</code>
1706             */
1707            COLOR
1708        }
1709    
1710    }