87

I'm new to jQuery and would like to parse an XML document.

I'm able to parse regular XML with the default namespaces but with XML such as:

<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
   <s:Schema id="RowsetSchema">
     <s:ElementType name="row" content="eltOnly" rs:CommandTimeout="30">
       <s:AttributeType name="ows_ID" rs:name="ID" rs:number="1">
        <s:datatype dt:type="i4" dt:maxLength="4" />
      </s:AttributeType>
       <s:AttributeType name="ows_DocIcon" rs:name="Type" rs:number="2">
        <s:datatype dt:type="string" dt:maxLength="512" />
      </s:AttributeType>
       <s:AttributeType name="ows_LinkTitle" rs:name="Title" rs:number="3">
        <s:datatype dt:type="string" dt:maxLength="512" />
      </s:AttributeType>
       <s:AttributeType name="ows_ServiceCategory" rs:name="Service Category" rs:number="4">
        <s:datatype dt:type="string" dt:maxLength="512" />
      </s:AttributeType>
    </s:ElementType>
  </s:Schema>
   <rs:data>
    <z:row ows_ID="2" ows_LinkTitle="Sample Data 1" />
    <z:row ows_ID="3" ows_LinkTitle="Sample Data 2" />
    <z:row ows_ID="4" ows_LinkTitle="Sample Data 3" />
  </rs:data>
</xml>

All I really want are the <z:row>.

So far, I've been using:

$.get(xmlPath, {}, function(xml) {
    $("rs:data", xml).find("z:row").each(function(i) {
        alert("found zrow");
    });
}, "xml");

with really no luck. Any ideas?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Brian Liang
  • 7,734
  • 10
  • 57
  • 72

20 Answers20

139

I got it.

Turns out that it requires \\ to escape the colon.

$.get(xmlPath, {}, function(xml) {
    $("rs\\:data", xml).find("z\\:row").each(function(i) {
        alert("found zrow");
    });
}, "xml");

As Rich pointed out:

The better solution does not require escaping and works on all "modern" browsers:

.find("[nodeName=z:row]")
Brian Liang
  • 7,734
  • 10
  • 57
  • 72
  • Yup, it works! Thank you for uncovering this hidden gem. Very useful for parsing Google Product RSS feeds. – fmalina Jun 30 '09 at 14:53
  • Appreciate this. I wanted to use jQuery with SOAP but was afraid of the namespace issue. By the way, this also works for element names with periods: = $("element\\.name") – Richard Clayton Sep 22 '09 at 01:21
  • 2
    `$('[nodeName=rs:data]', xml).find('[nodeName=z:row]')` - works with 1.3.2 under WebKit (where the escaped colon method apparently does not) – gnarf Jan 06 '10 at 07:07
  • 2
    this seems to have stopped working in jQuery version 1.4.4, which I think means jQuery has better XML namespace support. So to be safe, this works `$('[nodeName=rs:data],data')` – Josh Pearce Jan 11 '11 at 15:29
  • Hi Josh I am using your method as above, many thanks. the nodename is media:thumbnail and am trying to retrieve the url for the image. Would you know how to reach it as $('[nodeName=media:thumbnail],url') is returning a javascript object. Thanks Again – Gary Apr 23 '11 at 17:39
  • Lots of syntax problems in this answer: The one that works in Chrome 13 and FF7 `.find("[nodeName='namespace:name']")` The single quotes are important or it will throw errors. You can use `,name` as above, not sure that it offers anything. – Drew Aug 30 '11 at 18:45
  • 15
    Now jQuery 1.7 is out and this last solution doesn't work anymore. What is the new way? – Gapipro Nov 22 '11 at 09:59
  • using nodeName now needs the colon escaped aswell – Matt Ryan Feb 15 '12 at 18:14
  • 3
    In jQuery 1.8.x it doesn't works anymore. It should accomplished with a custom pseudo class compatibility workaround, as explained [here](http://www.h-online.com/open/news/item/jQuery-1-8-1-has-compatibility-workaround-for-custom-pseudo-selectors-1696774.html). – Miere Sep 04 '12 at 23:23
  • 5
    Even though this answers the question for the given XML doc, I'd like to remind people that the _prefixes_ like `rs`, `dt` or `s` are really not the namespaces. The namespaces are the URNs at the top of the file. The prefixes are just aliases chosen by the document author to keep things short. The same document, matching the same namespaces could be created with totally different prefixes. I'd encourage everyone to look for APIs that understand namespaces instead of assuming prefixes in your queries. E.g., in the browser DOM API you can use `getElementByTagNameNS()` and `getAttributeNS()`. – sergiopereira Sep 17 '14 at 18:05
