More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Format / Atom.js
blob:a/labs/openlayers/lib/OpenLayers/Format/Atom.js -> blob:b/labs/openlayers/lib/OpenLayers/Format/Atom.js
--- a/labs/openlayers/lib/OpenLayers/Format/Atom.js
+++ b/labs/openlayers/lib/OpenLayers/Format/Atom.js
@@ -1,1 +1,728 @@
-
+/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for 
+ * full list of contributors). Published under the Clear BSD license.  
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/GML/v3.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Atom
+ * Read/write Atom feeds. Create a new instance with the
+ *     <OpenLayers.Format.AtomFeed> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Atom = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * Property: namespaces
+     * {Object} Mapping of namespace aliases to namespace URIs.  Properties
+     *     of this object should not be set individually.  Read-only.  All
+     *     XML subclasses should have their own namespaces object.  Use
+     *     <setNamespace> to add or set a namespace alias after construction.
+     */
+    namespaces: {
+        atom: "http://www.w3.org/2005/Atom",
+        georss: "http://www.georss.org/georss"
+    },
+    
+    /**
+     * APIProperty: feedTitle
+     * {String} Atom feed elements require a title.  Default is "untitled".
+     */
+    feedTitle: "untitled",
+
+    /**
+     * APIProperty: defaultEntryTitle
+     * {String} Atom entry elements require a title.  In cases where one is
+     *     not provided in the feature attributes, this will be used.  Default
+     *     is "untitled".
+     */
+    defaultEntryTitle: "untitled",
+
+    /**
+     * Property: gmlParse
+     * {Object} GML Format object for parsing features
+     * Non-API and only created if necessary
+     */
+    gmlParser: null,
+    
+    /**
+     * APIProperty: xy
+     * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x)
+     * For GeoRSS the default is (y,x), therefore: false
+     */
+    xy: false,
+    
+    /**
+     * Constructor: OpenLayers.Format.AtomEntry
+     * Create a new parser for Atom.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Return a list of features from an Atom feed or entry document.
+     
+     * Parameters:
+     * doc - {Element} or {String}
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(doc) {
+        if (typeof doc == "string") {
+            doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+        }
+        return this.parseFeatures(doc);
+    },
+    
+    /**
+     * APIMethod: write
+     * Serialize or more feature nodes to Atom documents.
+     *
+     * Parameters:
+     * features - a single {<OpenLayers.Feature.Vector>} or an
+     * Array({<OpenLayers.Feature.Vector>}).
+     *
+     * Returns:
+     * {String} an Atom entry document if passed one feature node, or a feed
+     * document if passed an array of feature nodes.
+     */
+    write: function(features) {
+        var doc;
+        if (features instanceof Array) {
+            doc = this.createElementNSPlus("atom:feed");
+            doc.appendChild(
+                this.createElementNSPlus("atom:title", {
+                    value: this.feedTitle
+                })
+            );
+            for (var i=0, ii=features.length; i<ii; i++) {
+                doc.appendChild(this.buildEntryNode(features[i]));
+            }
+        }
+        else {
+            doc = this.buildEntryNode(features);
+        }
+        return OpenLayers.Format.XML.prototype.write.apply(this, [doc]);
+    },
+    
+    /**
+     * Method: buildContentNode
+     *
+     * Parameters:
+     * content - {Object}
+     *
+     * Returns:
+     * {DOMElement} an Atom content node.
+     *
+     * TODO: types other than text.
+     */
+    buildContentNode: function(content) {
+        var node = this.createElementNSPlus("atom:content", {
+            attributes: {
+                type: content.type || null
+            }
+        });
+        if (content.src) {
+            node.setAttribute("src", content.src);
+        } else {
+            if (content.type == "text" || content.type == null) {
+                node.appendChild(
+                    this.createTextNode(content.value)
+                );
+            } else if (content.type == "html") {
+                if (typeof content.value != "string") {
+                    throw "HTML content must be in form of an escaped string";
+                }
+                node.appendChild(
+                    this.createTextNode(content.value)
+                );
+            } else if (content.type == "xhtml") {
+                node.appendChild(content.value);
+            } else if (content.type == "xhtml" ||
+                           content.type.match(/(\+|\/)xml$/)) {
+                node.appendChild(content.value);
+            }
+            else { // MUST be a valid Base64 encoding
+                node.appendChild(
+                    this.createTextNode(content.value)
+                );
+            }
+        }
+        return node;
+    },
+    
+    /**
+     * Method: buildEntryNode
+     * Build an Atom entry node from a feature object.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     *
+     * Returns:
+     * {DOMElement} an Atom entry node.
+     *
+     * These entries are geared for publication using AtomPub.
+     *
+     * TODO: support extension elements
+     */
+    buildEntryNode: function(feature) {
+        var attrib = feature.attributes;
+        var atomAttrib = attrib.atom || {};
+        var entryNode = this.createElementNSPlus("atom:entry");
+        
+        // atom:author
+        if (atomAttrib.authors) {
+            var authors = atomAttrib.authors instanceof Array ?
+                atomAttrib.authors : [atomAttrib.authors];
+            for (var i=0, ii=authors.length; i<ii; i++) {
+                entryNode.appendChild(
+                    this.buildPersonConstructNode(
+                        "author", authors[i]
+                    )
+                );
+            }
+        }
+        
+        // atom:category
+        if (atomAttrib.categories) {
+            var categories = atomAttrib.categories instanceof Array ?
+                atomAttrib.categories : [atomAttrib.categories];
+            var category;
+            for (var i=0, ii=categories.length; i<ii; i++) {
+                category = categories[i];
+                entryNode.appendChild(
+                    this.createElementNSPlus("atom:category", {
+                        attributes: {
+                            term: category.term,
+                            scheme: category.scheme || null,
+                            label: category.label || null
+                        }
+                    })
+                );
+            }
+        }
+        
+        // atom:content
+        if (atomAttrib.content) {
+            entryNode.appendChild(this.buildContentNode(atomAttrib.content));
+        }
+        
+        // atom:contributor
+        if (atomAttrib.contributors) {
+            var contributors = atomAttrib.contributors instanceof Array ?
+                atomAttrib.contributors : [atomAttrib.contributors];
+            for (var i=0, ii=contributors.length; i<ii; i++) {
+                entryNode.appendChild(
+                    this.buildPersonConstructNode(
+                        "contributor",
+                        contributors[i]
+                        )
+                    );
+            }
+        }
+        
+        // atom:id
+        if (feature.fid) {
+            entryNode.appendChild(
+                this.createElementNSPlus("atom:id", {
+                    value: feature.fid
+                })
+            );
+        }
+        
+        // atom:link
+        if (atomAttrib.links) {
+            var links = atomAttrib.links instanceof Array ?
+                atomAttrib.links : [atomAttrib.links];
+            var link;
+            for (var i=0, ii=links.length; i<ii; i++) {
+                link = links[i];
+                entryNode.appendChild(
+                    this.createElementNSPlus("atom:link", {
+                        attributes: {
+                            href: link.href,
+                            rel: link.rel || null,
+                            type: link.type || null,
+                            hreflang: link.hreflang || null,
+                            title: link.title || null,
+                            length: link.length || null
+                        }
+                    })
+                );
+            }
+        }
+        
+        // atom:published
+        if (atomAttrib.published) {
+            entryNode.appendChild(
+                this.createElementNSPlus("atom:published", {
+                    value: atomAttrib.published
+                })
+            );
+        }
+        
+        // atom:rights
+        if (atomAttrib.rights) {
+            entryNode.appendChild(
+                this.createElementNSPlus("atom:rights", {
+                    value: atomAttrib.rights
+                })
+            );
+        }
+        
+        // atom:source not implemented
+        
+        // atom:summary
+        if (atomAttrib.summary || attrib.description) {
+            entryNode.appendChild(
+                this.createElementNSPlus("atom:summary", {
+                    value: atomAttrib.summary || attrib.description
+                })
+            );
+        }
+        
+        // atom:title
+        entryNode.appendChild(
+            this.createElementNSPlus("atom:title", {
+                value: atomAttrib.title || attrib.title || this.defaultEntryTitle
+            })
+        );
+        
+        // atom:updated
+        if (atomAttrib.updated) {
+            entryNode.appendChild(
+                this.createElementNSPlus("atom:updated", {
+                    value: atomAttrib.updated
+                })
+            );
+        }
+        
+        // georss:where
+        if (feature.geometry) {
+            var whereNode = this.createElementNSPlus("georss:where");
+            whereNode.appendChild(
+                this.buildGeometryNode(feature.geometry)
+            );
+            entryNode.appendChild(whereNode);
+        }
+        
+        return entryNode;
+    },
+    
+    /**
+     * Method: initGmlParser
+     * Creates a GML parser.
+     */
+    initGmlParser: function() {
+        this.gmlParser = new OpenLayers.Format.GML.v3({
+            xy: this.xy,
+            featureNS: "http://example.com#feature",
+            internalProjection: this.internalProjection,
+            externalProjection: this.externalProjection
+        });
+    },
+    
+    /**
+     * Method: buildGeometryNode
+     * builds a GeoRSS node with a given geometry
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     *
+     * Returns:
+     * {DOMElement} A gml node.
+     */
+    buildGeometryNode: function(geometry) {
+        if (!this.gmlParser) {
+            this.initGmlParser();
+        }
+        var node = this.gmlParser.writeNode("feature:_geometry", geometry);
+        return node.firstChild;
+    },
+    
+    /**
+     * Method: buildPersonConstructNode
+     *
+     * Parameters:
+     * name - {String}
+     * value - {Object}
+     *
+     * Returns:
+     * {DOMElement} an Atom person construct node.
+     *
+     * Example:
+     * >>> buildPersonConstructNode("author", {name: "John Smith"})
+     * {<author><name>John Smith</name></author>}
+     *
+     * TODO: how to specify extension elements? Add to the oNames array?
+     */
+    buildPersonConstructNode: function(name, value) {
+        var oNames = ["uri", "email"];
+        var personNode = this.createElementNSPlus("atom:" + name);
+        personNode.appendChild(
+            this.createElementNSPlus("atom:name", {
+                value: value.name
+            })
+        );
+        for (var i=0, ii=oNames.length; i<ii; i++) {
+            if (value[oNames[i]]) {
+                personNode.appendChild(
+                    this.createElementNSPlus("atom:" + oNames[i], {
+                        value: value[oNames[i]]
+                    })
+                );
+            }
+        }
+        return personNode;
+    },
+    
+    /**
+     * Method: getFirstChildValue
+     *
+     * Parameters:
+     * node - {DOMElement}
+     * nsuri - {String} Child node namespace uri ("*" for any).
+     * name - {String} Child node name.
+     * def - {String} Optional string default to return if no child found.
+     *
+     * Returns:
+     * {String} The value of the first child with the given tag name.  Returns
+     *     default value or empty string if none found.
+     */
+    getFirstChildValue: function(node, nsuri, name, def) {
+        var value;
+        var nodes = this.getElementsByTagNameNS(node, nsuri, name);
+        if (nodes && nodes.length > 0) {
+            value = this.getChildValue(nodes[0], def);
+        } else {
+            value = def;
+        }
+        return value;
+    },
+    
+    /**
+     * Method: parseFeature
+     * Parse feature from an Atom entry node..
+     *
+     * Parameters:
+     * node - {DOMElement} An Atom entry or feed node.
+     *
+     * Returns:
+     * An <OpenLayers.Feature.Vector>.
+     */
+    parseFeature: function(node) {
+        var atomAttrib = {};
+        var value = null;
+        var nodes = null;
+        var attval = null;
+        var atomns = this.namespaces.atom;
+        
+        // atomAuthor*
+        this.parsePersonConstructs(node, "author", atomAttrib);
+        
+        // atomCategory*
+        nodes = this.getElementsByTagNameNS(node, atomns, "category");
+        if (nodes.length > 0) {
+            atomAttrib.categories = [];
+        }
+        for (var i=0, ii=nodes.length; i<ii; i++) {
+            value = {};
+            value.term = nodes[i].getAttribute("term");
+            attval = nodes[i].getAttribute("scheme");
+            if (attval) { value.scheme = attval; }
+            attval = nodes[i].getAttribute("label");
+            if (attval) { value.label = attval; }
+            atomAttrib.categories.push(value);
+        }
+        
+        // atomContent?
+        nodes = this.getElementsByTagNameNS(node, atomns, "content");
+        if (nodes.length > 0) {
+            value = {};
+            attval = nodes[0].getAttribute("type");
+            if (attval) {
+                value.type = attval;
+            }
+            attval = nodes[0].getAttribute("src");
+            if (attval) {
+                value.src = attval;
+            } else {
+                if (value.type == "text" || 
+                    value.type == "html" || 
+                    value.type == null ) {
+                    value.value = this.getFirstChildValue(
+                                        node,
+                                        atomns,
+                                        "content",
+                                        null
+                                        );
+                } else if (value.type == "xhtml" ||
+                           value.type.match(/(\+|\/)xml$/)) {
+                    value.value = this.getChildEl(nodes[0]);
+                } else { // MUST be base64 encoded
+                    value.value = this.getFirstChildValue(
+                                        node,
+                                        atomns,
+                                        "content",
+                                        null
+                                        );
+                }
+                atomAttrib.content = value;
+            }
+        }
+        
+        // atomContributor*
+        this.parsePersonConstructs(node, "contributor", atomAttrib);
+        
+        // atomId
+        atomAttrib.id = this.getFirstChildValue(node, atomns, "id", null);
+        
+        // atomLink*
+        nodes = this.getElementsByTagNameNS(node, atomns, "link");
+        if (nodes.length > 0) {
+            atomAttrib.links = new Array(nodes.length);
+        }
+        var oAtts = ["rel", "type", "hreflang", "title", "length"];
+        for (var i=0, ii=nodes.length; i<ii; i++) {
+            value = {};
+            value.href = nodes[i].getAttribute("href");
+            for (var j=0, jj=oAtts.length; j<jj; j++) {
+                attval = nodes[i].getAttribute(oAtts[j]);
+                if (attval) {
+                    value[oAtts[j]] = attval;
+                }
+            }
+            atomAttrib.links[i] = value;
+        }
+        
+        // atomPublished?
+        value = this.getFirstChildValue(node, atomns, "published", null);
+        if (value) {
+            atomAttrib.published = value;
+        }
+        
+        // atomRights?
+        value = this.getFirstChildValue(node, atomns, "rights", null);
+        if (value) {
+            atomAttrib.rights = value;
+        }
+        
+        // atomSource? -- not implemented
+        
+        // atomSummary?
+        value = this.getFirstChildValue(node, atomns, "summary", null);
+        if (value) {
+            atomAttrib.summary = value;
+        }
+        
+        // atomTitle
+        atomAttrib.title = this.getFirstChildValue(
+                                node, atomns, "title", null
+                                );
+        
+        // atomUpdated
+        atomAttrib.updated = this.getFirstChildValue(
+                                node, atomns, "updated", null
+                                );
+        
+        var featureAttrib = {
+            title: atomAttrib.title,
+            description: atomAttrib.summary,
+            atom: atomAttrib
+        };
+        var geometry = this.parseLocations(node)[0];
+        var feature = new OpenLayers.Feature.Vector(geometry, featureAttrib);
+        feature.fid = atomAttrib.id;
+        return feature;
+    },
+    
+    /**
+     * Method: parseFeatures
+     * Return features from an Atom entry or feed.
+     *
+     * Parameters:
+     * node - {DOMElement} An Atom entry or feed node.
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s.
+     */
+    parseFeatures: function(node) {
+        var features = [];
+        var entries = this.getElementsByTagNameNS(
+            node, this.namespaces.atom, "entry"
+        );
+        if (entries.length == 0) {
+            entries = [node];
+        }
+        for (var i=0, ii=entries.length; i<ii; i++) {
+            features.push(this.parseFeature(entries[i]));
+        }
+        return features;
+    },
+    
+    /**
+     * Method: parseLocations
+     * Parse the locations from an Atom entry or feed.
+     *
+     * Parameters:
+     * node - {DOMElement} An Atom entry or feed node.
+     *
+     * Returns:
+     * An Array of <OpenLayers.Geometry>s.
+     */
+    parseLocations: function(node) {
+        var georssns = this.namespaces.georss;
+
+        var locations = {components: []};
+        var where = this.getElementsByTagNameNS(node, georssns, "where");
+        if (where && where.length > 0) {
+            if (!this.gmlParser) {
+                this.initGmlParser();
+            }
+            for (var i=0, ii=where.length; i<ii; i++) {
+                this.gmlParser.readChildNodes(where[i], locations);
+            }
+        }
+        
+        var components = locations.components;
+        var point = this.getElementsByTagNameNS(node, georssns, "point");
+        if (point && point.length > 0) {
+            for (var i=0, ii=point.length; i<ii; i++) {
+                var xy = OpenLayers.String.trim(
+                            point[i].firstChild.nodeValue
+                            ).split(/\s+/);
+                if (xy.length !=2) {
+                    xy = OpenLayers.String.trim(
+                                point[i].firstChild.nodeValue
+                                ).split(/\s*,\s*/);
+                }
+                components.push(
+                    new OpenLayers.Geometry.Point(
+                        parseFloat(xy[1]),
+                        parseFloat(xy[0])
+                    )
+                );
+            }
+        }
+
+        var line = this.getElementsByTagNameNS(node, georssns, "line");
+        if (line && line.length > 0) {
+            var coords;
+            var p;
+            var points;
+            for (var i=0, ii=line.length; i<ii; i++) {
+                coords = OpenLayers.String.trim(
+                                line[i].firstChild.nodeValue
+                                ).split(/\s+/);
+                points = [];
+                for (var j=0, jj=coords.length; j<jj; j+=2) {
+                    p = new OpenLayers.Geometry.Point(
+                        parseFloat(coords[j+1]),
+                        parseFloat(coords[j])
+                    );
+                    points.push(p);
+                }
+                components.push(
+                    new OpenLayers.Geometry.LineString(points)
+                );
+            }
+        }        
+
+        var polygon = this.getElementsByTagNameNS(node, georssns, "polygon");
+        if (polygon && polygon.length > 0) {
+            var coords;
+            var p;
+            var points;
+            for (var i=0, ii=polygon.length; i<ii; i++) {
+                coords = OpenLayers.String.trim(
+                            polygon[i].firstChild.nodeValue
+                            ).split(/\s+/);
+                points = [];
+                for (var j=0, jj=coords.length; j<jj; j+=2) {
+                    p = new OpenLayers.Geometry.Point(
+                        parseFloat(coords[j+1]),
+                        parseFloat(coords[j])
+                    );
+                    points.push(p);
+                }
+                components.push(
+                    new OpenLayers.Geometry.Polygon(
+                        [new OpenLayers.Geometry.LinearRing(components)]
+                    )
+                );
+            }
+        }
+        
+        if (this.internalProjection && this.externalProjection) {
+            for (var i=0, ii=components.length; i<ii; i++) {
+                if (components[i]) {
+                    components[i].transform(
+                        this.externalProjection,
+                        this.internalProjection
+                    );
+                }
+            }
+        }
+        
+        return components;
+    },
+    
+    /**
+     * Method: parsePersonConstruct
+     * Parse Atom person constructs from an Atom entry node.
+     *
+     * Parameters:
+     * node - {DOMElement} An Atom entry or feed node.
+     * name - {String} Construcy name ("author" or "contributor")
+     * data = {Object} Object in which to put parsed persons.
+     *
+     * Returns:
+     * An {Object}.
+     */
+    parsePersonConstructs: function(node, name, data) {
+        var persons = [];
+        var atomns = this.namespaces.atom;
+        var nodes = this.getElementsByTagNameNS(node, atomns, name);
+        var oAtts = ["uri", "email"];
+        for (var i=0, ii=nodes.length; i<ii; i++) {
+            var value = {};
+            value.name = this.getFirstChildValue(
+                            nodes[i],
+                            atomns,
+                            "name",
+                            null
+                            );
+            for (var j=0, jj=oAtts.length; j<jj; j++) {
+                var attval = this.getFirstChildValue(
+                            nodes[i],
+                            atomns,
+                            oAtts[j],
+                            null);
+                if (attval) {
+                    value[oAtts[j]] = attval;
+                }
+            }
+            persons.push(value);
+        }
+        if (persons.length > 0) {
+            data[name + "s"] = persons;
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.Atom"
+});
+