/* 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"
});