/* 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.js
*/
/**
* Class: OpenLayers.Format.XML
* Read and write XML. For cross-browser XML generation, use methods on an
* instance of the XML format class instead of on <code>document<end>.
* The DOM creation and traversing methods exposed here all mimic the
* W3C XML DOM methods. Create a new parser with the
* <OpenLayers.Format.XML> constructor.
*
* Inherits from:
* - <OpenLayers.Format>
*/
OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
/**
* 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: null,
/**
* Property: namespaceAlias
* {Object} Mapping of namespace URI to namespace alias. This object
* is read-only. Use <setNamespace> to add or set a namespace alias.
*/
namespaceAlias: null,
/**
* Property: defaultPrefix
* {String} The default namespace alias for creating element nodes.
*/
defaultPrefix: null,
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {},
/**
* Property: writers
* As a compliment to the <readers> property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {},
/**
* Property: xmldom
* {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
* object. It is not intended to be a browser sniffing property.
* Instead, the xmldom property is used instead of <code>document<end>
* where namespaced node creation methods are not supported. In all
* other browsers, this remains null.
*/
xmldom: null,
/**
* Constructor: OpenLayers.Format.XML
* Construct an XML parser. The parser is used to read and write XML.
* Reading XML from a string returns a DOM element. Writing XML from
* a DOM element returns a string.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on
* the object.
*/
initialize: function(options) {
if(window.ActiveXObject) {
this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
}
OpenLayers.Format.prototype.initialize.apply(this, [options]);
// clone the namespace object and set all namespace aliases
this.namespaces = OpenLayers.Util.extend({}, this.namespaces);
this.namespaceAlias = {};
for(var alias in this.namespaces) {
this.namespaceAlias[this.namespaces[alias]] = alias;
}
},
/**
* APIMethod: destroy
* Clean up.
*/
destroy: function() {
this.xmldom = null;
OpenLayers.Format.prototype.destroy.apply(this, arguments);
},
/**
* Method: setNamespace
* Set a namespace alias and URI for the format.
*
* Parameters:
* alias - {String} The namespace alias (prefix).
* uri - {String} The namespace URI.
*/
setNamespace: function(alias, uri) {
this.namespaces[alias] = uri;
this.namespaceAlias[uri] = alias;
},
/**
* APIMethod: read
* Deserialize a XML string and return a DOM node.
*
* Parameters:
* text - {String} A XML string
* Returns:
* {DOMElement} A DOM node
*/
read: function(text) {
var index = text.indexOf('<');
if(index > 0) {
text = text.substring(index);
}
var node = OpenLayers.Util.Try(
OpenLayers.Function.bind((
function() {
var xmldom;
/**
* Since we want to be able to call this method on the prototype
* itself, this.xmldom may not exist even if in IE.
*/
if(window.ActiveXObject && !this.xmldom) {
xmldom = new ActiveXObject("Microsoft.XMLDOM");
} else {
xmldom = this.xmldom;
}
xmldom.loadXML(text);
return xmldom;
}
), this),
function() {
return new DOMParser().parseFromString(text, 'text/xml');
},
function() {
var req = new XMLHttpRequest();
req.open("GET", "data:" + "text/xml" +
";charset=utf-8," + encodeURIComponent(text), false);
if(req.overrideMimeType) {
req.overrideMimeType("text/xml");
}
req.send(null);
return req.responseXML;
}
);
if(this.keepData) {
this.data = node;
}
return node;
},
/**
* APIMethod: write
* Serialize a DOM node into a XML string.
*
* Parameters:
* node - {DOMElement} A DOM node.
*
* Returns:
* {String} The XML string representation of the input node.
*/
write: function(node) {
var data;
if(this.xmldom) {
data = node.xml;
} else {
var serializer = new XMLSerializer();
if (node.nodeType == 1) {
// Add nodes to a document before serializing. Everything else
// is serialized as is. This may need more work. See #1218 .
var doc = document.implementation.createDocument("", "", null);
if (doc.importNode) {
node = doc.importNode(node, true);
}
doc.appendChild(node);
data = serializer.serializeToString(doc);
} else {
data = serializer.serializeToString(node);
}
}
return data;
},
/**
* APIMethod: createElementNS
* Create a new element with namespace. This node can be appended to
* another node with the standard node.appendChild method. For
* cross-browser support, this method must be used instead of
* document.createElementNS.
*
* Parameters:
* uri - {String} Namespace URI for the element.
* name - {String} The qualified name of the element (prefix:localname).
*
* Returns:
* {Element} A DOM element with namespace.
*/
createElementNS: function(uri, name) {
var element;
if(this.xmldom) {
if(typeof uri == "string") {
element = this.xmldom.createNode(1, name, uri);
} else {
element = this.xmldom.createNode(1, name, "");
}
} else {
element = document.createElementNS(uri, name);
}
return element;
},
/**
* APIMethod: createTextNode
* Create a text node. This node can be appended to another node with
* the standard node.appendChild method. For cross-browser support,
* this method must be used instead of document.createTextNode.
*
* Parameters:
* text - {String} The text of the node.
*
* Returns:
* {DOMElement} A DOM text node.
*/
createTextNode: function(text) {
var node;
if (typeof text !== "string") {
text = String(text);
}
if(this.xmldom) {
node = this.xmldom.createTextNode(text);
} else {
node = document.createTextNode(text);
}
return node;
},
/**
* APIMethod: getElementsByTagNameNS
* Get a list of elements on a node given the namespace URI and local name.
* To return all nodes in a given namespace, use '*' for the name
* argument. To return all nodes of a given (local) name, regardless
* of namespace, use '*' for the uri argument.
*
* Parameters:
* node - {Element} Node on which to search for other nodes.
* uri - {String} Namespace URI.
* name - {String} Local name of the tag (without the prefix).
*
* Returns:
* {NodeList} A node list or array of elements.
*/
getElementsByTagNameNS: function(node, uri, name) {
var elements = [];
if(node.getElementsByTagNameNS) {
elements = node.getElementsByTagNameNS(uri, name);
} else {
// brute force method
var allNodes = node.getElementsByTagName("*");
var potentialNode, fullName;
for(var i=0, len=allNodes.length; i<len; ++i) {
potentialNode = allNodes[i];
fullName = (potentialNode.prefix) ?
(potentialNode.prefix + ":" + name) : name;
if((name == "*") || (fullName == potentialNode.nodeName)) {
if((uri == "*") || (uri == potentialNode.namespaceURI)) {
elements.push(potentialNode);
}
}
}
}
return elements;
},
/**
* APIMethod: getAttributeNodeNS
* Get an attribute node given the namespace URI and local name.
*
* Parameters:
* node - {Element} Node on which to search for attribute nodes.
* uri - {String} Namespace URI.
* name - {String} Local name of the attribute (without the prefix).
*
* Returns:
* {DOMElement} An attribute node or null if none found.
*/
getAttributeNodeNS: function(node, uri, name) {
var attributeNode = null;
if(node.getAttributeNodeNS) {
attributeNode = node.getAttributeNodeNS(uri, name);
} else {
var attributes = node.attributes;
var potentialNode, fullName;
for(var i=0, len=attributes.length; i<len; ++i) {
potentialNode = attributes[i];
if(potentialNode.namespaceURI == uri) {
fullName = (potentialNode.prefix) ?
(potentialNode.prefix + ":" + name) : name;
if(fullName == potentialNode.nodeName) {
attributeNode = potentialNode;
break;
}
}
}
}
return attributeNode;
},
/**
* APIMethod: getAttributeNS
* Get an attribute value given the namespace URI and local name.
*
* Parameters:
* node - {Element} Node on which to search for an attribute.
* uri - {String} Namespace URI.
* name - {String} Local name of the attribute (without the prefix).
*
* Returns:
* {String} An attribute value or and empty string if none found.
*/
getAttributeNS: function(node, uri, name) {
var attributeValue = "";
if(node.getAttributeNS) {
attributeValue = node.getAttributeNS(uri, name) || "";
} else {
var attributeNode = this.getAttributeNodeNS(node, uri, name);
if(attributeNode) {
attributeValue = attributeNode.nodeValue;
}
}
return attributeValue;
},
/**
* APIMethod: getChildValue
* Get the textual value of the node if it exists, or return an
* optional default string. Returns an empty string if no first child
* exists and no default value is supplied.
*
* Parameters:
* node - {DOMElement} The element used to look for a first child value.
* def - {String} Optional string to return in the event that no
* first child value exists.
*
* Returns:
* {String} The value of the first child of the given node.
*/
getChildValue: function(node, def) {
var value = def || "";
if(node) {
for(var child=node.firstChild; child; child=child.nextSibling) {
switch(child.nodeType) {
case 3: // text node
case 4: // cdata section
value += child.nodeValue;
}
}
}
return value;
},
/**
* APIMethod: concatChildValues
* *Deprecated*. Use <getChildValue> instead.
*
* Concatenate the value of all child nodes if any exist, or return an
* optional default string. Returns an empty string if no children
* exist and no default value is supplied. Not optimized for large
* numbers of child nodes.
*
* Parameters:
* node - {DOMElement} The element used to look for child values.
* def - {String} Optional string to return in the event that no
* child exist.
*
* Returns:
* {String} The concatenated value of all child nodes of the given node.
*/
concatChildValues: function(node, def) {
var value = "";
var child = node.firstChild;
var childValue;
while(child) {
childValue = child.nodeValue;
if(childValue) {
value += childValue;
}
child = child.nextSibling;
}
if(value == "" && def != undefined) {
value = def;
}
return value;
},
/**
* APIMethod: isSimpleContent
* Test if the given node has only simple content (i.e. no child element
* nodes).
*
* Parameters:
* node - {DOMElement} An element node.
*
* Returns:
* {Boolean} The node has no child element nodes (nodes of type 1).
*/
isSimpleContent: function(node) {
var simple = true;
for(var child=node.firstChild; child; child=child.nextSibling) {
if(child.nodeType === 1) {
simple = false;
break;
}
}
return simple;
},
/**
* APIMethod: contentType
* Determine the content type for a given node.
*
* Parameters:
* node - {DOMElement}
*
* Returns:
* {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
* if the node has no, simple, complex, or mixed content.
*/
contentType: function(node) {
var simple = false,
complex = false;
var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;
for(var child=node.firstChild; child; child=child.nextSibling) {
switch(child.nodeType) {
case 1: // element
complex = true;
break;
case 8: // comment
break;
default:
simple = true;
}
if(complex && simple) {
break;
}
}
if(complex && simple) {
type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
} else if(complex) {
return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
} else if(simple) {
return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
}
return type;
},
/**
* APIMethod: hasAttributeNS
* Determine whether a node has a particular attribute matching the given
* name and namespace.
*
* Parameters:
* node - {Element} Node on which to search for an attribute.
* uri - {String} Namespace URI.
* name - {String} Local name of the attribute (without the prefix).
*
* Returns:
* {Boolean} The node has an attribute matching the name and namespace.
*/
hasAttributeNS: function(node, uri, name) {
var found = false;
if(node.hasAttributeNS) {
found = node.hasAttributeNS(uri, name);
} else {
found = !!this.getAttributeNodeNS(node, uri, name);
}
return found;
},
/**
* APIMethod: setAttributeNS
* Adds a new attribute or changes the value of an attribute with the given
* namespace and name.
*
* Parameters:
* node - {Element} Element node on which to set the attribute.
* uri - {String} Namespace URI for the attribute.
* name - {String} Qualified name (prefix:localname) for the attribute.
* value - {String} Attribute value.
*/
setAttributeNS: function(node, uri, name, value) {
if(node.setAttributeNS) {
node.setAttributeNS(uri, name, value);
} else {
if(this.xmldom) {
if(uri) {
var attribute = node.ownerDocument.createNode(
2, name, uri
);
attribute.nodeValue = value;
node.setAttributeNode(attribute);
} else {
node.setAttribute(name, value);
}
} else {
throw "setAttributeNS not implemented";
}
}
},
/**
* Method: createElementNSPlus
* Shorthand for creating namespaced elements with optional attributes and
* child text nodes.
*
* Parameters:
* name - {String} The qualified node name.
* options - {Object} Optional object for node configuration.
*
* Valid options:
* uri - {String} Optional namespace uri for the element - supply a prefix
* instead if the namespace uri is a property of the format's namespace
* object.
* attributes - {Object} Optional attributes to be set using the
* <setAttributes> method.
* value - {String} Optional text to be appended as a text node.
*
* Returns:
* {Element} An element node.
*/
createElementNSPlus: function(name, options) {
options = options || {};
// order of prefix preference
// 1. in the uri option
// 2. in the prefix option
// 3. in the qualified name
// 4. from the defaultPrefix
var uri = options.uri || this.namespaces[options.prefix];
if(!uri) {
var loc = name.indexOf(":");
uri = this.namespaces[name.substring(0, loc)];
}
if(!uri) {
uri = this.namespaces[this.defaultPrefix];
}
var node = this.createElementNS(uri, name);
if(options.attributes) {
this.setAttributes(node, options.attributes);
}
var value = options.value;
if(value != null) {
node.appendChild(this.createTextNode(value));
}
return node;
},
/**
* Method: setAttributes
* Set multiple attributes given key value pairs from an object.
*
* Parameters:
* node - {Element} An element node.
* obj - {Object || Array} An object whose properties represent attribute
* names and values represent attribute values. If an attribute name
* is a qualified name ("prefix:local"), the prefix will be looked up
* in the parsers {namespaces} object. If the prefix is found,
* setAttributeNS will be used instead of setAttribute.
*/
setAttributes: function(node, obj) {
var value, uri;
for(var name in obj) {
if(obj[name] != null && obj[name].toString) {
value = obj[name].toString();
// check for qualified attribute name ("prefix:local")
uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
this.setAttributeNS(node, uri, name, value);
}
}
},
/**
* Method: readNode
* Shorthand for applying one of the named readers given the node
* namespace and local name. Readers take two args (node, obj) and
* generally extend or modify the second.
*
* Parameters:
* node - {DOMElement} The node to be read (required).
* obj - {Object} The object to be modified (optional).
*
* Returns:
* {Object} The input object, modified (or a new one if none was provided).
*/
readNode: function(node, obj) {
if(!obj) {
obj = {};
}
var group = this.readers[node.namespaceURI ? this.namespaceAlias[node.namespaceURI]: this.defaultPrefix];
if(group) {
var local = node.localName || node.nodeName.split(":").pop();
var reader = group[local] || group["*"];
if(reader) {
reader.apply(this, [node, obj]);
}
}
return obj;
},
/**
* Method: readChildNodes
* Shorthand for applying the named readers to all children of a node.
* For each child of type 1 (element), <readSelf> is called.
*
* Parameters:
* node - {DOMElement} The node to be read (required).
* obj - {Object} The object to be modified (optional).
*
* Returns:
* {Object} The input object, modified.
*/
readChildNodes: function(node, obj) {
if(!obj) {
obj = {};
}
var children = node.childNodes;
var child;
for(var i=0, len=children.length; i<len; ++i) {
child = children[i];
if(child.nodeType == 1) {
this.readNode(child, obj);
}
}
return obj;
},
/**
* Method: writeNode
* Shorthand for applying one of the named writers and appending the
* results to a node. If a qualified name is not provided for the
* second argument (and a local name is used instead), the namespace
* of the parent node will be assumed.
*
* Parameters:
* name - {String} The name of a node to generate. If a qualified name
* (e.g. "pre:Name") is used, the namespace prefix is assumed to be
* in the <writers> group. If a local name is used (e.g. "Name") then
* the namespace of the parent is assumed. If a local name is used
* and no parent is supplied, then the default namespace is assumed.
* obj - {Object} Structure containing data for the writer.
* parent - {DOMElement} Result will be appended to this node. If no parent
* is supplied, the node will not be appended to anything.
*
* Returns:
* {DOMElement} The child node.
*/
writeNode: function(name, obj, parent) {
var prefix, local;
var split = name.indexOf(":");
if(split > 0) {
prefix = name.substring(0, split);
local = name.substring(split + 1);
} else {
if(parent) {
prefix = this.namespaceAlias[parent.namespaceURI];
} else {
prefix = this.defaultPrefix;
}
local = name;
}
var child = this.writers[prefix][local].apply(this, [obj]);
if(parent) {
parent.appendChild(child);
}
return child;
},
/**
* APIMethod: getChildEl
* Get the first child element. Optionally only return the first child
* if it matches the given name and namespace URI.
*
* Parameters:
* node - {DOMElement} The parent node.
* name - {String} Optional node name (local) to search for.
* uri - {String} Optional namespace URI to search for.
*
* Returns:
* {DOMElement} The first child. Returns null if no element is found, if
* something significant besides an element is found, or if the element
* found does not match the optional name and uri.
*/
getChildEl: function(node, name, uri) {
return node && this.getThisOrNextEl(node.firstChild, name, uri);
},
/**
* APIMethod: getNextEl
* Get the next sibling element. Optionally get the first sibling only
* if it matches the given local name and namespace URI.
*
* Parameters:
* node - {DOMElement} The node.
* name - {String} Optional local name of the sibling to search for.
* uri - {String} Optional namespace URI of the sibling to search for.
*
* Returns:
* {DOMElement} The next sibling element. Returns null if no element is
* found, something significant besides an element is found, or the
* found element does not match the optional name and uri.
*/
getNextEl: function(node, name, uri) {
return node && this.getThisOrNextEl(node.nextSibling, name, uri);
},
/**
* Method: getThisOrNextEl
* Return this node or the next element node. Optionally get the first
* sibling with the given local name or namespace URI.
*
* Parameters:
* node - {DOMElement} The node.
* name - {String} Optional local name of the sibling to search for.
* uri - {String} Optional namespace URI of the sibling to search for.
*
* Returns:
* {DOMElement} The next sibling element. Returns null if no element is
* found, something significant besides an element is found, or the
* found element does not match the query.
*/
getThisOrNextEl: function(node, name, uri) {
outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) {
switch(sibling.nodeType) {
case 1: // Element
if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) &&
(!uri || uri === sibling.namespaceURI)) {
// matches
break outer;
}
sibling = null;
break outer;
case 3: // Text
if(/^\s*$/.test(sibling.nodeValue)) {
break;
}
case 4: // CDATA
case 6: // ENTITY_NODE
case 12: // NOTATION_NODE
case 10: // DOCUMENT_TYPE_NODE
case 11: // DOCUMENT_FRAGMENT_NODE
sibling = null;
break outer;
} // ignore comments and processing instructions
}
return sibling || null;
},
/**
* APIMethod: lookupNamespaceURI
* Takes a prefix and returns the namespace URI associated with it on the given
* node if found (and null if not). Supplying null for the prefix will
* return the default namespace.
*
* For browsers that support it, this calls the native lookupNamesapceURI
* function. In other browsers, this is an implementation of
* http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
*
* For browsers that don't support the attribute.ownerElement property, this
* method cannot be called on attribute nodes.
*
* Parameters:
* node - {DOMElement} The node from which to start looking.
* prefix - {String} The prefix to lookup or null to lookup the default namespace.
*
* Returns:
* {String} The namespace URI for the given prefix. Returns null if the prefix
* cannot be found or the node is the wrong type.
*/
lookupNamespaceURI: function(node, prefix) {
var uri = null;
if(node) {
if(node.lookupNamespaceURI) {
uri = node.lookupNamespaceURI(prefix);
} else {
outer: switch(node.nodeType) {
case 1: // ELEMENT_NODE
if(node.namespaceURI !== null && node.prefix === prefix) {
uri = node.namespaceURI;
break outer;
}
var len = node.attributes.length;
if(len) {
var attr;
for(var i=0; i<len; ++i) {
attr = node.attributes[i];
if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
uri = attr.value || null;
break outer;
} else if(attr.name === "xmlns" && prefix === null) {
uri = attr.value || null;
break outer;
}
}
}
uri = this.lookupNamespaceURI(node.parentNode, prefix);
break outer;
case 2: // ATTRIBUTE_NODE
uri = this.lookupNamespaceURI(node.ownerElement, prefix);
break outer;
case 9: // DOCUMENT_NODE
uri = this.lookupNamespaceURI(node.documentElement, prefix);
break outer;
case 6: // ENTITY_NODE
case 12: // NOTATION_NODE
case 10: // DOCUMENT_TYPE_NODE
case 11: // DOCUMENT_FRAGMENT_NODE
break outer;
default:
// TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
// PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
uri = this.lookupNamespaceURI(node.parentNode, prefix);
break outer;
}
}
}
return uri;
},
CLASS_NAME: "OpenLayers.Format.XML"
});
OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};
/**
* APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
* Takes a prefix and returns the namespace URI associated with it on the given
* node if found (and null if not). Supplying null for the prefix will
* return the default namespace.
*
* For browsers that support it, this calls the native lookupNamesapceURI
* function. In other browsers, this is an implementation of
* http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
*
* For browsers that don't support the attribute.ownerElement property, this
* method cannot be called on attribute nodes.
*
* Parameters:
* node - {DOMElement} The node from which to start looking.
* prefix - {String} The prefix to lookup or null to lookup the default namespace.
*
* Returns:
* {String} The namespace URI for the given prefix. Returns null if the prefix
* cannot be found or the node is the wrong type.
*/
OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
OpenLayers.Format.XML.prototype.lookupNamespaceURI,
OpenLayers.Format.XML.prototype
);