35

I have spent several hours on this reading about plugins and all sorts of solutions with no luck.

ArnisAndy posted a link to a jQuery discussion, where this answer is offered and I can confirm that this works for me in Chrome(v18.0), FireFox(v11.0), IE(v9.08) and Safari (v5.1.5) using jQuery (v1.7.2).

I am trying to scrape a WordPress feed where content is named <content:encoded> and this is what worked for me:

content: $this.find("content\\:encoded, encoded").text()
Fasani
  • 5,141
  • 1
  • 23
  • 24
  • 3
    This was the only one that reliably worked for me using the latest jQuery (same version) so thank you! – Dominic K Jul 02 '12 at 20:07
  • 2
    This worked for me while I used an `.each()` loop to iterate through `item` elements: `$('dc\\:creator, creator', this).text()`. Though, I'm not sure why the extra `, creator` was needed, and `dc\\:creator` didn't just work. – Fillip Peyton Nov 24 '14 at 23:24
19

Although the above answer seems to be correct, it does not work in webkit browsers (Safari, Chrome). A better solution I believe would be:

.find("[nodeName=z:myRow, myRow]")    
Caranicas
  • 149
  • 1
  • 11
Rich
  • 553
  • 1
  • 10
  • 20
  • 5
    this seems to have stopped working in jQuery version 1.4.4, which I think means jQuery has better XML namespace support. So to be safe, this works `$('[nodeName=rs:data],data')` – Josh Pearce Jan 11 '11 at 15:29
19

If you are using jquery 1.5 you will have to add quotes around the node selector attribute value to make it work:

.find('[nodeName="z:row"]')
s0laris
  • 421
  • 3
  • 5
17

In case someone needs to do this without jQuery, just with normal Javascript, and for Google Chrome (webkit), this is the only way I found to get it to work after a lot of research and testing.

parentNode.getElementsByTagNameNS("*", "name");

That will work for retrieving the following node: <prefix:name>. As you can see the prefix or namespace is omitted, and it will match elements with different namespaces provided the tag name is name. But hopefully this won't be a problem for you.

None of this worked for me (I am developping a Google Chrome extension):

getElementsByTagNameNS("prefix", "name")

getElementsByTagName("prefix:name")

getElementsByTagName("prefix\\:name")

getElementsByTagName("name")

Edit: after some sleep, I found a working workaround :) This function returns the first node matching a full nodeName such as <prefix:name>:

// Helper function for nodes names that include a prefix and a colon, such as "<yt:rating>"
function getElementByNodeName(parentNode, nodeName)
{   
    var colonIndex = nodeName.indexOf(":");
    var tag = nodeName.substr(colonIndex + 1);
    var nodes = parentNode.getElementsByTagNameNS("*", tag);
    for (var i = 0; i < nodes.length; i++)
    {
        if (nodes[i].nodeName == nodeName) return nodes[i]
    }
    return undefined;
}

It can easily be modified in case you need to return all the matching elements. Hope it helps!

cprcrack
  • 17,118
  • 7
  • 88
  • 91
  • My understanding is that the first argument of `getElementsByTagNameNS()` should be the namespace, not the prefix. So the value of the `xmlns:prefix` attribute on the root element. – cdauth Oct 28 '21 at 10:12
14

None of the solutions above work that well. I found this and has been improved for speed. just add this, worked like a charm:

$.fn.filterNode = function(name) {
    return this.find('*').filter(function() {
       return this.nodeName === name;
    });
};

usage:

var ineedthatelementwiththepsuedo = $('someparentelement').filterNode('dc:creator');

source: http://www.steveworkman.com/html5-2/javascript/2011/improving-javascript-xml-node-finding-performance-by-2000/

Tj Tate
  • 169
  • 1
  • 8
9

The "\\" escaping isn't foolproof and the simple

.find('[nodeName="z:row"]')

Method seems to have been broken as of Jquery 1.7. I was able to find a solution for 1.7 , using a filter function, here: Improving Javascript XML Node Finding Performance

