How to Parse XML Cross-domain in jQuery?

Solution 1:

To fully understand why pure cross-domain XML will not work, it helps to first look at how cross domain JSON is facilitated.

First, let's look at what happens when you make an AJAX request in jQuery:

$.ajax({
    url: '/user.php?userId=123',
    success: function(data) {
        alert(data); // alerts the response
    });

In the above example, the AJAX request is made relative to the domain. We know that if we attempt to add a different domain before the path, the request will fail with a security exception.

However, that's not to say that the browser cannot make requests to another domain. Here is an example that may be familiar to you:

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>

Based on our knowledge of how to import JavaScript on the page, we see that it is possible to load a resource that exists on another domain!

JSONP is a concept that exploits this knowledge. JSONP stands for "JSON with padding", and it's success hinges on the fact that JavaScript Objects can be expressed using a string notation, and the fact that JavaScript script tags can load and run content from external domains.

Under the hood, jQuery's JSONP looks something like this although it may not be exact:

// programmatically load a script tag on the page using the given url
function loadRemoteData(url) {
    var script = document.createElement("script");
    script.setAttribute("type","text/javascript");
    script.setAttribute("src", url);
    document.getElementsByTagName("head")[0].appendChild(script);
}

Also, on the page somewhere, we define a callback handler:

function processData(jsonResult) {
    alert(JSON.stringify(jsonResult)); //alert the JSON as a string
}

Here, we make the request:

// make a request for the data using the script tag remoting approach.
loadRemoteData("http://example.com/users.php?userId=123&callback=processData");

For this to work properly, our PHP script must both return the data in JSON format, and it must also add "padding" around the string in the form of a JavaScript function name that we may pass in as a parameter (i.e. "callback")

Thus, the response from the server may look something like this, if we were to look at it in the Firebug or Chrome NET tab:

processData( { "userId" : "123" , "name" : "James" , "email" : "[email protected]" } );

Because we know JavaScript content runs as soon as it's downloaded, our processData function we defined earlier is immediately called and is passed our JSON string as a parameter. It is then alerted, using JSON.stringify to convert the object back into a string.

Since it's an object, I could also access it's properties, like so:

function processData(jsonResult) {
    alert(JSON.stringify(jsonResult)); //alert the JSON as a string

    // alert the name and email
    alert("User name is " + jsonResult.name + " and email is " + jsonResult.email);
}

Finally, let's move onto the main question: Can JSONP be used to fetch XML, or can we parse XML cross-domain? The answer, as others have pointed out, is a resounding NO, but let's look at why by using an example:

processData(<?xml version="1.0"><user><userid>12345</userid><name>James</name><email>[email protected]</email></user>);

Now, what will happen if raw XML is passed into the function? It will break, as JavaScript has no way to convert XML into JSON.

However, suppose we put the XML in quotes:

processData("<?xml version="1.0"><user><userid>12345</userid><name>James</name><email>[email protected]</email></user>");

Now, in this example, the jsonResult variable actually takes a string, which we can work with. Using some JavaScript XML parsing utilities, we could load that string into the XML DOM Parser and do stuff with it!

However, it's not pure XML, it's still a JavaScript response under the hood. The response type from the PHP server is still text/javascript, and we're still using a script tag to load what is really just plain JavaScript.

In summary, you could work with "XMLP" or XML with padding (I just made that up, it's not real!), but if you're going to go through all of the trouble of actually modifying your response to return a function callback wrapper, you may as well just convert your output to JSON and let the browser handle conversions automatically and natively and save yourself the trouble of having to use an XML parser.

But if for some reason it's easier to keep your data in XML format, you could modify the response and give it a JavaScript wrapper.

Cases where I could see this being useful might be if you have XML data from a legacy application stored in a database, and you return it to the client-side using script-tag remoting or JSONP calls.

Solution 2:

I found a very good solution to retrieve xml from cross domain ajax request.

Since jQuery 1.5 you can use dataType "jsonp xml" (http://api.jquery.com/jQuery.ajax/) !

So i used this :

$.ajax({
            type: "GET",
            url: "http://yoururl",
            dataType: "jsonp xml",
            success: function(xmlResponse) { // process data }
        });

Server side for my Webservices i used to encapsulate the xml string result within the callback created by jQuery:

private static Stream GetXmlPStream(string result, string callback)
        {
            if (result == null)
                result = string.Empty;

            result = EncodeJsString(result);

            if (!String.IsNullOrEmpty(callback))
                result = callback + "(" + result + ");";

            byte[] resultBytes = Encoding.UTF8.GetBytes(result);

            if (WebOperationContext.Current != null)
                WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
            return new MemoryStream(resultBytes);
        }

and the magic method (i found it in another Stack thread) that you'll need to sanitize your xml string (so javascript can parse it) :

private static string EncodeJsString(string s)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("\"");
            foreach (char c in s)
            {
                switch (c)
                {
                    case '\"':
                        sb.Append("\\\"");
                        break;
                    case '\\':
                        sb.Append("\\\\");
                        break;
                    case '\b':
                        sb.Append("\\b");
                        break;
                    case '\f':
                        sb.Append("\\f");
                        break;
                    case '\n':
                        sb.Append("\\n");
                        break;
                    case '\r':
                        sb.Append("\\r");
                        break;
                    case '\t':
                        sb.Append("\\t");
                        break;
                    default:
                        int i = (int)c;
                        if (i < 32 || i > 127)
                        {
                            sb.AppendFormat("\\u{0:X04}", i);
                        }
                        else
                        {
                            sb.Append(c);
                        }
                        break;
                }
            }
            sb.Append("\"");

            return sb.ToString();
        }

Hope this will help !