Package com.codename1.processing
XPath based expression language designed to assist in JSON/XML parsing/generating
The com.codename1.processing.Result class provides a subset of
XPath, but it is not limited to just XML
documents, it can also work with JSON documents, and even with raw java.util.Map objects.
As an example, we'll demonstrate how to process a response from the Google Reverse Geocoder API. Lets start with this XML snippet:
OK
London
London
locality
political
Ontario
ON
administrative_area_level_1
political
Canada
CA
country
political
We want to extract some of the data above into simpler string results. We can do this using:
Result result = Result.fromContent(input, Result.XML);
String country = result.getAsString("/result/address_component[type='country']/long_name");
String region = result.getAsString("/result/address_component[type='administrative_area_level_1']/long_name");
String city = result.getAsString("/result/address_component[type='locality']/long_name");
If you are at all familiar with processing responses from webservices, you will notice that what would normally require several lines of code of selecting and testing nodes in regular java can now be done in a single line using the new path expressions.
In the code above, input can be any of:
-
java.lang.InputStreamdirectly fromcom.codename1.io.ConnectionRequest#readResponse(java.io.InputStream) -
XML or JSON document in the form of a
String -
XML DOM
com.codename1.xml.Elementreturned fromcom.codename1.xml.XMLParser -
JSON DOM
java.util.Mapreturned fromcom.codename1.io.JSONParser
To use the expression processor when calling a webservice, you could use something like the following to parse JSON (notice this is interchangeable between JSON and XML):
Form hi = new Form("Location", new BoxLayout(BoxLayout.Y_AXIS));
hi.add("Pinpointing Location");
Display.getInstance().callSerially(() -> {
Location l = Display.getInstance().getLocationManager().getCurrentLocationSync();
ConnectionRequest request = new ConnectionRequest("http://maps.googleapis.com/maps/api/geocode/json", false) {
private String country;
private String region;
private String city;
private String json;
@Override
protected void readResponse(InputStream input) throws IOException {
Result result = Result.fromContent(input, Result.JSON);
country = result.getAsString("/results/address_components[types='country']/long_name");
region = result.getAsString("/results/address_components[types='administrative_area_level_1']/long_name");
city = result.getAsString("/results/address_components[types='locality']/long_name");
json = result.toString();
}
@Override
protected void postResponse() {
hi.removeAll();
hi.add(country);
hi.add(region);
hi.add(city);
hi.add(new SpanLabel(json));
hi.revalidate();
}
};
request.setContentType("application/json");
request.addRequestHeader("Accept", "application/json");
request.addArgument("sensor", "true");
request.addArgument("latlng", l.getLatitude() + "," + l.getLongitude());
NetworkManager.getInstance().addToQueue(request);
});
hi.show();
The returned JSON looks something like this (notice it's snipped because the data is too long):
{
"status": "OK",
"results": [
{
"place_id": "ChIJJ5T9-iFawokRTPGaOginEO4",
"formatted_address": "280 Broadway, New York, NY 10007, USA",
"address_components": [
{
"short_name": "280",
"types": ["street_number"],
"long_name": "280"
},
{
"short_name": "Broadway",
"types": ["route"],
"long_name": "Broadway"
},
{
"short_name": "Lower Manhattan",
"types": [
"neighborhood",
"political"
],
"long_name": "Lower Manhattan"
},
{
"short_name": "Manhattan",
"types": [
"sublocality_level_1",
"sublocality",
"political"
],
"long_name": "Manhattan"
},
{
"short_name": "New York",
"types": [
"locality",
"political"
],
"long_name": "New York"
},
{
"short_name": "New York County",
"types": [
"administrative_area_level_2",
"political"
],
"long_name": "New York County"
},
{
"short_name": "NY",
"types": [
"administrative_area_level_1",
"political"
],
"long_name": "New York"
},
{
"short_name": "US",
"types": [
"country",
"political"
],
"long_name": "United States"
},
{
"short_name": "10007",
"types": ["postal_code"],
"long_name": "10007"
},
{
"short_name": "1868",
"types": ["postal_code_suffix"],
"long_name": "1868"
}
],
"types": ["street_address"],
"geometry": {
"viewport": {
"northeast": {
"lng": -74.0044642197085,
"lat": 40.7156470802915
},
"southwest": {
"lng": -74.0071621802915,
"lat": 40.7129491197085
}
},
"location_type": "ROOFTOP",
"location": {
"lng": -74.00581319999999,
"lat": 40.7142981
}
}
}
/* SNIPED the rest */
]
}
The XML processor currently handles global selections by using a double slash anywhere within the expression, for example:
// get all address_component names anywhere in the document with a type "political"
String array[] = result.getAsStringArray("//address_component[type='political']/long_name");
// get all types anywhere under the second result (dimension is 0-based)
String array[] = result.getAsStringArray("/result[1]//type");
NOTE: Notice that Google's JSON webservice uses plural form for each of the node names in that API (ie. results, address_components, and types) where they don't in the XML services (ie result, address_component etc.).
Example 2
It also possible to do some more complex expressions. We'll use the following XML fragment for the next batch of examples:
Bernard
Tomic
SOUTHPORT
QLD
1992-10-21
Mathew
Ebden
CHURCHLANDS
WA
1987-11-26
Lleyton
Hewitt
EXETER
SA
1981-02-24
Above, if you want to select the IDs of all players that are ranked in the top 2, you can use an expression like:
int top2[] = result.getAsIntegerArray("//player[@rank < 3]/@id");
(Notice above that the expression is using an attribute for selecting both rank and id. In JSON documents, if you attempt to select an attribute, it will look for a child node under the attribute name you ask for).
If a document is ordered, you might want to select nodes by their position, for example:
String first2[] = result.getAsStringArray("//player[position() < 3]/firstname");
String secondLast = result.getAsString("//player[last() - 1]/firstName");
It is also possible to select parent nodes, by using the '..' expression. For example:
int id = result.getAsInteger("//lastname[text()='Hewitt']/../@id");
Above, we globally find a lastname element with a value of 'Hewitt', then grab the parent node of lastname which happens to be the player node, then grab the id attribute from the player node. Alternatively, you could get the same result from the following simpler statement:
int id = result.getAsInteger("//player[lastname='Hewitt']/@id");
It is also possible to nest expressions, for example:
String id=result.getAsInteger("//player[//address[country/isocode='CA']]/@id");
In the above example, if the player node had an address object, we'd be selecting all players from Canada. This is a simple example of a nested expression, but they can get much more complex, which will be required as the documents themselves get more complex. Moving on, to select a node based on the existence of an attribute:
int id[] = result.getAsIntegerArray("//player[@rank]/@id");
Above, we selected the IDs of all ranked players. Conversely, we can select the non-ranked players like this:
int id[] = result.getAsIntegerArray("//player[@rank=null]/@id");
(Logical not (!) operators currently are not implemented). You can also select by the existence of a child node.
int id[] = result.getAsIntegerArray("//player[middlename]/@id");
Above, we selected all players that have a middle name.
Keep in mind that the Codename One path expression language is not a full implementation of XPath 1.0, but does already handle many of the most useful features of the specification.
-
ClassesClassDescriptionAn evaluator for a very small expression language to extract primitive types from structured information.