jmazella
  • 146
  • 1
  • 2
3

Found solution in the comment: Parsing XML with namespaces using jQuery $().find

Using the second half of node name after the colon worked for me. Used .find("lat") instead of .find("geo\:lat") and it worked for me.


My setup:

  • Chrome 42
  • jQuery 2.1.3

Sample XML (snippet from Google Contacts API):

<entry>
  <id>http://www.google.com/m8/feeds/contacts/mstefanow%40gmail.com/base/0</id>
  <gd:email rel="http://schemas.google.com/g/2005#other" address="email@example.com" primary="true"/>
</entry>

Parsing code:

var xmlDoc = $.parseXML( xml );
var $xml = $( xmlDoc );
var $emailNode = $xml.find( "email" );
$("#email").html($emailNode.attr("address"));

Plnkr: http://plnkr.co/edit/l8VzyDq1NHtn5qC9zTjf?p=preview

Community
  • 1
  • 1
Mars Robertson
  • 12,673
  • 11
  • 68
  • 89
3

It's worth noting that as of jQuery 1.7 there were issues with some of the work-arounds for finding namespaced elements. See these links for more information:

ArnisAndy
  • 336
  • 3
  • 7
  • If the performance is important, then the best solution is to select the tags without jQuery. For a comparison, see: http://jsperf.com/node-vs-double-select/13 –  Sep 24 '12 at 17:33
2

jQuery 1.7 doesn't work with the following:

$(xml).find("[nodeName=a:IndexField2]")

One solution which I did get to work in Chrome, Firefox, and IE is to use selectors which work in IE AND selectors which work in Chrome, based on the fact that one way works in IE and the other in Chrome:

$(xml).find('a\\\\:IndexField2, IndexField2')

In IE, this returns nodes using the namespace (Firefox and IE require the namespace), and in Chrome, the selector returns nodes based on the non-namespace selector. I have not tested this in Safari, but it should work because it's working in Chrome.

Tisho
  • 8,320
  • 6
  • 44
  • 52
2

My solution (because I use a Php proxy) is to replace : namespace by _ ... so no more namespace issues ;-)

Keep it simple !

Thomas Decaux
  • 21,738
  • 2
  • 113
  • 124
2

Original Answer : jQuery XML parsing how to get element attribute

Here is an example for how to successfully get the value in Chrome..

 item.description = jQuery(this).find("[nodeName=itunes\\:summary]").eq(0).text();
Community
  • 1
  • 1
John Drefahl
  • 556
  • 3
  • 17
2

As of beginning of 2016, for me the following syntax works with jQuery 1.12.0:

  • IE 11 (11.0.9600.18204, Update 11.0.28, KB3134815): .find("z\\:row")
  • Firefox 44.0.2: .find("z\\:row")
  • Chrome 44.0.2403.89m: .find("row")

The syntax .find("[nodeName=z:row]") doesn't work in any of the browsers mentioned above. I found no way to apply a namespace in Chrome.

Putting it all together, the following syntax works in all of the browsers mentioned above: .find("row,z\\:row")

stefan.schwetschke
  • 8,862
  • 1
  • 26
  • 30
1

As mentioned above, there are problems with the above solution with current browsers/versions of jQuery - the suggested plug-in doesn't completely work either because of case issues (nodeName, as a property, is sometimes in all upper case). So, I wrote the following quick function:

$.findNS = function (o, nodeName)
{
    return o.children().filter(function ()
    {
        if (this.nodeName)
            return this.nodeName.toUpperCase() == nodeName.toUpperCase();
        else
            return false;
    });
};

Example usage:

$.findNS($(xml), 'x:row');
Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66
Mike Oliver
  • 395
  • 1
  • 3
  • 12
1

content: $this.find("content\\:encoded, encoded").text()

is the perfect solution...

Bud Damyanov
  • 30,171
  • 6
  • 44
  • 52
sachinkondana
  • 564
  • 5
  • 15
1

There is a plugin jquery-xmlns for jQuery to work with namespaces in selectors.

Dima Fomin
  • 1,228
  • 1
  • 16
  • 27
0

just replaced the namespace by empty string. Works fine for me. Tested solution across browsers: Firefox, IE, Chrome

My task was to read and parse an EXCEL-file via Sharepoint EXCEL REST API. The XML-response contains tags with "x:" namespace.

