XPath Using Javascript Policy

Not applicable

Hi - I am trying to extract some data from the yahooapis.com endpoint using Apigee Edge and a Javascript policy. I am using an XPath example similar to what has been described in the following post but nothing appears to be returned.

The path I am using :

https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20i...

The sample my code is based upon

https://github.com/apigee/api-platform-samples/tree/master/sample-proxies/async-callout

The following is my JS code - even a simple snippet from the Yahoo web site does not appear to work ?

function extractTempSunriseSunset(rss) {
  var yahoo = new Namespace('http://www.yahooapis.com/v1/base.rng');
  var yweather = new Namespace('http://xml.weather.yahoo.com/ns/rss/1.0');
  var wind = rss.query.results.channel.wind;
  var temp = rss.query.results.channel.toXMLString();
  var j = {
    "wind": wind
  };
  return j;
}
Solved Solved
1 2 1,273
1 ACCEPTED SOLUTION

Hi Ghulam,

You are using the e4X syntax for selecting nodes in an XML document. (not xpath) e4X is part of Rhino and is supported for the JS callout in Apigee Edge. But you should be aware that according to the documentation from Mozilla, e4X is "Obsolete", and as such will be removed from JavaScript. We will expect it to be removed from a future version of Rhino / Nashorn, which means at some point it will not be possible to use this syntax within an updated Apigee Edge. But for now it remains supported.

OK, now to answer your question. What you need to do is apply the namespace with the double-colon operator to traverse into namespace-qualified elements and attributes. Here's what I used:

function extractTempSunriseSunset(doc) {
  var yahoo = new Namespace('http://www.yahooapis.com/v1/base.rng');
  var yweather = new Namespace('http://xml.weather.yahoo.com/ns/rss/1.0');


  var j = {
        location : {
          city: doc.results.channel.yweather::location.@city.toString(),
          region: doc.results.channel.yweather::location.@region.toString(),
          country: doc.results.channel.yweather::location.@country.toString()
        },
        currentConditions : {
          wind: {
            speed: doc.results.channel.yweather::wind.@speed.toString(),
            direction: doc.results.channel.yweather::wind.@direction.toString()
          },
          date: doc.results.channel.item.yweather::condition.@date.toString(),
          temp: doc.results.channel.item.yweather::condition.@temp.toString(),
          desc: doc.results.channel.item.yweather::condition.@text.toString()
        }
      };
  return j;
}




function retrieveXml() {


  var query = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=xml&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys';


  var exchange = httpClient.get(query);
  exchange.waitForComplete();


  if (exchange.isError()) {
    throw { error: exchange.getError(), details: 'while retrieving'};
  }
  return exchange.getResponse();
}


var myresponse = retrieveXml();
// we expect content to be an XML string
var extractedData = extractTempSunriseSunset(myresponse.content.asXML);
response.content = JSON.stringify(extractedData, null, 2) + '\n';

The first function, extractTempSunriseSunset, shows how to use the e4X syntax. The second function, retrieveXml, just sends the request and gets the response. And here is the result I obtained.

{
  "location": {
    "city": "Nome",
    "region": " AK",
    "country": "United States"
  },
  "currentConditions": {
    "wind": {
      "speed": "18",
      "direction": "113"
    },
    "date": "Mon, 30 May 2016 02:00 PM AKDT",
    "temp": "57",
    "desc": "Clear"
  }
}


You will have to modify the default execution time limit on your JS if you use this code to retrieve a response from Yahoo. The default is 200ms runtime for a JS policy. This one could run over 1000 ms. Here's what I used:

<Javascript name="JS-ExtractValue" timeLimit="3200">
    <ResourceURL>jsc://ghulamExtractValue.js</ResourceURL>
</Javascript>

This ought to work for you. I think it may be a good idea to migrate to an alternative package that really does xpath and works in Rhino. I'll work on that.

EDIT - after further review I think maybe if you're going to do Xpath, I'd suggest that you use Java or nodejs. It's not so simple to get an xpath module that works in Rhino as it run within Apigee Edge.

View solution in original post

2 REPLIES 2

Hi Ghulam,

You are using the e4X syntax for selecting nodes in an XML document. (not xpath) e4X is part of Rhino and is supported for the JS callout in Apigee Edge. But you should be aware that according to the documentation from Mozilla, e4X is "Obsolete", and as such will be removed from JavaScript. We will expect it to be removed from a future version of Rhino / Nashorn, which means at some point it will not be possible to use this syntax within an updated Apigee Edge. But for now it remains supported.

OK, now to answer your question. What you need to do is apply the namespace with the double-colon operator to traverse into namespace-qualified elements and attributes. Here's what I used:

function extractTempSunriseSunset(doc) {
  var yahoo = new Namespace('http://www.yahooapis.com/v1/base.rng');
  var yweather = new Namespace('http://xml.weather.yahoo.com/ns/rss/1.0');


  var j = {
        location : {
          city: doc.results.channel.yweather::location.@city.toString(),
          region: doc.results.channel.yweather::location.@region.toString(),
          country: doc.results.channel.yweather::location.@country.toString()
        },
        currentConditions : {
          wind: {
            speed: doc.results.channel.yweather::wind.@speed.toString(),
            direction: doc.results.channel.yweather::wind.@direction.toString()
          },
          date: doc.results.channel.item.yweather::condition.@date.toString(),
          temp: doc.results.channel.item.yweather::condition.@temp.toString(),
          desc: doc.results.channel.item.yweather::condition.@text.toString()
        }
      };
  return j;
}




function retrieveXml() {


  var query = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=xml&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys';


  var exchange = httpClient.get(query);
  exchange.waitForComplete();


  if (exchange.isError()) {
    throw { error: exchange.getError(), details: 'while retrieving'};
  }
  return exchange.getResponse();
}


var myresponse = retrieveXml();
// we expect content to be an XML string
var extractedData = extractTempSunriseSunset(myresponse.content.asXML);
response.content = JSON.stringify(extractedData, null, 2) + '\n';

The first function, extractTempSunriseSunset, shows how to use the e4X syntax. The second function, retrieveXml, just sends the request and gets the response. And here is the result I obtained.

{
  "location": {
    "city": "Nome",
    "region": " AK",
    "country": "United States"
  },
  "currentConditions": {
    "wind": {
      "speed": "18",
      "direction": "113"
    },
    "date": "Mon, 30 May 2016 02:00 PM AKDT",
    "temp": "57",
    "desc": "Clear"
  }
}


You will have to modify the default execution time limit on your JS if you use this code to retrieve a response from Yahoo. The default is 200ms runtime for a JS policy. This one could run over 1000 ms. Here's what I used:

<Javascript name="JS-ExtractValue" timeLimit="3200">
    <ResourceURL>jsc://ghulamExtractValue.js</ResourceURL>
</Javascript>

This ought to work for you. I think it may be a good idea to migrate to an alternative package that really does xpath and works in Rhino. I'll work on that.

EDIT - after further review I think maybe if you're going to do Xpath, I'd suggest that you use Java or nodejs. It's not so simple to get an xpath module that works in Rhino as it run within Apigee Edge.

Thanks @dino - this really helps - appreciate you taking the time.