Index: KML.js
===================================================================
--- KML.js	(revision 9859)
+++ KML.js	(working copy)
@@ -15,45 +15,45 @@
 
 /**
  * Class: OpenLayers.Format.KML
- * Read/Wite KML. Create a new instance with the <OpenLayers.Format.KML>
- *     constructor. 
- * 
+ * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML>
+ *     constructor.
+ *
  * Inherits from:
  *  - <OpenLayers.Format.XML>
  */
 OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
-    
+
     /**
      * APIProperty: kmlns
      * {String} KML Namespace to use. Defaults to 2.0 namespace.
      */
     kmlns: "http://earth.google.com/kml/2.0",
-    
-    /** 
+
+    /**
      * APIProperty: placemarksDesc
      * {String} Name of the placemarks.  Default is "No description available."
      */
     placemarksDesc: "No description available",
-    
-    /** 
+
+    /**
      * APIProperty: foldersName
      * {String} Name of the folders.  Default is "OpenLayers export."
      */
     foldersName: "OpenLayers export",
-    
-    /** 
+
+    /**
      * APIProperty: foldersDesc
      * {String} Description of the folders. Default is "Exported on [date]."
      */
     foldersDesc: "Exported on " + new Date(),
-    
+
     /**
      * APIProperty: extractAttributes
      * {Boolean} Extract attributes from KML.  Default is true.
      *           Extracting styleUrls requires this to be set to true
      */
     extractAttributes: true,
-    
+
     /**
      * Property: extractStyles
      * {Boolean} Extract styles from KML.  Default is false.
@@ -61,28 +61,28 @@
      *           set to true
      */
     extractStyles: false,
-    
+
     /**
      * Property: internalns
      * {String} KML Namespace to use -- defaults to the namespace of the
-     *     Placemark node being parsed, but falls back to kmlns. 
+     *     Placemark node being parsed, but falls back to kmlns.
      */
     internalns: null,
 
     /**
      * Property: features
      * {Array} Array of features
-     *     
+     *
      */
     features: null,
 
     /**
      * Property: styles
      * {Object} Storage of style objects
-     *     
+     *
      */
     styles: null,
-    
+
     /**
      * Property: styleBaseUrl
      * {String}
@@ -98,7 +98,7 @@
 
     /**
      * APIProperty: maxDepth
-     * {Integer} Maximum depth for recursive loading external KML URLs 
+     * {Integer} Maximum depth for recursive loading external KML URLs
      *           Defaults to 0: do no external fetching
      */
     maxDepth: 0,
@@ -119,17 +119,19 @@
             splitSpace: (/\s+/),
             trimComma: (/\s*,\s*/g),
             kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
+            color: (/(\w{2})(\w{2})(\w{2})/),
             kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
