1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Format.js
10 * Class: OpenLayers.Format.XML
11 * Read and write XML. For cross-browser XML generation, use methods on an
12 * instance of the XML format class instead of on <code>document<end>.
13 * The DOM creation and traversing methods exposed here all mimic the
14 * W3C XML DOM methods. Create a new parser with the
15 * <OpenLayers.Format.XML> constructor.
18 * - <OpenLayers.Format>
20 OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
23 * Property: namespaces
24 * {Object} Mapping of namespace aliases to namespace URIs. Properties
25 * of this object should not be set individually. Read-only. All
26 * XML subclasses should have their own namespaces object. Use
27 * <setNamespace> to add or set a namespace alias after construction.
32 * Property: namespaceAlias
33 * {Object} Mapping of namespace URI to namespace alias. This object
34 * is read-only. Use <setNamespace> to add or set a namespace alias.
39 * Property: defaultPrefix
40 * {String} The default namespace alias for creating element nodes.
46 * Contains public functions, grouped by namespace prefix, that will
47 * be applied when a namespaced node is found matching the function
48 * name. The function will be applied in the scope of this parser
49 * with two arguments: the node being read and a context object passed
56 * As a compliment to the <readers> property, this structure contains public
57 * writing functions grouped by namespace alias and named like the
58 * node names they produce.
64 * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
65 * object. It is not intended to be a browser sniffing property.
66 * Instead, the xmldom property is used instead of <code>document<end>
67 * where namespaced node creation methods are not supported. In all
68 * other browsers, this remains null.
73 * Constructor: OpenLayers.Format.XML
74 * Construct an XML parser. The parser is used to read and write XML.
75 * Reading XML from a string returns a DOM element. Writing XML from
76 * a DOM element returns a string.
79 * options - {Object} Optional object whose properties will be set on
82 initialize: function(options) {
83 if(window.ActiveXObject) {
84 this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
86 OpenLayers.Format.prototype.initialize.apply(this, [options]);
87 // clone the namespace object and set all namespace aliases
88 this.namespaces = OpenLayers.Util.extend({}, this.namespaces);
89 this.namespaceAlias = {};
90 for(var alias in this.namespaces) {
91 this.namespaceAlias[this.namespaces[alias]] = alias;
101 OpenLayers.Format.prototype.destroy.apply(this, arguments);
105 * Method: setNamespace
106 * Set a namespace alias and URI for the format.
109 * alias - {String} The namespace alias (prefix).
110 * uri - {String} The namespace URI.
112 setNamespace: function(alias, uri) {
113 this.namespaces[alias] = uri;
114 this.namespaceAlias[uri] = alias;
119 * Deserialize a XML string and return a DOM node.
122 * text - {String} A XML string
125 * {DOMElement} A DOM node
127 read: function(text) {
128 var index = text.indexOf('<');
130 text = text.substring(index);
132 var node = OpenLayers.Util.Try(
133 OpenLayers.Function.bind((
137 * Since we want to be able to call this method on the prototype
138 * itself, this.xmldom may not exist even if in IE.
140 if(window.ActiveXObject && !this.xmldom) {
141 xmldom = new ActiveXObject("Microsoft.XMLDOM");
143 xmldom = this.xmldom;
146 xmldom.loadXML(text);
151 return new DOMParser().parseFromString(text, 'text/xml');
154 var req = new XMLHttpRequest();
155 req.open("GET", "data:" + "text/xml" +
156 ";charset=utf-8," + encodeURIComponent(text), false);
157 if(req.overrideMimeType) {
158 req.overrideMimeType("text/xml");
161 return req.responseXML;
174 * Serialize a DOM node into a XML string.
177 * node - {DOMElement} A DOM node.
180 * {String} The XML string representation of the input node.
182 write: function(node) {
187 var serializer = new XMLSerializer();
188 if (node.nodeType == 1) {
189 // Add nodes to a document before serializing. Everything else
190 // is serialized as is. This may need more work. See #1218 .
191 var doc = document.implementation.createDocument("", "", null);
192 if (doc.importNode) {
193 node = doc.importNode(node, true);
195 doc.appendChild(node);
196 data = serializer.serializeToString(doc);
198 data = serializer.serializeToString(node);
205 * APIMethod: createElementNS
206 * Create a new element with namespace. This node can be appended to
207 * another node with the standard node.appendChild method. For
208 * cross-browser support, this method must be used instead of
209 * document.createElementNS.
212 * uri - {String} Namespace URI for the element.
213 * name - {String} The qualified name of the element (prefix:localname).
216 * {Element} A DOM element with namespace.
218 createElementNS: function(uri, name) {
221 if(typeof uri == "string") {
222 element = this.xmldom.createNode(1, name, uri);
224 element = this.xmldom.createNode(1, name, "");
227 element = document.createElementNS(uri, name);
233 * APIMethod: createTextNode
234 * Create a text node. This node can be appended to another node with
235 * the standard node.appendChild method. For cross-browser support,
236 * this method must be used instead of document.createTextNode.
239 * text - {String} The text of the node.
242 * {DOMElement} A DOM text node.
244 createTextNode: function(text) {
247 node = this.xmldom.createTextNode(text);
249 node = document.createTextNode(text);
255 * APIMethod: getElementsByTagNameNS
256 * Get a list of elements on a node given the namespace URI and local name.
257 * To return all nodes in a given namespace, use '*' for the name
258 * argument. To return all nodes of a given (local) name, regardless
259 * of namespace, use '*' for the uri argument.
262 * node - {Element} Node on which to search for other nodes.
263 * uri - {String} Namespace URI.
264 * name - {String} Local name of the tag (without the prefix).
267 * {NodeList} A node list or array of elements.
269 getElementsByTagNameNS: function(node, uri, name) {
271 if(node.getElementsByTagNameNS) {
272 elements = node.getElementsByTagNameNS(uri, name);
274 // brute force method
275 var allNodes = node.getElementsByTagName("*");
276 var potentialNode, fullName;
277 for(var i=0, len=allNodes.length; i<len; ++i) {
278 potentialNode = allNodes[i];
279 fullName = (potentialNode.prefix) ?
280 (potentialNode.prefix + ":" + name) : name;
281 if((name == "*") || (fullName == potentialNode.nodeName)) {
282 if((uri == "*") || (uri == potentialNode.namespaceURI)) {
283 elements.push(potentialNode);
292 * APIMethod: getAttributeNodeNS
293 * Get an attribute node given the namespace URI and local name.
296 * node - {Element} Node on which to search for attribute nodes.
297 * uri - {String} Namespace URI.
298 * name - {String} Local name of the attribute (without the prefix).
301 * {DOMElement} An attribute node or null if none found.
303 getAttributeNodeNS: function(node, uri, name) {
304 var attributeNode = null;
305 if(node.getAttributeNodeNS) {
306 attributeNode = node.getAttributeNodeNS(uri, name);
308 var attributes = node.attributes;
309 var potentialNode, fullName;
310 for(var i=0, len=attributes.length; i<len; ++i) {
311 potentialNode = attributes[i];
312 if(potentialNode.namespaceURI == uri) {
313 fullName = (potentialNode.prefix) ?
314 (potentialNode.prefix + ":" + name) : name;
315 if(fullName == potentialNode.nodeName) {
316 attributeNode = potentialNode;
322 return attributeNode;
326 * APIMethod: getAttributeNS
327 * Get an attribute value given the namespace URI and local name.
330 * node - {Element} Node on which to search for an attribute.
331 * uri - {String} Namespace URI.
332 * name - {String} Local name of the attribute (without the prefix).
335 * {String} An attribute value or and empty string if none found.
337 getAttributeNS: function(node, uri, name) {
338 var attributeValue = "";
339 if(node.getAttributeNS) {
340 attributeValue = node.getAttributeNS(uri, name) || "";
342 var attributeNode = this.getAttributeNodeNS(node, uri, name);
344 attributeValue = attributeNode.nodeValue;
347 return attributeValue;
351 * APIMethod: getChildValue
352 * Get the textual value of the node if it exists, or return an
353 * optional default string. Returns an empty string if no first child
354 * exists and no default value is supplied.
357 * node - {DOMElement} The element used to look for a first child value.
358 * def - {String} Optional string to return in the event that no
359 * first child value exists.
362 * {String} The value of the first child of the given node.
364 getChildValue: function(node, def) {
365 var value = def || "";
367 for(var child=node.firstChild; child; child=child.nextSibling) {
368 switch(child.nodeType) {
370 case 4: // cdata section
371 value += child.nodeValue;
379 * APIMethod: concatChildValues
380 * *Deprecated*. Use <getChildValue> instead.
382 * Concatenate the value of all child nodes if any exist, or return an
383 * optional default string. Returns an empty string if no children
384 * exist and no default value is supplied. Not optimized for large
385 * numbers of child nodes.
388 * node - {DOMElement} The element used to look for child values.
389 * def - {String} Optional string to return in the event that no
393 * {String} The concatenated value of all child nodes of the given node.
395 concatChildValues: function(node, def) {
397 var child = node.firstChild;
400 childValue = child.nodeValue;
404 child = child.nextSibling;
406 if(value == "" && def != undefined) {
413 * APIMethod: isSimpleContent
414 * Test if the given node has only simple content (i.e. no child element
418 * node - {DOMElement} An element node.
421 * {Boolean} The node has no child element nodes (nodes of type 1).
423 isSimpleContent: function(node) {
425 for(var child=node.firstChild; child; child=child.nextSibling) {
426 if(child.nodeType === 1) {
435 * APIMethod: contentType
436 * Determine the content type for a given node.
439 * node - {DOMElement}
442 * {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
443 * if the node has no, simple, complex, or mixed content.
445 contentType: function(node) {
449 var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;
451 for(var child=node.firstChild; child; child=child.nextSibling) {
452 switch(child.nodeType) {
461 if(complex && simple) {
466 if(complex && simple) {
467 type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
469 return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
471 return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
477 * APIMethod: hasAttributeNS
478 * Determine whether a node has a particular attribute matching the given
479 * name and namespace.
482 * node - {Element} Node on which to search for an attribute.
483 * uri - {String} Namespace URI.
484 * name - {String} Local name of the attribute (without the prefix).
487 * {Boolean} The node has an attribute matching the name and namespace.
489 hasAttributeNS: function(node, uri, name) {
491 if(node.hasAttributeNS) {
492 found = node.hasAttributeNS(uri, name);
494 found = !!this.getAttributeNodeNS(node, uri, name);
500 * APIMethod: setAttributeNS
501 * Adds a new attribute or changes the value of an attribute with the given
502 * namespace and name.
505 * node - {Element} Element node on which to set the attribute.
506 * uri - {String} Namespace URI for the attribute.
507 * name - {String} Qualified name (prefix:localname) for the attribute.
508 * value - {String} Attribute value.
510 setAttributeNS: function(node, uri, name, value) {
511 if(node.setAttributeNS) {
512 node.setAttributeNS(uri, name, value);
516 var attribute = node.ownerDocument.createNode(
519 attribute.nodeValue = value;
520 node.setAttributeNode(attribute);
522 node.setAttribute(name, value);
525 throw "setAttributeNS not implemented";
531 * Method: createElementNSPlus
532 * Shorthand for creating namespaced elements with optional attributes and
536 * name - {String} The qualified node name.
537 * options - {Object} Optional object for node configuration.
540 * uri - {String} Optional namespace uri for the element - supply a prefix
541 * instead if the namespace uri is a property of the format's namespace
543 * attributes - {Object} Optional attributes to be set using the
544 * <setAttributes> method.
545 * value - {String} Optional text to be appended as a text node.
548 * {Element} An element node.
550 createElementNSPlus: function(name, options) {
551 options = options || {};
552 // order of prefix preference
553 // 1. in the uri option
554 // 2. in the prefix option
555 // 3. in the qualified name
556 // 4. from the defaultPrefix
557 var uri = options.uri || this.namespaces[options.prefix];
559 var loc = name.indexOf(":");
560 uri = this.namespaces[name.substring(0, loc)];
563 uri = this.namespaces[this.defaultPrefix];
565 var node = this.createElementNS(uri, name);
566 if(options.attributes) {
567 this.setAttributes(node, options.attributes);
569 var value = options.value;
571 if(typeof value == "boolean") {
572 value = String(value);
574 node.appendChild(this.createTextNode(value));
580 * Method: setAttributes
581 * Set multiple attributes given key value pairs from an object.
584 * node - {Element} An element node.
585 * obj - {Object || Array} An object whose properties represent attribute
586 * names and values represent attribute values. If an attribute name
587 * is a qualified name ("prefix:local"), the prefix will be looked up
588 * in the parsers {namespaces} object. If the prefix is found,
589 * setAttributeNS will be used instead of setAttribute.
591 setAttributes: function(node, obj) {
593 for(var name in obj) {
594 if(obj[name] != null && obj[name].toString) {
595 value = obj[name].toString();
596 // check for qualified attribute name ("prefix:local")
597 uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
598 this.setAttributeNS(node, uri, name, value);
605 * Shorthand for applying one of the named readers given the node
606 * namespace and local name. Readers take two args (node, obj) and
607 * generally extend or modify the second.
610 * node - {DOMElement} The node to be read (required).
611 * obj - {Object} The object to be modified (optional).
614 * {Object} The input object, modified (or a new one if none was provided).
616 readNode: function(node, obj) {
620 var group = this.readers[this.namespaceAlias[node.namespaceURI]];
622 var local = node.localName || node.nodeName.split(":").pop();
623 var reader = group[local] || group["*"];
625 reader.apply(this, [node, obj]);
632 * Method: readChildNodes
633 * Shorthand for applying the named readers to all children of a node.
634 * For each child of type 1 (element), <readSelf> is called.
637 * node - {DOMElement} The node to be read (required).
638 * obj - {Object} The object to be modified (optional).
641 * {Object} The input object, modified.
643 readChildNodes: function(node, obj) {
647 var children = node.childNodes;
649 for(var i=0, len=children.length; i<len; ++i) {
651 if(child.nodeType == 1) {
652 this.readNode(child, obj);
660 * Shorthand for applying one of the named writers and appending the
661 * results to a node. If a qualified name is not provided for the
662 * second argument (and a local name is used instead), the namespace
663 * of the parent node will be assumed.
666 * name - {String} The name of a node to generate. If a qualified name
667 * (e.g. "pre:Name") is used, the namespace prefix is assumed to be
668 * in the <writers> group. If a local name is used (e.g. "Name") then
669 * the namespace of the parent is assumed. If a local name is used
670 * and no parent is supplied, then the default namespace is assumed.
671 * obj - {Object} Structure containing data for the writer.
672 * parent - {DOMElement} Result will be appended to this node. If no parent
673 * is supplied, the node will not be appended to anything.
676 * {DOMElement} The child node.
678 writeNode: function(name, obj, parent) {
680 var split = name.indexOf(":");
682 prefix = name.substring(0, split);
683 local = name.substring(split + 1);
686 prefix = this.namespaceAlias[parent.namespaceURI];
688 prefix = this.defaultPrefix;
692 var child = this.writers[prefix][local].apply(this, [obj]);
694 parent.appendChild(child);
700 * APIMethod: getChildEl
701 * Get the first child element. Optionally only return the first child
702 * if it matches the given name and namespace URI.
705 * node - {DOMElement} The parent node.
706 * name - {String} Optional node name (local) to search for.
707 * uri - {String} Optional namespace URI to search for.
710 * {DOMElement} The first child. Returns null if no element is found, if
711 * something significant besides an element is found, or if the element
712 * found does not match the optional name and uri.
714 getChildEl: function(node, name, uri) {
715 return node && this.getThisOrNextEl(node.firstChild, name, uri);
719 * APIMethod: getNextEl
720 * Get the next sibling element. Optionally get the first sibling only
721 * if it matches the given local name and namespace URI.
724 * node - {DOMElement} The node.
725 * name - {String} Optional local name of the sibling to search for.
726 * uri - {String} Optional namespace URI of the sibling to search for.
729 * {DOMElement} The next sibling element. Returns null if no element is
730 * found, something significant besides an element is found, or the
731 * found element does not match the optional name and uri.
733 getNextEl: function(node, name, uri) {
734 return node && this.getThisOrNextEl(node.nextSibling, name, uri);
738 * Method: getThisOrNextEl
739 * Return this node or the next element node. Optionally get the first
740 * sibling with the given local name or namespace URI.
743 * node - {DOMElement} The node.
744 * name - {String} Optional local name of the sibling to search for.
745 * uri - {String} Optional namespace URI of the sibling to search for.
748 * {DOMElement} The next sibling element. Returns null if no element is
749 * found, something significant besides an element is found, or the
750 * found element does not match the query.
752 getThisOrNextEl: function(node, name, uri) {
753 outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) {
754 switch(sibling.nodeType) {
756 if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) &&
757 (!uri || uri === sibling.namespaceURI)) {
764 if(/^\s*$/.test(sibling.nodeValue)) {
768 case 6: // ENTITY_NODE
769 case 12: // NOTATION_NODE
770 case 10: // DOCUMENT_TYPE_NODE
771 case 11: // DOCUMENT_FRAGMENT_NODE
774 } // ignore comments and processing instructions
776 return sibling || null;
780 * APIMethod: lookupNamespaceURI
781 * Takes a prefix and returns the namespace URI associated with it on the given
782 * node if found (and null if not). Supplying null for the prefix will
783 * return the default namespace.
785 * For browsers that support it, this calls the native lookupNamesapceURI
786 * function. In other browsers, this is an implementation of
787 * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
789 * For browsers that don't support the attribute.ownerElement property, this
790 * method cannot be called on attribute nodes.
793 * node - {DOMElement} The node from which to start looking.
794 * prefix - {String} The prefix to lookup or null to lookup the default namespace.
797 * {String} The namespace URI for the given prefix. Returns null if the prefix
798 * cannot be found or the node is the wrong type.
800 lookupNamespaceURI: function(node, prefix) {
803 if(node.lookupNamespaceURI) {
804 uri = node.lookupNamespaceURI(prefix);
806 outer: switch(node.nodeType) {
807 case 1: // ELEMENT_NODE
808 if(node.namespaceURI !== null && node.prefix === prefix) {
809 uri = node.namespaceURI;
812 var len = node.attributes.length;
815 for(var i=0; i<len; ++i) {
816 attr = node.attributes[i];
817 if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
818 uri = attr.value || null;
820 } else if(attr.name === "xmlns" && prefix === null) {
821 uri = attr.value || null;
826 uri = this.lookupNamespaceURI(node.parentNode, prefix);
828 case 2: // ATTRIBUTE_NODE
829 uri = this.lookupNamespaceURI(node.ownerElement, prefix);
831 case 9: // DOCUMENT_NODE
832 uri = this.lookupNamespaceURI(node.documentElement, prefix);
834 case 6: // ENTITY_NODE
835 case 12: // NOTATION_NODE
836 case 10: // DOCUMENT_TYPE_NODE
837 case 11: // DOCUMENT_FRAGMENT_NODE
840 // TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
841 // PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
842 uri = this.lookupNamespaceURI(node.parentNode, prefix);
850 CLASS_NAME: "OpenLayers.Format.XML"
854 OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};
857 * APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
858 * Takes a prefix and returns the namespace URI associated with it on the given
859 * node if found (and null if not). Supplying null for the prefix will
860 * return the default namespace.
862 * For browsers that support it, this calls the native lookupNamesapceURI
863 * function. In other browsers, this is an implementation of
864 * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
866 * For browsers that don't support the attribute.ownerElement property, this
867 * method cannot be called on attribute nodes.
870 * node - {DOMElement} The node from which to start looking.
871 * prefix - {String} The prefix to lookup or null to lookup the default namespace.
874 * {String} The namespace URI for the given prefix. Returns null if the prefix
875 * cannot be found or the node is the wrong type.
877 OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
878 OpenLayers.Format.XML.prototype.lookupNamespaceURI,
879 OpenLayers.Format.XML.prototype