You need to use XPath expressions to select objects from a complex object graph referencing
the contents of a Map
and using
expressions with variable references.
Use Commons JXPath to select objects from a collection using XPath
queries. The following example uses Commons Digester to parse an XML
file into an object graph, and selects Planet
objects with a radius greater than
5000:
import org.apache.commons.digester.Digester; import org.apache.commons.digester.xmlrules.DigesterLoader; import org.apache.commons.jxpath.JXPathContext; List planets = new ArrayList( ); // Parse Planet XML into a List of Planet objects InputStream input = getClass( ).getResourceAsStream("./planets.xml"); URL rules = getClass( ).getResource("./planet-digester-rules.xml"); Digester digester = DigesterLoader.createDigester(rules); digester.push(planets); digester.parse( input ); // Select all planets with a radius greater than 5000 System.out.println( "Planet Name where radius > 5000"); JXPathContext context = JXPathContext.newContext( planets ); Iterator iterator = context.iterate(".[@radius > 5000]/name"); while( iterator.hasNext( ) ) { Object o = (Object) iterator.next( ); System.out.println( "Object: " + o); }
The Planet
objects are filtered
and the names of planets with sufficient radii are printed to the
console:
Planet Name where radius > 5000 Object: Venus Object: Saturn
This object graph was created from an XML document that contains a
list of planets and their physical properties including mass, radius,
atmospheric composition, lunar population, and orbital distance and
period. Commons Digester (from Chapter 6) is
used to parse this XML document to a list of Planet
objects. The following XML document,
planets.xml
, was parsed in the
previous example:
<planets> <planet name="Venus" mass="4.869e+24" radius="6051.8"> <orbit distance="108200000" period="224.701"/> <atmosphere meanTemp="482" pressure="92"> <component symbol="CO2" percentage="96"/> <component symbol="N" percentage="3"/> <component symbol="Other" percentage="1"/> </atmosphere> </planet> <planet name="Mars" mass="6.421e+23" radius="3397.2"> <orbit distance="227940000" period="686.98"/> <atmosphere meanTemp="-63" pressure="0.007"> <component symbol="CO2" percentage="95.32"/> <component symbol="N2" percentage="2.7"/> <component symbol="Ar" percentage="1.6"/> </atmosphere> <moon>Phobos</moon> <moon>Deimos</moon> </planet> <planet name="Saturn" mass="5.688e+26" radius="60268"> <orbit distance="1429400000" period="29.458"/> <atmosphere meanTemp="-125" pressure="1.4"> <component symbol="H" percentage="97"/> <component symbol="He" percentage="3"/> </atmosphere> <moon>Pan</moon> <moon>Atlas</moon> <moon>Prometheus</moon> <moon>Pandora</moon> <moon>Epimetheus</moon> <moon>Janus</moon> <moon>Mimas</moon> </planet> </planets>
To parse this XML document, the following Digester rules are
passed to DigesterLoader.createDigester()
. This digester rule-set creates a Planet
, Orbit
, and Atmosphere
object for each planet. Moon
objects are created and added to Planet
objects using the addMoon( )
method on Planet
. Individual compounds in an atmosphere
are added to an Atmosphere
's component
Map
using addComponent( )
. The following XML document contains the contents of the
planet-digester-rules.xml
file used
in the previous example:
<digester-rules> <pattern value="planets/planet"> <object-create-rule classname="Planet"/> <set-properties-rule/> <pattern value="orbit"> <object-create-rule classname=" Orbit"/> <set-properties-rule/> <set-next-rule methodname="setOrbit" paramtype=" Orbit"/> </pattern> <pattern value="atmosphere"> <object-create-rule classname="Atmosphere"/> <set-properties-rule/> <pattern value="component"> <call-method-rule methodname="addComponent" paramcount="2" paramtypes="java.lang.String,java.lang.Double"/> <call-param-rule attrname="symbol" paramnumber="0"/> <call-param-rule attrname="percentage" paramnumber="1"/> </pattern> <set-next-rule methodname="setAtmosphere" paramtype=" Atmosphere"/> </pattern> <call-method-rule pattern="moon" methodname="addMoon" paramtypes="java.lang.String" paramcount="0"/> <set-next-rule methodname="add" paramtype="java.lang.Object"/> </pattern> </digester-rules>
XPath expressions can be parameterized by referencing a variable
name: $variable
. The following
example references a variable $moonName
, which is populated by calling
declareVariable( )
on context.getVariables( )
:
System.out.println( "Planet Name where a moon is named Deimos"); context.getVariables( ).declareVariable("moonName", "Deimos"); iterator = context.iterate("./moons[. = $moonName]/../name"); while( iterator.hasNext( ) ) { String name = (String) iterator.next( ); System.out.println( "Planet Namet: " + name); }
This example selects the name of a planet with a moon named "Deimos." The results of the previous example are printed below:
Planet Name where a moon is named Deimos Planet Namet: Mars
Planet.getAtmosphere( ).getComponents(
)
returns a Map
with
element symbols as keys. The following example selects every planet with
more than a 2% Helium atmosphere:
System.out.println( "Planet where Helium percentage greater than 2"); iterator = context.iterate("./atmosphere/components/He[.>2]/../../.."); while( iterator.hasNext( ) ) { Planet p = (Planet) iterator.next( ); System.out.println( "Planet: " + p.getName( )); }
To select every planet with more than a 2% Helium atmosphere, the
XPath expression in the previous example references a specific key in
the components Map
as if it were a
nested element. components/He[.>2]
will evaluate to
true
if getComponents( ).get("He")
is a number larger
than 2. The previous code determines that Saturn is the only one of
these three planets with more than 2% Helium:
Planet where Helium percentage greater than 2 Planet: Saturn
The following example prints a list of each moon and the corresponding planet using a reference to a variable in an XPath expression:
System.out.println( "All of the Moon Names"); iterator = context.iterate("./moons"); while( iterator.hasNext( ) ) { String moon = (String) iterator.next( ); context.getVariables( ).declareVariable("moonName", moon); String planet = (String) context.getValue("./moons[. = $moonName]/../name"); System.out.println( "Moon: " + moon + ", \t\t\tPlanet: " + planet); }
The previous example shows that a JXPathContext
can be reused. This example
iterates through each moon and finds the name of the planet
corresponding to each moon using the results of the first expression to
populate a variable in the second expression. The XPath expression,
./moons[. = $moonName]/../name
,
contains a reference to the variable $moonName
, which is set by passing a variable
name and value to the declareVariable(
)
method on JXPathContext
. This example prints each moon
and planet to the console as follows:
All of the Moon Names Moon: Phobos, Planet: Mars Moon: Deimos, Planet: Mars Moon: Pan, Planet: Saturn Moon: Atlas, Planet: Saturn Moon: Prometheus, Planet: Saturn Moon: Pandora, Planet: Saturn Moon: Epimetheus, Planet: Saturn Moon: Janus, Planet: Saturn Moon: Mimas, Planet: Saturn
There is much more to JXPath including object creation and the ability to set properties using XPath expressions; for more information about using JXPath to access maps, collections, servlet contexts, and DOM/JDOM Documents, see the JXPath User's Guide (http://commons.apache.org/jxpath/users-guide.html).