-            straightBracket: (/\$\[(.*?)\]/g)
+            straightBracket: (/\$\[(.*?)\]/g),
+            version: (/\b[0-9]+.[0-9]+\b/)
         };
         OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
     },
 
     /**
      * APIMethod: read
-     * Read data from a string, and return a list of features. 
-     * 
-     * Parameters: 
+     * Read data from a string, and return a list of features.
+     *
+     * Parameters:
      * data    - {String} or {DOMElement} data to read/parse.
      *
      * Returns:
@@ -137,8 +139,8 @@
      */
     read: function(data) {
         this.features = [];
-        this.styles   = {};
-        this.fetched  = {};
+        this.styles = {};
+        this.fetched = {};
 
         // Set default options 
         var options = {
@@ -151,9 +153,9 @@
 
     /**
      * Method: parseData
-     * Read data from a string, and return a list of features. 
-     * 
-     * Parameters: 
+     * Read data from a string, and return a list of features.
+     *
+     * Parameters:
      * data    - {String} or {DOMElement} data to read/parse.
      * options - {Object} Hash of options
      *
@@ -161,20 +163,20 @@
      * {Array(<OpenLayers.Feature.Vector>)} List of features.
      */
     parseData: function(data, options) {
-        if(typeof data == "string") {
+        if (typeof data == "string") {
             data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
         }
 
         // Loop throught the following node types in this order and
         // process the nodes found 
         var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
-        for(var i=0, len=types.length; i<len; ++i) {
+        for (var i = 0, len = types.length; i < len; ++i) {
             var type = types[i];
 
             var nodes = this.getElementsByTagNameNS(data, "*", type);
 
             // skip to next type if no nodes are found
-            if(nodes.length == 0) { 
+            if (nodes.length == 0) {
                 continue;
             }
 
@@ -204,21 +206,21 @@
                     break;
             }
         }
-        
+
         return this.features;
     },
 
     /**
      * Method: parseLinks
      * Finds URLs of linked KML documents and fetches them
-     * 
-     * Parameters: 
+     *
+     * Parameters:
      * nodes   - {Array} of {DOMElement} data to read/parse.
      * options - {Object} Hash of options
-     * 
+     *
      */
     parseLinks: function(nodes, options) {
-        
+
         // Fetch external links <NetworkLink> and <Link>
         // Don't do anything if we have reached our maximum depth for recursion
         if (options.depth >= this.maxDepth) {
@@ -229,15 +231,15 @@
         var newOptions = OpenLayers.Util.extend({}, options);
         newOptions.depth++;
 
-        for(var i=0, len=nodes.length; i<len; i++) {
+        for (var i = 0, len = nodes.length; i < len; i++) {
             var href = this.parseProperty(nodes[i], "*", "href");
-            if(href && !this.fetched[href]) {
+            if (href && !this.fetched[href]) {
                 this.fetched[href] = true; // prevent reloading the same urls
                 var data = this.fetchLink(href);
                 if (data) {
                     this.parseData(data, newOptions);
                 }
-            } 
+            }
         }
 
     },
@@ -245,10 +247,10 @@
     /**
      * Method: fetchLink
      * Fetches a URL and returns the result
-     * 
-     * Parameters: 
+     *
+     * Parameters:
      * href  - {String} url to be fetched
-     * 
+     *
      */
     fetchLink: function(href) {
         var request = OpenLayers.Request.GET({url: href, async: false});
@@ -261,18 +263,18 @@
      * Method: parseStyles
      * Looks for <Style> nodes in the data and parses them
      * Also parses <StyleMap> nodes, but only uses the 'normal' key
-     * 
-     * Parameters: 
+     *
+     * Parameters:
      * nodes    - {Array} of {DOMElement} data to read/parse.
      * options  - {Object} Hash of options
-     * 
+     *
      */
     parseStyles: function(nodes, options) {
-        for(var i=0, len=nodes.length; i<len; i++) {
+        for (var i = 0, len = nodes.length; i < len; i++) {
             var style = this.parseStyle(nodes[i]);
-            if(style) {
+            if (style) {
                 styleName = (options.styleBaseUrl || "") + "#" + style.id;
-                
+
                 this.styles[styleName] = style;
             }
         }
@@ -282,21 +284,21 @@
      * Method: parseStyle
      * Parses the children of a <Style> node and builds the style hash
      * accordingly
-     * 
-     * Parameters: 
+     *
+     * Parameters:
      * node - {DOMElement} <Style> node
-     * 
+     *
      */
     parseStyle: function(node) {
         var style = {};
-        
+
         var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle"];
         var type, nodeList, geometry, parser;
-        for(var i=0, len=types.length; i<len; ++i) {
+        for (var i = 0, len = types.length; i < len; ++i) {
             type = types[i];
-            styleTypeNode = this.getElementsByTagNameNS(node, 
-                                                   "*", type)[0];
-            if(!styleTypeNode) { 
+            styleTypeNode = this.getElementsByTagNameNS(node,
+                    "*", type)[0];
+            if (!styleTypeNode) {
                 continue;
             }
 
@@ -306,19 +308,19 @@
                     var color = this.parseProperty(styleTypeNode, "*", "color");
                     if (color) {
                         var matches = (color.toString()).match(
-                                                         this.regExes.kmlColor);
+                                this.regExes.kmlColor);
 
                         // transparency
                         var alpha = matches[1];
                         style["strokeOpacity"] = parseInt(alpha, 16) / 255;
 
                         // rgb colors (google uses bgr)
-                        var b = matches[2]; 
-                        var g = matches[3]; 
-                        var r = matches[4]; 
+                        var b = matches[2];
+                        var g = matches[3];
+                        var r = matches[4];
                         style["strokeColor"] = "#" + r + g + b;
                     }
-                    
+
                     var width = this.parseProperty(styleTypeNode, "*", "width");
                     if (width) {
                         style["strokeWidth"] = width;
@@ -328,40 +330,40 @@
                     var color = this.parseProperty(styleTypeNode, "*", "color");
                     if (color) {
                         var matches = (color.toString()).match(
-                                                         this.regExes.kmlColor);
+                                this.regExes.kmlColor);
 
                         // transparency
                         var alpha = matches[1];
                         style["fillOpacity"] = parseInt(alpha, 16) / 255;
 
                         // rgb colors (google uses bgr)
-                        var b = matches[2]; 
-                        var g = matches[3]; 
-                        var r = matches[4]; 
+                        var b = matches[2];
+                        var g = matches[3];
+                        var r = matches[4];
                         style["fillColor"] = "#" + r + g + b;
                     }
-                     // Check is fill is disabled
+                    // Check is fill is disabled
                     var fill = this.parseProperty(styleTypeNode, "*", "fill");
                     if (fill == "0") {
                         style["fillColor"] = "none";
                     }
-                   
+
                     break;
                 case "iconstyle":
                     // set scale
-                    var scale = parseFloat(this.parseProperty(styleTypeNode, 
-                                                          "*", "scale") || 1);
-  
+                    var scale = parseFloat(this.parseProperty(styleTypeNode,
+                            "*", "scale") || 1);
+
                     // set default width and height of icon
                     var width = 32 * scale;
                     var height = 32 * scale;
 
-                    var iconNode = this.getElementsByTagNameNS(styleTypeNode, 
-                                               "*", 
-                                               "Icon")[0];
+                    var iconNode = this.getElementsByTagNameNS(styleTypeNode,
+                            "*",
+                            "Icon")[0];
                     if (iconNode) {
                         var href = this.parseProperty(iconNode, "*", "href");
-                        if (href) {                                                   
+                        if (href) {
 
                             var w = this.parseProperty(iconNode, "*", "w");
                             var h = this.parseProperty(iconNode, "*", "h");
@@ -371,12 +373,12 @@
                             // scale to prevent icons from being too big
                             var google = "http://maps.google.com/mapfiles/kml";
                             if (OpenLayers.String.startsWith(
-                                                 href, google) && !w && !h) {
+                                    href, google) && !w && !h) {
                                 w = 64;
                                 h = 64;
                                 scale = scale / 2;
                             }
-                                
+
                             // if only dimension is defined, make sure the
                             // other one has the same value
                             w = w || h;
@@ -399,19 +401,19 @@
                             // and request the appropriate icon from the 
                             // google maps website
                             var matches = href.match(this.regExes.kmlIconPalette);
-                            if (matches)  {
+                            if (matches) {
                                 var palette = matches[1];
                                 var file_extension = matches[2];
 
                                 var x = this.parseProperty(iconNode, "*", "x");
                                 var y = this.parseProperty(iconNode, "*", "y");
 
-                                var posX = x ? x/32 : 0;
-                                var posY = y ? (7 - y/32) : 7;
+                                var posX = x ? x / 32 : 0;
+                                var posY = y ? (7 - y / 32) : 7;
 
                                 var pos = posY * 8 + posX;
-                                href = "http://maps.google.com/mapfiles/kml/pal" 
-                                     + palette + "/icon" + pos + file_extension;
+                                href = "http://maps.google.com/mapfiles/kml/pal"
+                                        + palette + "/icon" + pos + file_extension;
                             }
 
                             style["graphicOpacity"] = 1; // fully opaque
@@ -422,9 +424,9 @@
 
 
                     // hotSpots define the offset for an Icon
-                    var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode, 
-                                               "*", 
-                                               "hotSpot")[0];
+                    var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode,
+                            "*",
+                            "hotSpot")[0];
                     if (hotSpotNode) {
                         var x = parseFloat(hotSpotNode.getAttribute("x"));
                         var y = parseFloat(hotSpotNode.getAttribute("y"));
@@ -448,7 +450,7 @@
                             style["graphicYOffset"] = -(y * scale) + 1;
                         }
                         else if (yUnits == "fraction") {
-                            style["graphicYOffset"] =  -height * (1 - y) + 1;
+                            style["graphicYOffset"] = -height * (1 - y) + 1;
                         }
                     }
 
@@ -458,10 +460,10 @@
 
                 case "balloonstyle":
                     var balloonStyle = OpenLayers.Util.getXmlNodeValue(
-                                            styleTypeNode);
+                            styleTypeNode);
                     if (balloonStyle) {
                         style["balloonStyle"] = balloonStyle.replace(
-                                       this.regExes.straightBracket, "${$1}");
+                                this.regExes.straightBracket, "${$1}");
                     }
                     break;
                 default:
@@ -485,23 +487,23 @@
      * Method: parseStyleMaps
      * Looks for <Style> nodes in the data and parses them
      * Also parses <StyleMap> nodes, but only uses the 'normal' key
-     * 
-     * Parameters: 
+     *
+     * Parameters:
      * nodes    - {Array} of {DOMElement} data to read/parse.
      * options  - {Object} Hash of options
-     * 
+     *
      */
     parseStyleMaps: function(nodes, options) {
         // Only the default or "normal" part of the StyleMap is processed now
         // To do the select or "highlight" bit, we'd need to change lots more
 
-        for(var i=0, len=nodes.length; i<len; i++) {
+        for (var i = 0, len = nodes.length; i < len; i++) {
             var node = nodes[i];
-            var pairs = this.getElementsByTagNameNS(node, "*", 
-                            "Pair");
+            var pairs = this.getElementsByTagNameNS(node, "*",
+                    "Pair");
 
             var id = node.getAttribute("id");
-            for (var j=0, jlen=pairs.length; j<jlen; j++) {
+            for (var j = 0, jlen = pairs.length; j < jlen; j++) {
                 var pair = pairs[j];
                 // Use the shortcut in the SLD format to quickly retrieve the 
                 // value of a node. Maybe it's good to have a method in 
@@ -511,7 +513,7 @@
 
                 if (styleUrl && key == "normal") {
                     this.styles[(options.styleBaseUrl || "") + "#" + id] =
-                        this.styles[(options.styleBaseUrl || "") + styleUrl];
+                    this.styles[(options.styleBaseUrl || "") + styleUrl];
                 }
 
                 if (styleUrl && key == "highlight") {
@@ -528,18 +530,18 @@
      * Method: parseFeatures
      * Loop through all Placemark nodes and parse them.
      * Will create a list of features
-     * 
-     * Parameters: 
+     *
+     * Parameters:
      * nodes    - {Array} of {DOMElement} data to read/parse.
      * options  - {Object} Hash of options
-     * 
+     *
      */
     parseFeatures: function(nodes, options) {
         var features = new Array(nodes.length);
-        for(var i=0, len=nodes.length; i<len; i++) {
+        for (var i = 0, len = nodes.length; i < len; i++) {
             var featureNode = nodes[i];
-            var feature = this.parseFeature.apply(this,[featureNode]) ;
-            if(feature) {
+            var feature = this.parseFeature.apply(this, [featureNode]);
+            if (feature) {
 
                 // Create reference to styleUrl 
                 if (this.extractStyles && feature.attributes &&
@@ -551,14 +553,14 @@
                     // Make sure that <Style> nodes within a placemark are 
                     // processed as well
                     var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
-                                                        "*",
-                                                        "Style")[0];
+                            "*",
+                            "Style")[0];
                     if (inlineStyleNode) {
-                        var inlineStyle= this.parseStyle(inlineStyleNode);
+                        var inlineStyle = this.parseStyle(inlineStyleNode);
                         if (inlineStyle) {
                             feature.style = OpenLayers.Util.extend(
-                                feature.style, inlineStyle
-                            );
+                                    feature.style, inlineStyle
+                                    );
                         }
                     }
                 }
@@ -590,24 +592,24 @@
         // only accept one geometry per feature - look for highest "order"
         var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
         var type, nodeList, geometry, parser;
-        for(var i=0, len=order.length; i<len; ++i) {
+        for (var i = 0, len = order.length; i < len; ++i) {
             type = order[i];
-            this.internalns = node.namespaceURI ? 
-                    node.namespaceURI : this.kmlns;
-            nodeList = this.getElementsByTagNameNS(node, 
-                                                   this.internalns, type);
-            if(nodeList.length > 0) {
+            this.internalns = node.namespaceURI ?
+                              node.namespaceURI : this.kmlns;
+            nodeList = this.getElementsByTagNameNS(node,
+                    this.internalns, type);
+            if (nodeList.length > 0) {
                 // only deal with first geometry of this type
                 var parser = this.parseGeometry[type.toLowerCase()];
-                if(parser) {
+                if (parser) {
                     geometry = parser.apply(this, [nodeList[0]]);
                     if (this.internalProjection && this.externalProjection) {
-                        geometry.transform(this.externalProjection, 
-                                           this.internalProjection); 
-                    }                       
+                        geometry.transform(this.externalProjection,
+                                this.internalProjection);
+                    }
                 } else {
                     OpenLayers.Console.error(OpenLayers.i18n(
-                                "unsupportedGeometryType", {'geomType':type}));
+                            "unsupportedGeometryType", {'geomType':type}));
                 }
                 // stop looking for different geometry types
                 break;
@@ -616,28 +618,28 @@
 
         // construct feature (optionally with attributes)
         var attributes;
-        if(this.extractAttributes) {
+        if (this.extractAttributes) {
             attributes = this.parseAttributes(node);
         }
         var feature = new OpenLayers.Feature.Vector(geometry, attributes);
 
         var fid = node.getAttribute("id") || node.getAttribute("name");
-        if(fid != null) {
+        if (fid != null) {
             feature.fid = fid;
         }
 
         return feature;
-    },        
-    
+    },
+
     /**
      * Method: getStyle
      * Retrieves a style from a style hash using styleUrl as the key
-     * If the styleUrl doesn't exist yet, we try to fetch it 
+     * If the styleUrl doesn't exist yet, we try to fetch it
      * Internet
-     * 
-     * Parameters: 
+     *
+     * Parameters:
      * styleUrl  - {String} URL of style
-     * options   - {Object} Hash of options 
+     * options   - {Object} Hash of options
      *
      * Returns:
      * {Object}  - (reference to) Style hash
@@ -651,10 +653,10 @@
         newOptions.styleBaseUrl = styleBaseUrl;
 
         // Fetch remote Style URLs (if not fetched before) 
-        if (!this.styles[styleUrl] 
-                && !OpenLayers.String.startsWith(styleUrl, "#") 
+        if (!this.styles[styleUrl]
+                && !OpenLayers.String.startsWith(styleUrl, "#")
                 && newOptions.depth <= this.maxDepth
-                && !this.fetched[styleBaseUrl] ) {
+                && !this.fetched[styleBaseUrl]) {
 
             var data = this.fetchLink(styleBaseUrl);
             if (data) {
@@ -667,14 +669,14 @@
         var style = OpenLayers.Util.extend({}, this.styles[styleUrl]);
         return style;
     },
-    
+
     /**
      * Property: parseGeometry
      * Properties of this object are the functions that parse geometries based
      *     on their type.
      */
     parseGeometry: {
-        
+
         /**
          * Method: parseGeometry.point
          * Given a KML node representing a point geometry, create an OpenLayers
@@ -688,28 +690,28 @@
          */
         point: function(node) {
             var nodeList = this.getElementsByTagNameNS(node, this.internalns,
-                                                       "coordinates");
+                    "coordinates");
             var coords = [];
-            if(nodeList.length > 0) {
+            if (nodeList.length > 0) {
                 var coordString = nodeList[0].firstChild.nodeValue;
                 coordString = coordString.replace(this.regExes.removeSpace, "");
                 coords = coordString.split(",");
             }
 
             var point = null;
-            if(coords.length > 1) {
+            if (coords.length > 1) {
                 // preserve third dimension
-                if(coords.length == 2) {
+                if (coords.length == 2) {
                     coords[2] = null;
                 }
                 point = new OpenLayers.Geometry.Point(coords[0], coords[1],
-                                                      coords[2]);
+                        coords[2]);
             } else {
                 throw "Bad coordinate string: " + coordString;
             }
             return point;
         },
-        
+
         /**
          * Method: parseGeometry.linestring
          * Given a KML node representing a linestring geometry, create an
@@ -723,36 +725,36 @@
          */
         linestring: function(node, ring) {
             var nodeList = this.getElementsByTagNameNS(node, this.internalns,
-                                                       "coordinates");
+                    "coordinates");
             var line = null;
-            if(nodeList.length > 0) {
+            if (nodeList.length > 0) {
                 var coordString = this.getChildValue(nodeList[0]);
 
                 coordString = coordString.replace(this.regExes.trimSpace,
-                                                  "");
+                        "");
                 coordString = coordString.replace(this.regExes.trimComma,
-                                                  ",");
+                        ",");
                 var pointList = coordString.split(this.regExes.splitSpace);
                 var numPoints = pointList.length;
                 var points = new Array(numPoints);
                 var coords, numCoords;
-                for(var i=0; i<numPoints; ++i) {
+                for (var i = 0; i < numPoints; ++i) {
                     coords = pointList[i].split(",");
                     numCoords = coords.length;
-                    if(numCoords > 1) {
-                        if(coords.length == 2) {
+                    if (numCoords > 1) {
+                        if (coords.length == 2) {
                             coords[2] = null;
                         }
                         points[i] = new OpenLayers.Geometry.Point(coords[0],
-                                                                  coords[1],
-                                                                  coords[2]);
+                                coords[1],
+                                coords[2]);
                     } else {
                         throw "Bad LineString point coordinates: " +
                               pointList[i];
                     }
                 }
-                if(numPoints) {
-                    if(ring) {
+                if (numPoints) {
+                    if (ring) {
                         line = new OpenLayers.Geometry.LinearRing(points);
                     } else {
                         line = new OpenLayers.Geometry.LineString(points);
@@ -764,7 +766,7 @@
 
             return line;
         },
-        
+
         /**
          * Method: parseGeometry.polygon
          * Given a KML node representing a polygon geometry, create an
@@ -778,16 +780,16 @@
          */
         polygon: function(node) {
             var nodeList = this.getElementsByTagNameNS(node, this.internalns,
-                                                       "LinearRing");
+                    "LinearRing");
             var numRings = nodeList.length;
             var components = new Array(numRings);
-            if(numRings > 0) {
+            if (numRings > 0) {
                 // this assumes exterior ring first, inner rings after
                 var ring;
-                for(var i=0, len=nodeList.length; i<len; ++i) {
+                for (var i = 0, len = nodeList.length; i < len; ++i) {
                     ring = this.parseGeometry.linestring.apply(this,
-                                                        [nodeList[i], true]);
-                    if(ring) {
+                            [nodeList[i], true]);
+                    if (ring) {
                         components[i] = ring;
                     } else {
                         throw "Bad LinearRing geometry: " + i;
@@ -796,7 +798,7 @@
             }
             return new OpenLayers.Geometry.Polygon(components);
         },
-        
+
         /**
          * Method: parseGeometry.multigeometry
          * Given a KML node representing a multigeometry, create an
@@ -812,21 +814,21 @@
             var child, parser;
             var parts = [];
             var children = node.childNodes;
-            for(var i=0, len=children.length; i<len; ++i ) {
+            for (var i = 0, len = children.length; i < len; ++i) {
                 child = children[i];
-                if(child.nodeType == 1) {
+                if (child.nodeType == 1) {
                     var type = (child.prefix) ?
-                            child.nodeName.split(":")[1] :
-                            child.nodeName;
+                               child.nodeName.split(":")[1] :
+                               child.nodeName;
                     var parser = this.parseGeometry[type.toLowerCase()];
-                    if(parser) {
+                    if (parser) {
                         parts.push(parser.apply(this, [child]));
                     }
                 }
             }
             return new OpenLayers.Geometry.Collection(parts);
         }
-        
+
     },
 
     /**
@@ -840,22 +842,22 @@
      */
     parseAttributes: function(node) {
         var attributes = {};
-       
+
         // Extended Data is parsed first.
         var edNodes = node.getElementsByTagName("ExtendedData");
         if (edNodes.length) {
             attributes = this.parseExtendedData(edNodes[0]);
         }
-        
+
         // assume attribute nodes are type 1 children with a type 3 or 4 child
         var child, grandchildren, grandchild;
         var children = node.childNodes;
 
-        for(var i=0, len=children.length; i<len; ++i) {
+        for (var i = 0, len = children.length; i < len; ++i) {
             child = children[i];
-            if(child.nodeType == 1) {
+            if (child.nodeType == 1) {
                 grandchildren = child.childNodes;
-                if(grandchildren.length == 1 || grandchildren.length == 3) {
+                if (grandchildren.length == 1 || grandchildren.length == 3) {
                     var grandchild;
                     switch (grandchildren.length) {
                         case 1:
@@ -866,17 +868,17 @@
                             grandchild = grandchildren[1];
                             break;
                     }
-                    if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
+                    if (grandchild.nodeType == 3 || grandchild.nodeType == 4) {
                         var name = (child.prefix) ?
-                                child.nodeName.split(":")[1] :
-                                child.nodeName;
+                                   child.nodeName.split(":")[1] :
+                                   child.nodeName;
                         var value = OpenLayers.Util.getXmlNodeValue(grandchild);
                         if (value) {
                             value = value.replace(this.regExes.trimSpace, "");
                             attributes[name] = value;
                         }
                     }
-                } 
+                }
             }
         }
         return attributes;
@@ -892,14 +894,14 @@
         var attributes = {};
         var i, len, data, key;
         var dataNodes = node.getElementsByTagName("Data");
-        for (i = 0, len = dataNodes.length; i < len; i++) {
+        for (i = 0,len = dataNodes.length; i < len; i++) {
             data = dataNodes[i];
             key = data.getAttribute("name");
             var ed = {};
             var valueNode = data.getElementsByTagName("value");
             if (valueNode.length) {
                 ed['value'] = this.getChildValue(valueNode[0]);
-            }    
+            }
             var nameNode = data.getElementsByTagName("displayName");
             if (nameNode.length) {
                 ed['displayName'] = this.getChildValue(nameNode[0]);
@@ -907,7 +909,7 @@
             attributes[key] = ed;
         }
         var simpleDataNodes = node.getElementsByTagName("SimpleData");
-        for (i = 0, len = simpleDataNodes.length; i < len; i++) {
+        for (i = 0,len = simpleDataNodes.length; i < len; i++) {
             var ed = {};
             data = simpleDataNodes[i];
             key = data.getAttribute("name");
@@ -915,10 +917,10 @@
             ed['displayName'] = key;
             attributes[key] = ed;
         }
-        
-        return attributes;    
+
+        return attributes;
     },
-    
+
     /**
      * Method: parseProperty
      * Convenience method to find a node and return its value
@@ -927,10 +929,10 @@
      * xmlNode    - {<DOMElement>}
      * namespace  - {String} namespace of the node to find
      * tagName    - {String} name of the property to parse
-     * 
+     *
      * Returns:
      * {String} The value for the requested property (defaults to null)
-     */    
+     */
     parseProperty: function(xmlNode, namespace, tagName) {
         var value;
         var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
@@ -939,14 +941,14 @@
         } catch(e) {
             value = null;
         }
-     
+
         return value;
-    },                                                              
+    },
 
     /**
      * APIMethod: write
-     * Accept Feature Collection, and return a string. 
-     * 
+     * Accept Feature Collection, and return a string.
+     *
      * Parameters:
      * features - {Array(<OpenLayers.Feature.Vector>} An array of features.
      *
@@ -954,12 +956,20 @@
      * {String} A KML string.
      */
     write: function(features) {
-        if(!(features instanceof Array)) {
+        if (!(features instanceof Array)) {
             features = [features];
         }
         var kml = this.createElementNS(this.kmlns, "kml");
-        var folder = this.createFolderXML();
-        for(var i=0, len=features.length; i<len; ++i) {
+        var folder = null;
+        if (this.getKmlVersion() == '2.2') {
+            folder = this.createDocumentXML();
+        } else {
+            folder = this.createFolderXML();
+        }
+        for (var i = 0, len = features.length; i < len; ++i) {
+            if (features[i].attributes.styleUrl) {
+                folder.appendChild(this.createStyleXML(features[i]));
+            }
             folder.appendChild(this.createPlacemarkXML(features[i]));
         }
         kml.appendChild(folder);
@@ -969,89 +979,205 @@
     /**
      * Method: createFolderXML
      * Creates and returns a KML folder node
-     * 
+     *
      * Returns:
      * {DOMElement}
      */
     createFolderXML: function() {
         // Folder name
         var folderName = this.createElementNS(this.kmlns, "name");
-        var folderNameText = this.createTextNode(this.foldersName); 
+        var folderNameText = this.createTextNode(this.foldersName);
         folderName.appendChild(folderNameText);
 
         // Folder description
-        var folderDesc = this.createElementNS(this.kmlns, "description");        
-        var folderDescText = this.createTextNode(this.foldersDesc); 
+        var folderDesc = this.createElementNS(this.kmlns, "description");
+        var folderDescText = this.createTextNode(this.foldersDesc);
         folderDesc.appendChild(folderDescText);
 
         // Folder
         var folder = this.createElementNS(this.kmlns, "Folder");
         folder.appendChild(folderName);
         folder.appendChild(folderDesc);
-        
+
         return folder;
     },
 
     /**
+     * Method: createDocumentXML
+     * Creates and returns a KML document node
+     * From 2.2 documentation: Do not put shared styles within a Folder.
+     *
+     * Returns:
+     * {DOMElement}
+     */
+    createDocumentXML: function() {
+        // Folder name
+        var documentName = this.createElementNS(this.kmlns, "name");
+        var documentNameText = this.createTextNode(this.foldersName);
+        documentName.appendChild(documentNameText);
+
+        // Folder description
+        var documentDesc = this.createElementNS(this.kmlns, "description");
+        var documentDescText = this.createTextNode(this.foldersDesc);
+        documentDesc.appendChild(documentDescText);
+
+        // Folder
+        var document = this.createElementNS(this.kmlns, "Document");
+        document.appendChild(documentName);
+        document.appendChild(documentDesc);
+
+        return document;
+    },
+
+    /**
      * Method: createPlacemarkXML
-     * Creates and returns a KML placemark node representing the given feature. 
-     * 
+     * Creates and returns a KML placemark node representing the given feature.
+     *
      * Parameters:
      * feature - {<OpenLayers.Feature.Vector>}
-     * 
+     *
      * Returns:
      * {DOMElement}
      */
-    createPlacemarkXML: function(feature) {        
+    createPlacemarkXML: function(feature) {
         // Placemark name
         var placemarkName = this.createElementNS(this.kmlns, "name");
         var name = (feature.attributes.name) ?
-                    feature.attributes.name : feature.id;
+                   feature.attributes.name : feature.id;
         placemarkName.appendChild(this.createTextNode(name));
 
         // Placemark description
         var placemarkDesc = this.createElementNS(this.kmlns, "description");
         var desc = (feature.attributes.description) ?
-                    feature.attributes.description : this.placemarksDesc;
+                   feature.attributes.description : this.placemarksDesc;
         placemarkDesc.appendChild(this.createTextNode(desc));
-        
+
         // Placemark
         var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
-        if(feature.fid != null) {
+        if (feature.fid != null) {
             placemarkNode.setAttribute("id", feature.fid);
         }
         placemarkNode.appendChild(placemarkName);
         placemarkNode.appendChild(placemarkDesc);
 
+        // Optional Placemark styleUrl
+        if (feature.attributes.styleUrl) {
+            var placemarkStyleUrl = this.createElementNS(this.kmlns, "styleUrl");
+            var styleUrl = (feature.attributes.styleUrl);
+            placemarkStyleUrl.appendChild(this.createTextNode(styleUrl));
+            placemarkNode.appendChild(placemarkStyleUrl);
+        } else if (feature.style) {
+            placemarkNode.appendChild(this.createStyleXML(feature));
+        }
+
+        // Optional Placemark visibility
+        if (feature.attributes.visibility) {
+            var placemarkVisibility = this.createElementNS(this.kmlns, "visibility");
+            var visibility = (feature.attributes.visibility);
+            placemarkVisibility.appendChild(this.createTextNode(visibility));
+            placemarkNode.appendChild(placemarkVisibility);
+        }
+
         // Geometry node (Point, LineString, etc. nodes)
         var geometryNode = this.buildGeometryNode(feature.geometry);
-        placemarkNode.appendChild(geometryNode);        
-        
+        placemarkNode.appendChild(geometryNode);
+
         // TBD - deal with remaining (non name/description) attributes.
         return placemarkNode;
-    },    
+    },
 
     /**
+     * Method: createStyleXML
+     * Creates and returns a KML style node representing the given feature.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     *
+     * Returns:
+     * {DOMElement}
+     */
+    createStyleXML: function(feature) {
+        // Placemark
+        var styleNode = this.createElementNS(this.kmlns, "Style");
+
+        if (feature.style.id != null) {
+            styleNode.setAttribute("id", feature.style.id);
+        }
+
+        // Get the line style
+        if ((feature.style.strokeColor && feature.style.strokeOpacity) || (feature.style.strokeWidth)) {
+            var linestyleNode = this.createElementNS(this.kmlns, "LineStyle");
+            // Stroke color and opacity
+            if (feature.style.strokeColor && feature.style.strokeOpacity) {
+                var kmlColor = this.color2KmlColor(feature.style.strokeOpacity, feature.style.strokeColor.toString())
+                var styleColor = this.createElementNS(this.kmlns, "color");
+                var color = (kmlColor);
+                styleColor.appendChild(this.createTextNode(color));
+                linestyleNode.appendChild(styleColor);
+            }
+            // Stroke width
+            if (feature.style.strokeWidth) {
+                var styleWidth = this.createElementNS(this.kmlns, "width");
+                var width = (feature.style.strokeWidth);
+                styleWidth.appendChild(this.createTextNode(width));
+                linestyleNode.appendChild(styleWidth);
+            }
+            styleNode.appendChild(linestyleNode);
+        }
+
+        // Get the poly style
+        if (feature.style.fillColor && (feature.style.fillColor != "none") && feature.style.fillOpacity) {
+            var polystyleNode = this.createElementNS(this.kmlns, "PolyStyle");
+            // Stroke color and opacity
+            if (feature.style.fillColor && feature.style.fillOpacity) {
+                var kmlColor = this.color2KmlColor(feature.style.fillOpacity, feature.style.fillColor.toString())
+                var styleColor = this.createElementNS(this.kmlns, "color");
+                var color = (kmlColor);
+                styleColor.appendChild(this.createTextNode(color));
+                polystyleNode.appendChild(styleColor);
+            }
+            styleNode.appendChild(polystyleNode);
+        }
+
+        // Get the icon style
+        if (feature.style.externalGraphic) {
+            var iconstyleNode = this.createElementNS(this.kmlns, "IconStyle");
+            var iconNode = this.createElementNS(this.kmlns, "Icon");
+
+            // Add href
+            var href = this.createElementNS(this.kmlns, "href");
+            href.appendChild(this.createTextNode(feature.style.externalGraphic));
+            iconNode.appendChild(href);
+
+            iconstyleNode.appendChild(iconNode);
+            styleNode.appendChild(iconstyleNode);
+        }
+
+        return styleNode;
+    },
+
+
+    /**
      * Method: buildGeometryNode
      * Builds and returns a KML geometry node with the given geometry.
-     * 
+     *
      * Parameters:
      * geometry - {<OpenLayers.Geometry>}
-     * 
+     *
      * Returns:
      * {DOMElement}
      */
     buildGeometryNode: function(geometry) {
         if (this.internalProjection && this.externalProjection) {
             geometry = geometry.clone();
-            geometry.transform(this.internalProjection, 
-                               this.externalProjection);
-        }                       
+            geometry.transform(this.internalProjection,
+                    this.externalProjection);
+        }
         var className = geometry.CLASS_NAME;
         var type = className.substring(className.lastIndexOf(".") + 1);
         var builder = this.buildGeometry[type.toLowerCase()];
         var node = null;
-        if(builder) {
+        if (builder) {
             node = builder.apply(this, [geometry]);
         }
         return node;
@@ -1081,7 +1207,7 @@
             kml.appendChild(this.buildCoordinatesNode(geometry));
             return kml;
         },
-        
+
         /**
          * Method: buildGeometry.multipoint
          * Given an OpenLayers multipoint geometry, create a KML
@@ -1112,7 +1238,7 @@
             kml.appendChild(this.buildCoordinatesNode(geometry));
             return kml;
         },
-        
+
         /**
          * Method: buildGeometry.multilinestring
          * Given an OpenLayers multilinestring geometry, create a KML
@@ -1143,7 +1269,7 @@
             kml.appendChild(this.buildCoordinatesNode(geometry));
             return kml;
         },
-        
+
         /**
          * Method: buildGeometry.polygon
          * Given an OpenLayers polygon geometry, create a KML polygon.
@@ -1158,17 +1284,17 @@
             var kml = this.createElementNS(this.kmlns, "Polygon");
             var rings = geometry.components;
             var ringMember, ringGeom, type;
-            for(var i=0, len=rings.length; i<len; ++i) {
-                type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+            for (var i = 0, len = rings.length; i < len; ++i) {
+                type = (i == 0) ? "outerBoundaryIs" : "innerBoundaryIs";
                 ringMember = this.createElementNS(this.kmlns, type);
                 ringGeom = this.buildGeometry.linearring.apply(this,
-                                                               [rings[i]]);
+                        [rings[i]]);
                 ringMember.appendChild(ringGeom);
                 kml.appendChild(ringMember);
             }
             return kml;
         },
-        
+
         /**
          * Method: buildGeometry.multipolygon
          * Given an OpenLayers multipolygon geometry, create a KML
@@ -1197,10 +1323,10 @@
         collection: function(geometry) {
             var kml = this.createElementNS(this.kmlns, "MultiGeometry");
             var child;
-            for(var i=0, len=geometry.components.length; i<len; ++i) {
+            for (var i = 0, len = geometry.components.length; i < len; ++i) {
                 child = this.buildGeometryNode.apply(this,
-                                                     [geometry.components[i]]);
-                if(child) {
+                        [geometry.components[i]]);
+                if (child) {
                     kml.appendChild(child);
                 }
             }
@@ -1212,24 +1338,24 @@
      * Method: buildCoordinatesNode
      * Builds and returns the KML coordinates node with the given geometry
      * <coordinates>...</coordinates>
-     * 
+     *
      * Parameters:
      * geometry - {<OpenLayers.Geometry>}
-     * 
+     *
      * Return:
      * {DOMElement}
-     */     
+     */
     buildCoordinatesNode: function(geometry) {
         var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
-        
+
         var path;
         var points = geometry.components;
-        if(points) {
+        if (points) {
             // LineString or LinearRing
             var point;
             var numPoints = points.length;
             var parts = new Array(numPoints);
-            for(var i=0; i<numPoints; ++i) {
+            for (var i = 0; i < numPoints; ++i) {
                 point = points[i];
                 parts[i] = point.x + "," + point.y;
             }
@@ -1238,12 +1364,46 @@
             // Point
             path = geometry.x + "," + geometry.y;
         }
-        
+
         var txtNode = this.createTextNode(path);
         coordinatesNode.appendChild(txtNode);
-        
+
         return coordinatesNode;
-    },    
+    },
 
-    CLASS_NAME: "OpenLayers.Format.KML" 
+    /**
+     * Method: color2KmlColor
+     * Convert an opacity (from 0 to 1) and a colorcode (#ff2233) to a KML color code
+     *
+     * Parameters:
+     * opacity
+     * color_code - hexadecimal color codr
+     *
+     * Return:
+     * KML color code
+     */
+    color2KmlColor: function(opacity, color_code) {
+        var opacityHex = (opacity * 255).toString(16).toUpperCase();
+        color_code = color_code.replace('#', '').toUpperCase();
+        var matches = (color_code.toString()).match(this.regExes.color);
+
+        return opacityHex.toString() + matches[3].toString() + matches[2].toString() + matches[1].toString();
+
+    },
+
+    /**
+     * Method: getKmlVersion
+     * Get the KML version from the KML namespace
+     *
+     * Parameters:
+     *
+     * Return:
+     * KML version number
+     */
+    getKmlVersion: function() {
+        var matches = this.kmlns.toString().match(this.regExes.version);
+        return matches[0];
+    },
+
+    CLASS_NAME: "OpenLayers.Format.KML"
 });
