OpenLayers OpenLayers

Changeset 4206

Show
Ignore:
Timestamp:
09/10/07 18:02:18 (1 year ago)
Author:
tschaub
Message:

GML format rewrite - now subclasses from XML format. Refactored code to get ~2-3x improvement in parsing time. Thanks for all the tests crschmidt (closes #938).

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/openlayers/lib/OpenLayers/Format/GML.js

    r4110 r4206  
    44 
    55/** 
    6  * @requires OpenLayers/Format.js 
     6 * @requires OpenLayers/Format/XML.js 
    77 * @requires OpenLayers/Feature/Vector.js 
    8  * @requires OpenLayers/Ajax.js 
    98 * @requires OpenLayers/Geometry.js 
    109 * 
    1110 * Class: OpenLayers.Format.GML 
    1211 * Read/Wite GML. Create a new instance with the <OpenLayers.Format.GML> 
    13  *     constructor. 
     12 *     constructor.  Supports the GML simple features profile. 
    1413 *  
    1514 * Inherits from: 
    1615 *  - <OpenLayers.Format> 
    1716 */ 
    18 OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format, { 
     17OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, { 
    1918     
    2019    /* 
    2120     * APIProperty: featureNS 
    22      * Namespace used for feature attributes. Default matches the NS 
    23      * used by MapServer output
     21     * {String} Namespace used for feature attributes.  Default is 
     22     *     "http://mapserver.gis.umn.edu/mapserver"
    2423     */ 
    2524    featureNS: "http://mapserver.gis.umn.edu/mapserver", 
     25     
     26    /** 
     27     * APIProperty: featurePrefix 
     28     * {String} Namespace alias (or prefix) for feature nodes.  Default is 
     29     *     "feature". 
     30     */ 
     31    featurePrefix: "feature", 
    2632     
    2733    /* 
    2834     * APIProperty: featureName 
    29      * element name for features. Default is 'featureMember'
     35     * {String} Element name for features. Default is "featureMember"
    3036     */ 
    3137    featureName: "featureMember",  
     
    3339    /* 
    3440     * APIProperty: layerName 
    35      * Name of data layer. Default is 'features'. 
    36      */ 
    37       
     41     * {String} Name of data layer. Default is "features". 
     42     */ 
    3843    layerName: "features", 
    3944     
    4045    /** 
    4146     * APIProperty: geometry 
    42      * Name of geometry element
     47     * {String} Name of geometry element.  Defaults to "geometry"
    4348     */ 
    4449    geometryName: "geometry", 
     
    4651    /**  
    4752     * APIProperty: collectionName 
    48      * Name of featureCollection element 
     53     * {String} Name of featureCollection element. 
    4954     */ 
    5055    collectionName: "FeatureCollection", 
     
    5257    /** 
    5358     * APIProperty: gmlns 
    54      * GML Namespace 
     59     * {String} GML Namespace. 
    5560     */ 
    5661    gmlns: "http://www.opengis.net/gml", 
    57      
    5862 
    5963    /** 
    6064     * APIProperty: extractAttributes 
    61      * {Boolean} Extract attributes from GML. Most of the time, this is a 
    62      * significant time usage, due to the need to recursively descend the XML 
    63      * to search for attributes. 
     65     * {Boolean} Extract attributes from GML. 
    6466     */ 
    6567    extractAttributes: true, 
    6668     
    67      
    6869    /** 
    6970     * Constructor: OpenLayers.Format.GML 
    70      * Create a new parser for GML 
     71     * Create a new parser for GML. 
    7172     * 
    7273     * Parameters: 
    7374     * options - {Object} An optional object whose properties will be set on 
    74      *                    this instance. 
     75     *     this instance. 
    7576     */ 
    7677    initialize: function(options) { 
    77         OpenLayers.Format.prototype.initialize.apply(this, [options]); 
     78        // compile regular expressions once instead of every time they are used 
     79        this.regExes = { 
     80            trimSpace: (/^\s*|\s*$/g), 
     81            removeSpace: (/\s*/g), 
     82            splitSpace: (/\s+/), 
     83            trimComma: (/\s*,\s*/g) 
     84        }; 
     85        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); 
    7886    }, 
    7987 
     
    8391     *  
    8492     * Parameters: 
    85      * data - {String} or {XMLNode} data to read/parse. 
    86      */ 
    87      read: function(data) { 
    88         if (typeof data == "string") {  
    89             data = OpenLayers.parseXMLString(data); 
    90         }     
    91         var featureNodes = OpenLayers.Ajax.getElementsByTagNameNS(data, this.gmlns, "gml", this.featureName); 
    92         if (featureNodes.length == 0) { return []; } 
    93  
    94         // Determine dimension of the FeatureCollection. Ie, dim=2 means (x,y) coords 
    95         // dim=3 means (x,y,z) coords 
    96         // GML3 can have 2 or 3 dimensions. GML2 only 2. 
    97         var dim; 
    98         var coordNodes = OpenLayers.Ajax.getElementsByTagNameNS(featureNodes[0], this.gmlns, "gml", "posList"); 
    99         if (coordNodes.length == 0) { 
    100             coordNodes = OpenLayers.Ajax.getElementsByTagNameNS(featureNodes[0], this.gmlns, "gml", "pos"); 
    101         } 
    102         if (coordNodes.length > 0) { 
    103             dim = coordNodes[0].getAttribute("srsDimension"); 
    104         }     
    105         this.dim = (dim == "3" || dim == 3) ? 3 : 2; 
    106          
     93     * data - {String} or {DOMElement} data to read/parse. 
     94     * 
     95     * Returns: 
     96     * {Array(<OpenLayers.Feature.Vector>)} An array of features. 
     97     */ 
     98    read: function(data) { 
     99        if(typeof data == "string") {  
     100            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); 
     101        } 
     102        var featureNodes = this.getElementsByTagNameNS(data.documentElement, 
     103                                                       this.gmlns, 
     104                                                       this.featureName); 
    107105        var features = []; 
    108          
    109         // Process all the featureMembers 
    110         for (var i = 0; i < featureNodes.length; i++) { 
     106        for(var i=0; i<featureNodes.length; i++) { 
    111107            var feature = this.parseFeature(featureNodes[i]); 
    112  
    113             if (feature) { 
     108            if(feature) { 
    114109                features.push(feature); 
    115110            } 
    116111        } 
    117112        return features; 
    118      }, 
    119  
    120      /** 
    121       * Method: parseFeature 
    122       * This function is the core of the GML parsing code in OpenLayers. 
    123       * It creates the geometries that are then attached to the returned 
    124       * feature, and calls parseAttributes() to get attribute data out. 
    125       
    126       * Parameters: 
    127       * xmlNode - {<DOMElement>}  
    128       */ 
    129      parseFeature: function(xmlNode) { 
    130         var geom; 
    131         var p; // [points,bounds] 
    132  
    133         var feature = new OpenLayers.Feature.Vector(); 
    134  
    135         // match MultiPolygon 
    136         if (OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, this.gmlns, "gml", "MultiPolygon").length != 0) { 
    137             var multipolygon = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, this.gmlns, "gml", "MultiPolygon")[0]; 
    138             feature.fid = multipolygon.parentNode.parentNode.getAttribute('fid'); 
    139  
    140             geom = new OpenLayers.Geometry.MultiPolygon(); 
    141             var polygons = OpenLayers.Ajax.getElementsByTagNameNS(multipolygon, 
    142                 this.gmlns, "gml", "Polygon"); 
    143             for (var i = 0; i < polygons.length; i++) { 
    144                 polygon = this.parsePolygonNode(polygons[i],geom); 
    145                 geom.addComponents(polygon); 
    146             } 
    147         } 
    148         // match MultiLineString 
    149         else if (OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    150             this.gmlns, "gml", "MultiLineString").length != 0) { 
    151             var multilinestring = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    152             this.gmlns, "gml", "MultiLineString")[0]; 
    153             feature.fid = multilinestring.parentNode.parentNode.getAttribute('fid'); 
    154              
    155             geom = new OpenLayers.Geometry.MultiLineString(); 
    156             var lineStrings = OpenLayers.Ajax.getElementsByTagNameNS(multilinestring, this.gmlns, "gml", "LineString"); 
    157              
    158             for (var i = 0; i < lineStrings.length; i++) { 
    159                 p = this.parseCoords(lineStrings[i]); 
    160                 if(p.points){ 
    161                     var lineString = new OpenLayers.Geometry.LineString(p.points); 
    162                     geom.addComponents(lineString); 
    163                     // TBD Bounds only set for one of multiple geometries 
    164                 } 
    165             } 
    166         } 
    167         // match MultiPoint 
    168         else if (OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    169             this.gmlns, "gml", "MultiPoint").length != 0) { 
    170             var multiPoint = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    171                 this.gmlns, "gml", "MultiPoint")[0]; 
    172             feature.fid = multiPoint.parentNode.parentNode.getAttribute('fid'); 
     113    }, 
     114     
     115    /** 
     116     * Method: parseFeature 
     117     * This function is the core of the GML parsing code in OpenLayers. 
     118     *    It creates the geometries that are then attached to the returned 
     119     *    feature, and calls parseAttributes() to get attribute data out. 
     120     *     
     121     * Parameters: 
     122     * node - {DOMElement} A GML feature node.  
     123     */ 
     124    parseFeature: function(node) { 
     125        // only accept on geometry per feature - look for highest "order" 
     126        var order = ["MultiPolygon", "Polygon", 
     127                     "MultiLineString", "LineString", 
     128                     "MultiPoint", "Point"]; 
     129        var type, nodeList, geometry, parser; 
     130        for(var i=0; i<order.length; ++i) { 
     131            type = order[i]; 
     132            nodeList = this.getElementsByTagNameNS(node, this.gmlns, type); 
     133            if(nodeList.length > 0) { 
     134                // only deal with first geometry of this type 
     135                var parser = this.parseGeometry[type.toLowerCase()]; 
     136                if(parser) { 
     137                    geometry = parser.apply(this, [nodeList[0]]); 
     138                } else { 
     139                    OpenLayers.Console.error("Unsupported geometry type: " + 
     140                                             type); 
     141                } 
     142                // stop looking for different geometry types 
     143                break; 
     144            } 
     145        } 
     146         
     147        // construct feature (optionally with attributes) 
     148        var attributes; 
     149        if(this.extractAttributes) { 
     150            attributes = this.parseAttributes(node); 
     151        } 
     152        var feature = new OpenLayers.Feature.Vector(geometry, attributes); 
     153        // assign fid - this can come from a "fid" or "id" attribute 
     154        var childNode = node.firstChild; 
     155        var fid; 
     156        while(childNode) { 
     157            if(childNode.nodeType == 1) { 
     158                fid = childNode.getAttribute("fid") || 
     159                      childNode.getAttribute("id"); 
     160                if(fid) { 
     161                    break; 
     162                } 
     163            } 
     164            childNode = childNode.nextSibling; 
     165        } 
     166        feature.fid = fid; 
     167        return feature; 
     168    }, 
     169     
     170    /** 
     171     * Property: parseGeometry 
     172     * Properties of this object are the functions that parse geometries based 
     173     *     on their type. 
     174     */ 
     175    parseGeometry: { 
     176         
     177        /** 
     178         * Method: parseGeometry.point 
     179         * Given a GML node representing a point geometry, create an OpenLayers 
     180         *     point geometry. 
     181         * 
     182         * Parameters: 
     183         * node - {DOMElement} A GML node. 
     184         * 
     185         * Returns: 
     186         * {<OpenLayers.Geometry.Point>} A point geometry. 
     187         */ 
     188        point: function(node) { 
     189            /** 
     190             * Three coordinate variations to consider: 
     191             * 1) <gml:pos>x y z</gml:pos> 
     192             * 2) <gml:coordinates>x, y, z</gml:coordinates> 
     193             * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord> 
     194             */ 
     195            var nodeList; 
     196            var coords = []; 
     197 
     198            // look for <gml:pos> 
     199            var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos"); 
     200            if(nodeList.length > 0) { 
     201                coordString = nodeList[0].firstChild.nodeValue; 
     202                coordString = coordString.replace(this.regExes.trimSpace, ""); 
     203                coords = coordString.split(this.regExes.splitSpace); 
     204            } 
     205 
     206            // look for <gml:coordinates> 
     207            if(coords.length == 0) { 
     208                nodeList = this.getElementsByTagNameNS(node, this.gmlns, 
     209                                                       "coordinates"); 
     210                if(nodeList.length > 0) { 
     211                    coordString = nodeList[0].firstChild.nodeValue; 
     212                    coordString = coordString.replace(this.regExes.removeSpace, 
     213                                                      ""); 
     214                    coords = coordString.split(","); 
     215                } 
     216            } 
     217 
     218            // look for <gml:coord> 
     219            if(coords.length == 0) { 
     220                nodeList = this.getElementsByTagNameNS(node, this.gmlns, 
     221                                                       "coord"); 
     222                if(nodeList.length > 0) { 
     223                    var xList = this.getElementsByTagNameNS(nodeList[0], 
     224                                                            this.gmlns, "X"); 
     225                    var yList = this.getElementsByTagNameNS(nodeList[0], 
     226                                                            this.gmlns, "Y"); 
     227                    if(xList.length > 0 && yList.length > 0) { 
     228                        coords = [xList[0].firstChild.nodeValue, 
     229                                  yList[0].firstChild.nodeValue]; 
     230                    } 
     231                } 
     232            } 
    173233                 
    174             geom = new OpenLayers.Geometry.MultiPoint(); 
    175              
    176             var points = OpenLayers.Ajax.getElementsByTagNameNS(multiPoint, this.gmlns, "gml", "Point"); 
    177              
    178             for (var i = 0; i < points.length; i++) { 
    179                 p = this.parseCoords(points[i]); 
    180                 geom.addComponents(p.points[0]); 
    181                 // TBD Bounds only set for one of multiple geometries 
    182             } 
    183         } 
    184         // match Polygon 
    185         else if (OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    186             this.gmlns, "gml", "Polygon").length != 0) { 
    187             var polygon = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    188                 this.gmlns, "gml", "Polygon")[0]; 
    189             feature.fid = polygon.parentNode.parentNode.getAttribute('fid'); 
    190              
    191             geom = this.parsePolygonNode(polygon); 
    192         } 
    193         // match LineString 
    194         else if (OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    195             this.gmlns, "gml", "LineString").length != 0) { 
    196             var lineString = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    197                 this.gmlns, "gml", "LineString")[0]; 
    198             feature.fid = lineString.parentNode.parentNode.getAttribute('fid'); 
    199  
    200             p = this.parseCoords(lineString); 
    201             if (p.points) { 
    202                 geom = new OpenLayers.Geometry.LineString(p.points); 
    203                 // TBD Bounds only set for one of multiple geometries 
    204             } 
    205         } 
    206         // match Point 
    207         else if (OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    208             this.gmlns, "gml", "Point").length != 0) { 
    209             var point = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, 
    210                 this.gmlns, "gml", "Point")[0]; 
    211             feature.fid = point.parentNode.parentNode.getAttribute('fid'); 
    212              
    213             p = this.parseCoords(point); 
    214             if (p.points) { 
    215                 geom = p.points[0]; 
    216                 // TBD Bounds only set for one of multiple geometries 
    217             } 
    218         } 
    219          
    220         feature.geometry = geom;  
    221         if (this.extractAttributes) { 
    222             feature.attributes = this.parseAttributes(xmlNode); 
    223         }     
    224          
    225         return feature; 
    226     },         
     234            // preserve third dimension 
     235            if(coords.length == 2) { 
     236                coords[2] = null; 
     237            } 
     238            return new OpenLayers.Geometry.Point(coords[0], coords[1], 
     239                                                 coords[2]); 
     240        }, 
     241         
     242        /** 
     243         * Method: parseGeometry.multipoint 
     244         * Given a GML node representing a multipoint geometry, create an 
     245         *     OpenLayers multipoint geometry. 
     246         * 
     247         * Parameters: 
     248         * node - {DOMElement} A GML node. 
     249         * 
     250         * Returns: 
     251         * {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry. 
     252         */ 
     253        multipoint: function(node) { 
     254            var nodeList = this.getElementsByTagNameNS(node, this.gmlns, 
     255                                                       "Point"); 
     256            var components = []; 
     257            if(nodeList.length > 0) { 
     258                var point; 
     259                for(var i=0; i<nodeList.length; ++i) { 
     260                    point = this.parseGeometry.point.apply(this, [nodeList[i]]); 
     261                    if(point) { 
     262                        components.push(point); 
     263                    } 
     264                } 
     265            } 
     266            return new OpenLayers.Geometry.MultiPoint(components); 
     267        }, 
     268         
     269        /** 
     270         * Method: parseGeometry.linestring 
     271         * Given a GML node representing a linestring geometry, create an 
     272         *     OpenLayers linestring geometry. 
     273         * 
     274         * Parameters: 
     275         * node - {DOMElement} A GML node. 
     276         * 
     277         * Returns: 
     278         * {<OpenLayers.Geometry.LineString>} A linestring geometry. 
     279         */ 
     280        linestring: function(node, ring) { 
     281            /** 
     282             * Two coordinate variations to consider: 
     283             * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList> 
     284             * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates> 
     285             */ 
     286            var nodeList, coordString; 
     287            var coords = []; 
     288            var points = []; 
     289 
     290            // look for <gml:posList> 
     291            nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList"); 
     292            if(nodeList.length > 0) { 
     293                coordString = nodeList[0].firstChild.nodeValue; 
     294                coordString = coordString.replace(this.regExes.trimSpace, ""); 
     295                coords = coordString.split(this.regExes.splitSpace); 
     296                var dim = parseInt(nodeList[0].getAttribute("dimension")); 
     297                var j, x, y, z; 
     298                for(var i=0; i<coords.length/dim; ++i) { 
     299                    j = i * dim; 
     300                    x = coords[j]; 
     301                    y = coords[j+1]; 
     302                    z = (dim == 2) ? null : coords[j+2]; 
     303                    points.push(new OpenLayers.Geometry.Point(x, y, z)); 
     304                } 
     305            } 
     306 
     307            // look for <gml:coordinates> 
     308            if(coords.length == 0) { 
     309                nodeList = this.getElementsByTagNameNS(node, this.gmlns, 
     310                                                       "coordinates"); 
     311                if(nodeList.length > 0) { 
     312                    coordString = nodeList[0].firstChild.nodeValue; 
     313                    coordString = coordString.replace(this.regExes.trimSpace, 
     314                                                      ""); 
     315                    coordString = coordString.replace(this.regExes.trimComma, 
     316                                                      ","); 
     317                    var pointList = coordString.split(this.regExes.splitSpace); 
     318                    for(var i=0; i<pointList.length; ++i) { 
     319                        coords = pointList[i].split(","); 
     320                        if(coords.length == 2) { 
     321                            coords[2] = null; 
     322                        } 
     323                        points.push(new OpenLayers.Geometry.Point(coords[0], 
     324                                                                  coords[1], 
     325                                                                  coords[2])); 
     326                    } 
     327                } 
     328            } 
     329 
     330            var line = null; 
     331            if(points.length != 0) { 
     332                if(ring) { 
     333                    line = new OpenLayers.Geometry.LinearRing(points); 
     334                } else { 
     335                    line = new OpenLayers.Geometry.LineString(points); 
     336                } 
     337            } 
     338            return line; 
     339        }, 
     340         
     341        /** 
     342         * Method: parseGeometry.multilinestring 
     343         * Given a GML node representing a multilinestring geometry, create an 
     344         *     OpenLayers multilinestring geometry. 
     345         * 
     346         * Parameters: 
     347         * node - {DOMElement} A GML node. 
     348         * 
     349         * Returns: 
     350         * {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry. 
     351         */ 
     352        multilinestring: function(node) { 
     353            var nodeList = this.getElementsByTagNameNS(node, this.gmlns, 
     354                                                       "LineString"); 
     355            var components = []; 
     356            if(nodeList.length > 0) { 
     357                var line; 
     358                for(var i=0; i<nodeList.length; ++i) { 
     359                    line = this.parseGeometry.linestring.apply(this, 
     360                                                               [nodeList[i]]); 
     361                    if(line) { 
     362                        components.push(line); 
     363                    } 
     364                } 
     365            } 
     366            return new OpenLayers.Geometry.MultiLineString(components); 
     367        }, 
     368         
     369        /** 
     370         * Method: parseGeometry.polygon 
     371         * Given a GML node representing a polygon geometry, create an 
     372         *     OpenLayers polygon geometry. 
     373         * 
     374         * Parameters: 
     375         * node - {DOMElement} A GML node. 
     376         * 
     377         * Returns: 
     378         * {<OpenLayers.Geometry.Polygon>} A polygon geometry. 
     379         */ 
     380        polygon: function(node) { 
     381            var nodeList = this.getElementsByTagNameNS(node, this.gmlns, 
     382                                                       "LinearRing"); 
     383            var components = []; 
     384            if(nodeList.length > 0) { 
     385                // this assumes exterior ring first, inner rings after 
     386                var ring; 
     387                for(var i=0; i<nodeList.length; ++i) { 
     388                    ring = this.parseGeometry.linestring.apply(this, 
     389                                                        [nodeList[i], true]); 
     390                    if(ring) { 
     391                        components.push(ring); 
     392                    } 
     393                } 
     394            } 
     395            return new OpenLayers.Geometry.Polygon(components); 
     396        }, 
     397         
     398        /** 
     399         * Method: parseGeometry.multipolygon 
     400         * Given a GML node representing a multipolygon geometry, create an 
     401         *     OpenLayers multipolygon geometry. 
     402         * 
     403         * Parameters: 
     404         * node - {DOMElement} A GML node. 
     405         * 
     406         * Returns: 
     407         * {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry. 
     408         */ 
     409        multipolygon: function(node) { 
     410            var nodeList = this.getElementsByTagNameNS(node, this.gmlns, 
     411                                                       "Polygon"); 
     412            var components = []; 
     413            if(nodeList.length > 0) { 
     414                var polygon; 
     415                for(var i=0; i<nodeList.length; ++i) { 
     416                    polygon = this.parseGeometry.polygon.apply(this, 
     417                                                               [nodeList[i]]); 
     418                    if(polygon) { 
     419                        components.push(polygon); 
     420                    } 
     421                } 
     422            } 
     423            return new OpenLayers.Geometry.MultiPolygon(components); 
     424        } 
     425    }, 
    227426     
    228427    /** 
    229428     * Method: parseAttributes 
    230      * recursive function parse the attributes of a GML node. 
    231      * Searches for any child nodes which aren't geometries, 
    232      * and gets their value. 
    233429     * 
    234430     * Parameters: 
    235      * xmlNode - {<DOMElement>}  
    236      */ 
    237     parseAttributes: function(xmlNode) { 
    238         var nodes = xmlNode.childNodes; 
     431     * node - {<DOMElement>} 
     432     * 
     433     * Returns: 
     434     * {Object} An attributes object. 
     435     */ 
     436    parseAttributes: function(node) { 
    239437        var attributes = {}; 
    240         for(var i = 0; i < nodes.length; i++) { 
    241             var name = nodes[i].nodeName; 
    242             var value = OpenLayers.Util.getXmlNodeValue(nodes[i]); 
    243             // Ignore Geometry attributes 
    244             // match ".//gml:pos|.//gml:posList|.//gml:coordinates" 
    245             if((name.search(":pos")!=-1) 
    246               ||(name.search(":posList")!=-1) 
    247               ||(name.search(":coordinates")!=-1)){ 
    248                continue;     
    249             } 
    250              
    251             // Check for a leaf node 
    252             if((nodes[i].childNodes.length == 1 && nodes[i].childNodes[0].nodeName == "#text") 
    253                 || (nodes[i].childNodes.length == 0 && nodes[i].nodeName!="#text")) { 
    254                 attributes[name] = value; 
    255             } 
    256             OpenLayers.Util.extend(attributes, this.parseAttributes(nodes[i])) 
    257         }    
     438        // assume attributes are children of the first type 1 child 
     439        var childNode = node.firstChild; 
     440        var children, i, child, grandchildren, grandchild, name, value; 
     441        while(childNode) { 
     442            if(childNode.nodeType == 1) { 
     443                // attributes are type 1 children with one type 3 child 
     444                children = childNode.childNodes; 
     445                for(i=0; i<children.length; ++i) { 
     446                    child = children[i]; 
     447                    if(child.nodeType == 1) { 
     448                        grandchildren = child.childNodes; 
     449                        if(grandchildren.length == 1) { 
     450                            grandchild = grandchildren[0]; 
     451                            if(grandchild.nodeType == 3) { 
     452                                name = (child.prefix) ? 
     453                                        child.nodeName.split(":")[1] : 
     454                                        child.nodeName; 
     455                                value = grandchild.nodeValue.replace( 
     456                                                this.regExes.trimSpace, ""); 
     457                                attributes[name] = value; 
     458                            } 
     459                        } 
     460                    } 
     461                } 
     462                break; 
     463            } 
     464            childNode = childNode.nextSibling; 
     465        } 
    258466        return attributes; 
    259467    }, 
    260468     
    261469    /** 
    262      * Method: parsePolygonNode 
     470     * APIMethod: write 
     471     * Generate a GML document string given a list of features.  
    263472     *  
    264473     * Parameters: 
    265      * xmlNode - {XMLNode}  
     474     * features - {Array(<OpenLayers.Feature.Vector>)} List of features to 
     475     *     serialize into a string. 
    266476     * 
    267477     * Returns: 
    268      * {<OpenLayers.Geometry.Polygon>} polygon geometry 
    269      */ 
    270     parsePolygonNode: function(polygonNode) { 
    271         var linearRings = OpenLayers.Ajax.getElementsByTagNameNS(polygonNode, 
    272             this.gmlns, "gml", "LinearRing"); 
    273          
    274         var rings = []; 
    275         var p; 
    276         var polyBounds; 
    277         for (var i = 0; i < linearRings.length; i++) { 
    278             p = this.parseCoords(linearRings[i]); 
    279             ring1 = new OpenLayers.Geometry.LinearRing(p.points); 
    280             rings.push(ring1); 
    281         } 
    282          
    283         var poly = new OpenLayers.Geometry.Polygon(rings); 
    284         return poly; 
    285     }, 
    286      
    287     /** 
    288      * Method: parseCoords 
    289      * Extract Geographic coordinates from an XML node. 
    290      * 
    291      * Parameters: 
    292      * xmlNode - {<XMLNode>}  
    293      * 
    294      * Returns: 
    295      * An array of <OpenLayers.Geometry.Point> points. 
    296      */ 
    297     parseCoords: function(xmlNode) { 
    298         var x, y, left, bottom, right, top, bounds; 
    299         var p = []; // return value = [points,bounds] 
    300          
    301         if (xmlNode) { 
    302             p.points = []; 
    303              
    304             // match ".//gml:pos|.//gml:posList|.//gml:coordinates" 
    305             // Note: GML2 coordinates are of the form:x y,x y,x y 
    306             // GML2 can also be of the form <coord><x>1</x><y>2</y></coord> 
    307             //       GML3 posList is of the form:x y x y. OR x y z x y z. 
    308              
    309             // GML3 Line or Polygon 
    310             var coordNodes = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, this.gmlns, "gml", "posList"); 
    311              
    312             // GML3 Point 
    313             if (coordNodes.length == 0) {  
    314                 coordNodes = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, this.gmlns, "gml", "pos"); 
    315             }     
    316  
    317             // GML2 
    318             if (coordNodes.length == 0) { 
    319                 coordNodes = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, this.gmlns, "gml", "coordinates"); 
    320             }     
    321  
    322             // TBD: Need to handle an array of coordNodes not just coordNodes[0] 
    323              
    324             var coordString = OpenLayers.Util.getXmlNodeValue(coordNodes[0]); 
    325              
    326             // Extract an array of Numbers from CoordString 
    327             var nums = (coordString) ? coordString.split(/[, \n\t]+/) : []; 
    328              
    329             // Remove elements caused by leading and trailing white space 
    330             while (nums[0] == "")  
    331                 nums.shift(); 
    332              
    333             while (nums[nums.length-1] == "")  
    334                 nums.pop(); 
    335              
    336             for(var i = 0; i < nums.length; i = i + this.dim) { 
    337                 x = parseFloat(nums[i]); 
    338                 y = parseFloat(nums[i+1]); 
    339                 p.points.push(new OpenLayers.Geometry.Point(x, y)); 
    340             } 
    341         } 
    342         return p; 
    343     }, 
    344      
    345     /** 
    346      * APIMethod: write 
    347      * Accept Feature Array, and return a string.  
    348      *  
    349      * Parameters: 
    350      * features - Array({<OpenLayers.Feature.Vector>}> List of features to 
    351      * serialize into a string. 
    352      */ 
    353      write: function(features) { 
    354         var featureCollection = document.createElementNS("http://www.opengis.net/wfs", "wfs:" + this.collectionName); 
    355         for (var i=0; i < features.length; i++) { 
    356             featureCollection.appendChild(this.createFeatureXML(features[i])); 
    357         } 
    358         return featureCollection; 
    359      }, 
    360      
     478     * {String} A string representing the GML document. 
     479     */ 
     480    write: function(features) { 
     481        if(!(features instanceof Array)) { 
     482            features = [features]; 
     483        } 
     484        var gml = this.createElementNS("http://www.opengis.net/wfs", 
     485                                       "wfs:" + this.collectionName); 
     486        for(var i=0; i<features.length; i++) { 
     487            gml.appendChild(this.createFeatureXML(features[i])); 
     488        } 
     489        return OpenLayers.Format.XML.prototype.write.apply(this, [gml]); 
     490    }, 
     491 
    361492    /**  
    362493     * Method: createFeatureXML 
    363      * Accept an OpenLayers.Feature.Vector, and build a geometry for it. 
     494     * Accept an OpenLayers.Feature.Vector, and build a GML node for it. 
    364495     * 
    365496     * Parameters: 
    366      * feature - {<OpenLayers.Feature.Vector>}  
     497     * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML. 
    367498     * 
    368499     * Returns: 
    369      * {DOMElement} 
     500     * {DOMElement} A node reprensting the feature in GML. 
    370501     */ 
    371502    createFeatureXML: function(feature) { 
    372         var geometryNode = this.buildGeometryNode(feature.geometry); 
    373         var geomContainer = document.createElementNS(this.featureNS, "feature:"+this.geometryName); 
     503        var geometry = feature.geometry; 
     504        var geometryNode = this.buildGeometryNode(geometry); 
     505        var geomContainer = this.createElementNS(this.featureNS, 
     506                                                 this.featurePrefix + ":" + 
     507                                                 this.geometryName); 
    374508        geomContainer.appendChild(geometryNode); 
    375         var featureNode = document.createElementNS(this.gmlns, "gml:" + this.featureName); 
    376         var featureContainer = document.createElementNS(this.featureNS, "feature:"+this.layerName); 
     509        var featureNode = this.createElementNS(this.gmlns, 
     510                                               "gml:" + this.featureName); 
     511        var featureContainer = this.createElementNS(this.featureNS, 
     512                                                    this.featurePrefix + ":" + 
     513                                                    this.layerName); 
     514        var fid = feature.fid || feature.id; 
     515        featureContainer.setAttribute("fid", fid); 
    377516        featureContainer.appendChild(geomContainer); 
    378517        for(var attr in feature.attributes) { 
    379             var attrText = document.createTextNode(feature.attributes[attr]);  
    380             var nodename = attr; 
    381             if (attr.search(":") != -1) { 
    382                 nodename = attr.split(":")[1]; 
    383             }     
    384             var attrContainer = document.createElementNS(this.featureNS, "feature:"+nodename); 
     518            var attrText = this.createTextNode(feature.attributes[attr]);  
     519            var nodename = attr.substring(attr.lastIndexOf(":") + 1); 
     520            var attrContainer = this.createElementNS(this.featureNS, 
     521                                                     this.featurePrefix + ":" + 
     522                                                     nodename); 
    385523            attrContainer.appendChild(attrText); 
    386524            featureContainer.appendChild(attrContainer); 
     
    388526        featureNode.appendChild(featureContainer); 
    389527        return featureNode; 
    390     },     
    391      
    392     /** 
    393      * Method: buildGeometryNode  
    394      * builds a GML file with a given geometry 
    395      * 
    396      * Parameters: 
    397      * geometry - {<OpenLayers.Geometry>}  
     528    }, 
     529     
     530    /** 
     531     * APIMethod: buildGeometryNode 
    398532     */ 
    399533    buildGeometryNode: function(geometry) { 
    400     // TBD test if geoserver can be given a Multi-geometry for a simple-geometry data store 
    401     // ie if multipolygon can be sent for a polygon feature type 
    402         var gml = ""; 
    403         // match MultiPolygon or Polygon 
    404         if (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon" 
    405             || geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") { 
    406                 gml = document.createElementNS(this.gmlns, 'gml:MultiPolygon'); 
    407                  
    408                 // TBD retrieve the srs from layer 
    409                 // srsName is non-standard, so not including it until it's right. 
    410                 //gml.setAttribute("srsName", "http://www.opengis.net/gml/srs/epsg.xml#4326"); 
    411                  
    412                 var polygonMember = document.createElementNS(this.gmlns, 'gml:polygonMember'); 
    413                  
    414                 var polygon = document.createElementNS(this.gmlns, 'gml:Polygon'); 
    415                 var outerRing = document.createElementNS(this.gmlns, 'gml:outerBoundaryIs'); 
    416                 var linearRing = document.createElementNS(this.gmlns, 'gml:LinearRing'); 
    417                  
    418                 // TBD manage polygons with holes 
    419                 linearRing.appendChild(this.buildCoordinatesNode(geometry.components[0])); 
    420                 outerRing.appendChild(linearRing); 
    421                 polygon.appendChild(outerRing); 
    422                 polygonMember.appendChild(polygon); 
    423                  
    424                 gml.appendChild(polygonMember); 
    425             } 
    426         // match MultiLineString or LineString 
    427         else if (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString" 
    428                  || geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") { 
    429                      gml = document.createElementNS(this.gmlns, 'gml:MultiLineString'); 
    430                       
    431                      // TBD retrieve the srs from layer 
    432                      // srsName is non-standard, so not including it until it's right. 
    433                      //gml.setAttribute("srsName", "http://www.opengis.net/gml/srs/epsg.xml#4326"); 
    434                       
    435                      var lineStringMember = document.createElementNS(this.gmlns, 'gml:lineStringMember'); 
    436                       
    437                      var lineString = document.createElementNS(this.gmlns, 'gml:LineString'); 
    438                       
    439                      lineString.appendChild(this.buildCoordinatesNode(geometry)); 
    440                      lineStringMember.appendChild(lineString); 
    441                       
    442                      gml.appendChild(lineStringMember); 
    443                  } 
    444         // match MultiPoint or Point 
    445         else if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point" ||  
    446                   geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") { 
    447                 // TBD retrieve the srs from layer 
    448                 // srsName is non-standard, so not including it until it's right. 
    449                 //gml.setAttribute("srsName", "http://www.opengis.net/gml/srs/epsg.xml#4326"); 
    450                       
    451                 gml = document.createElementNS(this.gmlns, 'gml:MultiPoint'); 
    452                 var parts = ""; 
    453                 if (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") { 
    454                     parts = geometry.components; 
    455                 } else { 
    456                     parts = [geometry]; 
    457                 }     
    458                  
    459                 for (var i = 0; i < parts.length; i++) {  
    460                     var pointMember = document.createElementNS(this.gmlns, 'gml:pointMember'); 
    461                     var point = document.createElementNS(this.gmlns, 'gml:Point'); 
    462                     point.appendChild(this.buildCoordinatesNode(parts[i])); 
    463                     pointMember.appendChild(point); 
    464                     gml.appendChild(pointMember); 
    465                }      
    466         } 
    467         return gml;          
    468     }, 
    469       
     534        var className = geometry.CLASS_NAME; 
     535        var type = className.substring(className.lastIndexOf(".") + 1); 
     536        var builder = this.buildGeometry[type.toLowerCase()]; 
     537        return builder.apply(this, [geometry]); 
     538    }, 
     539 
     540    /** 
     541     * Property: buildGeometry 
     542     * Object containing methods to do the actual geometry node building 
     543     *     based on geometry type. 
     544     */ 
     545    buildGeometry: { 
     546        // TBD retrieve the srs from layer 
     547        // srsName is non-standard, so not including it until it's right. 
     548        // gml.setAttribute("srsName", 
     549        //                  "http://www.opengis.net/gml/srs/epsg.xml#4326"); 
     550 
     551        /** 
     552         * Method: buildGeometry.point 
     553         * Given an OpenLayers point geometry, create a GML point. 
     554         *