I decided to replace the namespace in the XML by an empty string. Works this way: 1. Get the node of interest out of the XML-response 2. Convert the selected node XML-Response (Document) to String 2. Replace namespace by empty string 3. Convert string back to XML-Document

See code outline here -->

function processXMLResponse)(xData)
{
  var xml = TOOLS.convertXMLToString("", "",$(xData).find("entry content")[0]);
  xml = xml.replace(/x:/g, "");            // replace all occurences of namespace
  xData =  TOOLS.createXMLDocument(xml);   // convert string back to XML
}

For XML-to-String conversion find a solution here: http://www.sencha.com/forum/showthread.php?34553-Convert-DOM-XML-Document-to-string

Karl
  • 3,099
  • 3
  • 22
  • 24
0

Alternatively, you can use fast-xml-parser in your project, and convert the XML data into JS/JSON object. Then you can use it as object property. It doesn't use JQuery or other libraries but it'll solve your purpose.

var xmlData = '<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">'
+'   <s:Schema id="RowsetSchema">'
+'     <s:ElementType name="row" content="eltOnly" rs:CommandTimeout="30">'
+'       <s:AttributeType name="ows_ID" rs:name="ID" rs:number="1">'
+'        <s:datatype dt:type="i4" dt:maxLength="4" />'
+'      </s:AttributeType>'
+'       <s:AttributeType name="ows_DocIcon" rs:name="Type" rs:number="2">'
+'        <s:datatype dt:type="string" dt:maxLength="512" />'
+'      </s:AttributeType>'
+'       <s:AttributeType name="ows_LinkTitle" rs:name="Title" rs:number="3">'
+'        <s:datatype dt:type="string" dt:maxLength="512" />'
+'      </s:AttributeType>'
+'       <s:AttributeType name="ows_ServiceCategory" rs:name="Service Category" rs:number="4">'
+'        <s:datatype dt:type="string" dt:maxLength="512" />'
+'      </s:AttributeType>'
+'    </s:ElementType>'
+'  </s:Schema>'
+'   <rs:data>'
+'    <z:row ows_ID="2" ows_LinkTitle="Sample Data 1" />'
+'    <z:row ows_ID="3" ows_LinkTitle="Sample Data 2" />'
+'    <z:row ows_ID="4" ows_LinkTitle="Sample Data 3" />'
+'  </rs:data>'
+'</xml>'

var jsObj = parser.parse(xmlData,{attrPrefix:"",ignoreTextNodeAttr: false});
document.write(JSON.stringify(jsObj.xml["rs:data"]["z:row"][0],null,4) + "<br>");
document.write(JSON.stringify(jsObj.xml["rs:data"]["z:row"][1],null,4) + "<br>");
document.write(JSON.stringify(jsObj.xml["rs:data"]["z:row"][2],null,4) + "<br>");
<script src="https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/2.9.2/parser.min.js"></script>

You can ignore namespaces while parsing to js/json object. In this case you can directly access as jsObj.xml.data.row.

for(var i=0; i< jsObj.xml.data.row.length; i++){
  console.log(jsObj.xml.data.row[i]);
}

Disclaimer: I've created fast-xml-parser.

Amit Kumar Gupta
  • 7,193
  • 12
  • 64
  • 90
0

I have not seen any documentation on using JQuery to parse XML. JQuery typically uses the Browser dom to browse an HTML document, I don't believe it reads the html itself.

You should probably look at the built in XML handling in JavaScript itself.

http://www.webreference.com/programming/javascript/definitive2/

Chris Brandsma
  • 11,666
  • 5
  • 47
  • 58
  • 3
    Completely disagree. jQuery makes handling response XML easy, the only complication you will encounter is using xml namespaces. – Richard Clayton Sep 22 '09 at 00:53
  • 1
    @Richard: When using Ajax, jQuery does use the `responseXML` property of the built-in `XMLHttpRequest` object, which is indeed an XML document. However, jQuery (until 1.5, when `parseXML` was introduced) had no way of parsing XML, so Chris was right. – Tim Down Jun 21 '11 at 09:49
-1

For Webkit browsers, you can just leave off the colon. So to find <media:content> in an RSS feed for example, you can do this:

$(this).find("content");
donnapep
  • 500
  • 6
  • 19