From d1898d1a7da4c952e463152e79bf1ff3ee2fdf82 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 3 Apr 2009 19:17:49 +0000 Subject: [PATCH] Making it so format.getChildValue concatenates all simple content (except for GeoRSS format). Use getChildValue instead of concatChildValues. Adding a number of other format methods for those who do manual dom traversal. Original patch from dparker. r=me (see #1846) git-svn-id: http://svn.openlayers.org/trunk/openlayers@9180 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- lib/OpenLayers/Format/GML.js | 4 +- lib/OpenLayers/Format/GML/Base.js | 2 +- lib/OpenLayers/Format/GML/v3.js | 2 +- lib/OpenLayers/Format/KML.js | 6 +- lib/OpenLayers/Format/XML.js | 256 +++++++++++++++++++++++++++++- tests/Format/XML.html | 148 +++++++++++++++++ 6 files changed, 407 insertions(+), 11 deletions(-) diff --git a/lib/OpenLayers/Format/GML.js b/lib/OpenLayers/Format/GML.js index d6ba5d409a..e6913767f9 100644 --- a/lib/OpenLayers/Format/GML.js +++ b/lib/OpenLayers/Format/GML.js @@ -323,7 +323,7 @@ OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, { // look for nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList"); if(nodeList.length > 0) { - coordString = this.concatChildValues(nodeList[0]); + coordString = this.getChildValue(nodeList[0]); coordString = coordString.replace(this.regExes.trimSpace, ""); coords = coordString.split(this.regExes.splitSpace); var dim = parseInt(nodeList[0].getAttribute("dimension")); @@ -346,7 +346,7 @@ OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, { nodeList = this.getElementsByTagNameNS(node, this.gmlns, "coordinates"); if(nodeList.length > 0) { - coordString = this.concatChildValues(nodeList[0]); + coordString = this.getChildValue(nodeList[0]); coordString = coordString.replace(this.regExes.trimSpace, ""); coordString = coordString.replace(this.regExes.trimComma, diff --git a/lib/OpenLayers/Format/GML/Base.js b/lib/OpenLayers/Format/GML/Base.js index a5469f0bb5..6673a7b5e3 100644 --- a/lib/OpenLayers/Format/GML/Base.js +++ b/lib/OpenLayers/Format/GML/Base.js @@ -214,7 +214,7 @@ OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, { container.components.push(obj.points[0]); }, "coordinates": function(node, obj) { - var str = this.concatChildValues(node).replace( + var str = this.getChildValue(node).replace( this.regExes.trimSpace, "" ); str = str.replace(this.regExes.trimComma, ","); diff --git a/lib/OpenLayers/Format/GML/v3.js b/lib/OpenLayers/Format/GML/v3.js index 1c158b7aa7..416ec42042 100644 --- a/lib/OpenLayers/Format/GML/v3.js +++ b/lib/OpenLayers/Format/GML/v3.js @@ -130,7 +130,7 @@ OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, { obj.points = [point]; }, "posList": function(node, obj) { - var str = this.concatChildValues(node).replace( + var str = this.getChildValue(node).replace( this.regExes.trimSpace, "" ); var coords = str.split(this.regExes.splitSpace); diff --git a/lib/OpenLayers/Format/KML.js b/lib/OpenLayers/Format/KML.js index 3985162c26..b42644e2c3 100644 --- a/lib/OpenLayers/Format/KML.js +++ b/lib/OpenLayers/Format/KML.js @@ -726,7 +726,7 @@ OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { "coordinates"); var line = null; if(nodeList.length > 0) { - var coordString = this.concatChildValues(nodeList[0]); + var coordString = this.getChildValue(nodeList[0]); coordString = coordString.replace(this.regExes.trimSpace, ""); @@ -897,11 +897,11 @@ OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { var ed = {}; var valueNode = data.getElementsByTagName("value"); if (valueNode.length) { - ed['value'] = this.concatChildValues(valueNode[0]); + ed['value'] = this.getChildValue(valueNode[0]); } var nameNode = data.getElementsByTagName("displayName"); if (nameNode.length) { - ed['displayName'] = this.concatChildValues(nameNode[0]); + ed['displayName'] = this.getChildValue(nameNode[0]); } attributes[key] = ed; } diff --git a/lib/OpenLayers/Format/XML.js b/lib/OpenLayers/Format/XML.js index 4379a6e466..211e05ec9a 100644 --- a/lib/OpenLayers/Format/XML.js +++ b/lib/OpenLayers/Format/XML.js @@ -349,7 +349,7 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { /** * APIMethod: getChildValue - * Get the value of the first child node if it exists, or return an + * 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. * @@ -364,9 +364,12 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { getChildValue: function(node, def) { var value = def || ""; if(node) { - var child = node.firstChild; - if(child) { - value = child.nodeValue || value; + 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; @@ -374,6 +377,8 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { /** * APIMethod: concatChildValues + * *Deprecated*. Use 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 @@ -403,6 +408,70 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { } 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 @@ -627,6 +696,185 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { 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" + + "" + + "xyzy" + + ""; + + var format = new OpenLayers.Format.XML(); + var doc = format.read(text).documentElement; + + t.eq(format.getChildValue(doc), "xyzzy", "child value skips comments, concatenates multiple values, reads through entities"); + + } + + function test_getChildEl(t) { + + t.plan(3); + + var text = + "" + + "" + + "" + + "x" + + "x" + + ""; + + var format = new OpenLayers.Format.XML(); + var doc = format.read(text).documentElement; + + var a = format.getChildEl(doc); + t.eq(a.nodeName, "a", "first element found correctly"); + + a = format.getChildEl(doc, "a"); + t.eq(b, null, "first child element matches the given name"); + + var b = format.getChildEl(doc, "b"); + t.eq(b, null, "first child element does not match the given name"); + + } + + function test_getNextEl(t) { + t.plan(5); + + var text = + "" + + "" + + "" + + "x" + + "" + + "x" + + ""; + + var format = new OpenLayers.Format.XML(); + var doc = format.read(text).documentElement; + + var a = format.getChildEl(doc); + + var b = format.getNextEl(a); + t.eq(b && b.nodeName, "b", "next element correctly found"); + + b = format.getNextEl(a, "b"); + t.eq(b && b.nodeName, "b", "next element correctly found when name supplied"); + + b = format.getNextEl(a, "c"); + t.eq(b, null, "null returned when name does not match next element"); + + b = format.getNextEl(a, null, "urn:example"); + t.eq(b && b.nodeName, "b", "next element correctly found when namespace supplied"); + + b = format.getNextEl(a, null, "foo"); + t.eq(b, null, "null returned when namespace does not match next element"); + + } + + function test_isSimpleContent(t) { + t.plan(2); + + var text = + "" + + "" + + "" + + "xyzy" + + "" + + "xyzy" + + ""; + + var format = new OpenLayers.Format.XML(); + var doc = format.read(text).documentElement; + + var a = format.getChildEl(doc); + var b = format.getNextEl(a); + t.ok(!format.isSimpleContent(a), " content is not simple"); + t.ok(format.isSimpleContent(b), " content is simple"); + + } + + function test_lookupNamespaceURI(t) { + + t.plan(8); + + var text = + "" + + "" + + "" + + "" + + "" + + "" + + ""; + + var format = new OpenLayers.Format.XML(); + var doc = format.read(text).documentElement; + + var a = format.getChildEl(doc); + t.eq(format.lookupNamespaceURI(a, "baz"), "urn:baznamespace", "prefix lookup on first child"); + + var foo = format.getChildEl(a); + t.eq(format.lookupNamespaceURI(foo, "baz"), "urn:baznamespace", "prefix lookup on child of first child"); + + var b = format.getNextEl(a); + t.eq(format.lookupNamespaceURI(b, null), "urn:example", "default namespace lookup on element"); + + var bar = format.getChildEl(b); + t.eq(format.lookupNamespaceURI(bar, null), "urn:example", "default namespace lookup on child"); + t.eq(format.lookupNamespaceURI(bar, "baz"), "urn:baznamespace", "prefix lookup on child with different default"); + + // test that the alias behaves properly + var lookup = OpenLayers.Format.XML.lookupNamespaceURI; + t.eq(lookup(bar, "baz"), "urn:baznamespace", "(alias) prefix lookup on child with different default"); + + var attr = bar.attributes[0]; + // Internet Explorer didn't have the ownerElement property until 8. + var supportsOwnerElement = !!attr.ownerElement; + if(supportsOwnerElement) { + t.eq(format.lookupNamespaceURI(attr, null), "urn:example", "default namespace lookup on attribute"); + t.eq(format.lookupNamespaceURI(attr, "baz"), "urn:baznamespace", "prefix lookup on attribute with different default"); + } else { + t.debug_print("namespace lookup on attributes not supported in this browser"); + t.ok(true, "namespace lookup on attributes not supported in this browser"); + t.ok(true, "namespace lookup on attributes not supported in this browser"); + } + + } + + +