JXP
http://www.japisoft.com
Contact : http://www.japisoft.com/feedback.html
v1.3
Features :
- High performance full XPath
1.0 solution,
- SAX - like event system,
- Inner expression tree for fast multiple document evaluation,
- Syntax error location and cause,
- DOM support,
- FastParser lightweight node support,
- Customizable API with navigator, function libraries,
- Plugin for each node type system,
- Full samples with DOM,
- Lower/upper case feature support
- Tested on 150 XPath expressions,
- Open source for the registered version,
- All in 88 Ko,
- JDK1.1 and later compliant (tested on JDK1.1.8 and JDK1.4.1)
JXPath is a shareware, it is free to try for 30 days,
else you must register for a low price the full version at : http://www.japisoft.com/buy.html
I. Usage
a. FastParser
In the following sample, we parse an XML document with FastParser
and evaluate
an Xpath expression.
import java.io.*; import com.japisoft.xpath.*; import com.japisoft.xpath.kit.FastParserKit;
import com.japisoft.fastparser.*; import com.japisoft.fastparser.node.*;
...
// Parse the XML file 'TEST.XML'
Parser p = new Parser(); p.setInputStream( new FileInputStream( "TEST.XML" ) ); p.parse();
// Use the document element root
SimpleNode root = ( SimpleNode )p.getDocument().getRoot();
// Initialize the XPath engine with a valid Kit
XPath xp = new XPath( new FastParserKit() );
// Set the node for evaluation
xp.setReferenceNode( root );
// Set the XPat expression
xp.setXPathExpression("child::*[self::a or self::b][position()=last() - 1]" );
// Evaluate it
NodeSet ns = xp.resolve();
|
All the Xpath part is managed by the XPath class hiding
inner syntax building and evaluation.
When building an XPath class, you have to specify an XPath kit, this
kit is used for a specific
node type. For sample you have a DOM kit for
any DOM document evaluation or a FastParser
kit for
lightweight FastParser node. You can easily write your own kit.
Once you have an XPath instance, you just have to specify both the
reference node and the XPath expression.
The reference node is used for relative Xpath expression, if you use a
non relative expression, then you can
specify the root document element.
Calling the resolve method,
you receive a NodeSet
instance that contains all the result nodes. The NodeSet is
a subclass of the java standard Vector class.
You can also invoke the resolveAny
method that will return an Object rather than a NodeSet. This method is
useful
when you don't expect a set of nodes but rather a boolean, number,
string values. So you have to detect yourself the
object type before using it.
b. DOM
In this case, we parse an XML document with Xerces an evaluate an XPath
expression
from a DOM document.
import java.io.*; import com.japisoft.xpath.*; import com.japisoft.xpath.kit.DOMKit;
import org.w3c.dom.*; import org.apache.xerces.parsers.DOMParser;
...
// Parse the file
DOMParser parser = new DOMParser(); parser.parse( xmlFile ); Document doc = parser.getDocument();
// Root node
Node root = doc.getDocumentElement(); // Initialize the XPath engine with a valid Kit
XPath xp = new XPath( new DOMParserKit() );
// Set the node for evaluation
xp.setReferenceNode( root );
// Set the XPat expression
xp.setXPathExpression("child::*[self::a or self::b][position()=last() - 1]" );
// Evaluate it
NodeSet ns = xp.resolve();
|
This evaluation is the same as in the previous sample, we replace the FastParserKit
by the
DOMParserKit.
II. XPath context
We have seen that expression evaluation is made thanks to a reference
node. However you have
more options for setting your evaluation like the namespaces supported
and the variables used.
All this options are available in the XPath class.
a. Namespaces
- addNamespaceDeclaration : will add a mapping between a prefix
and a namespace URI
- removeNamespaceDeclaration : will remove a mapping between a
prefix and a namespace URI
Such namespace prefix is used in the XPath expression like :
/descendant::test:*
/descendant::test:e
In the first case, we retreive all node descendants matching the
namespace declared for the prefix test. In the second
case, we retreive all node descendants matching the namespace declared
for the prefix test and matching the name e.
If you use a prefix that is not declared, an exception will be thrown
while evaluating the XPath expression.
b. Variables
- addVariable(String name, boolean
value) : will add a name variable with a boolean value
- addVariable(String name, double
value) : will add a name variable with a double value. If you
wish another number type, then you will have to cast it in a double.
- addVariable(String name, String
value ) : will add a name variable with a String value
- addVariable(String name, NodeSet value ) : will add a name variable
tied to a NodeSet.
If a variable in XPath expression is not found, then an exception will
be thrown while evaluating.
III. XPathKit
In this chapter, we try to learn to create a specific evaluation kit.
This kit can be built for non DOM or non
FastParser node. This kit helps the engine to navigate in an XML
document while evaluating the XPath expression.
First of all an XPath
kit realizes an interface.
As sample, we provide here a sample from the DOM kit content :
package com.japisoft.xpath.kit;
import com.japisoft.xpath.function.basic.FunctionLib; import com.japisoft.xpath.function.Lib; import com.japisoft.xpath.navigator.DOMNavigator; import com.japisoft.xpath.XPathKit; import com.japisoft.xpath.NodeSet; import com.japisoft.xpath.Navigator;
import org.w3c.dom.*;
/** * Sample of XPathKit for DOM * @author (c) 2003 JAPISOFT * @version 1.0 */ public class DOMKit implements XPathKit {
/** @return the library resolver. If <code>null</code> is returned then the standard library is used */ public Lib getLibrary() { return new FunctionLib(); }
/** @return the tree navigator toolkit */ public Navigator getNavigator() { return new DOMNavigator(); }
// Particular method for the string-value on node private void findSubTextNode( Node n, StringBuffer res ) { NodeList nl = n.getChildNodes(); for ( int i = 0; i < nl.getLength(); i++ ) { Node child = nl.item( i ); if ( child.getNodeType() == Node.TEXT_NODE ) res.append( child.getNodeValue() ); }
for ( int i = 0; i < nl.getLength(); i++ ) { Node child = nl.item( i ); if ( child.hasChildNodes() && child.getNodeType() == Node.ELEMENT_NODE ) findSubTextNode( child, res ); } }
/** Compute the string-value for this node */ public String getStringValue( Object node ) { if ( node instanceof Node ) { Node n = (Node) node;
if ( n.getNodeType() == Node.ELEMENT_NODE ) { // Particular cas for node : get all sub text node StringBuffer sb = new StringBuffer(); findSubTextNode( n, sb ); return sb.toString(); } else return n.getNodeValue(); } else return node.toString(); }
/** Compute the node ID value */
public String getId( Object node ) { Node n = ( Node )node; try { return (n.getAttributes().getNamedItem("ID")).getNodeValue(); } catch( NullPointerException exc ) { return ""; } }
/** Compute the local name of the node */ public String getLocalName( Object node ) { Node n = ( Node )node; return n.getLocalName(); }
/** Compute the namespace URI for this node */ public String getNamespaceURI( Object node ) { Node n = ( Node )node; return n.getNamespaceURI(); }
/** Compute the qualified name for this node */
public String getName( Object node ) { Node n = ( Node )node; if ( n.getPrefix() != null ) { return n.getPrefix() + ":" + n.getNodeName(); } return getLocalName( node ); }
/** Compute the language for this node */ public String getLang( Object node ) { Node n = ( Node )node; try { return ( n.getAttributes().getNamedItem( "xml:lang" ) ).getNodeValue(); } catch( NullPointerException exc ) { return ""; } } }
|
The XPath kit is composed of three parts :
- The library part : method getLibrary
- The navigator part : method getNavigator
- Facilities on specific node type : All other public methods
The first part references to the standard XPath library. In any case,
you can use the FunctionLib.
This
functionLIb supports :
last, position, count, id,
local-name, namespace-uri, name, string, concat, starts-with, contains,
substring-before, substring-after, substring, string-length,
normalize-space, translate, boolean, not, true, false, lang, number,
sum, floor, ceiling, round : You can obtain detail about the
meaning of each function here : http://www.w3.org/TR/xpath
You can write or extent the existing library by implementing the Lib
interface or changing the FunctionLib
classes. The FunctionLib
is an Hashtable that maps the function name to a Function
implantation.
The second part is the most important. This is called the navigator
because it offers conveniences for navigating in the
XML tree.
Mainly, you have to implement this function :
public NodeSet getNodes( Object
refNode, String axis, String nodeType, String name );
The refNode is a node from
your node system, in a DOM system it will be a Node instance... The axis
refers to the way we want to navigate : 'ancestor', 'ancestor-or-self',
'attribute', 'child', 'descendant', 'descendant-or-self',
'following',
'following-sibling', 'namespace', 'parent', 'preceding',
'preceding-sibling', 'self'. Explanation for each axis is made in the
W3C document. The nodeType
specifies a kind of node : 'comment', 'text', 'processing-instruction',
'node'. And the name
is the name that must be matched like 'root' or '*'...
The last part is a set of facilities on node used by the standard XPath
library : FunctionLib.
As sample the getLocalName
returns the XML element local name, you may refer to the XML 1.0 recommandation for
function meaning.
IV. Features
A kit can include a set of features. A kit declares supporting a
feature by calling the addFeature method
from its super class AbstractKit.
/** * Sample of XPathKit for DOM * @author (c) 2003 JAPISOFT * @version 1.0 */ public class DOMKit extends AbstractKit {
/** Feature for ignoring lower/upper case, by default to false */ public final static String IGNORE_CASE_FEATURE = "http://www.japisoft.com/jxp/dom/ignorecase";
public DOMKit() { super(); addFeature( IGNORE_CASE_FEATURE, false); }
...
|
User can list available features for a kit by calling getSupportedFeatures() or by using
the isFeatureSupported
method for checking a feature.
Here a sample of usage for ignoring lower/upper case from the DOMKit.
DOMKit kit = new DOMKit(); kit.setFeature( kit.IGNORE_CASE_FEATURE, true ); XPath xp = new XPath( kit ); ...
|
When a feature is not supported, a RuntimeException is thown. Usely,
you don't have to insert your setFeature in a try catch section unless
you may change of kit.
V. Test cases
Here the list of XPath expression tested on JXPath both on DOM and FastParser lightweight
nodes.
a/d[2]/e local-name( /root/a ) *[c or d] a/c[d] a/c[d="Text for D"] a[3][@a1="va1"] a[@a1="va1"][1] a[@a1="va1"] /root/a/c/../@a1 /root/a/.. .//c . //c/d //c /root/a//d /root/a/d //a //d a/d[2]/e */d a[last()] a[1] /root/a[1]/@* /root/a[1]/@a1 text() * a /root/a[(1+1-1)*2 div 2] /root child::*[self::a or self::b][position()=last() - 1] child::*[self::a or self::b] child::a/child::c[child::d='Text for D'] child::a/child::c[child::d] child::a[position()=1][attribute::a1="va1"] child::a[attribute::a1="va1"][position()=1] child::a[attribute::a2="va2"] child::a/child::d[position()=2]/child::e[position()=1] /descendant::a[position()=2] child::a[1]/child::d[1]/preceding-sibling::c[position()=1] child::a[2]/following-sibling::b[position()=1] child::a[position()=2] child::a[position()=last()-1] child::a[position()=last()] child::a[position()=1] /descendant::a/child::c / /descendant::a /child::root/child::*/child::d child::b/descendant::a self::root /child::root/child::a/descendant-or-self::a /child::root/child::a/ancestor-or-self::a /child::root/child::a/child::c/ancestor::root /child::root/descendant::a /child::root/child::a/attribute::* /child::root/child::a/attribute::a1 /child::root/child::node() /child::root/child::a/child::d/child::text() /child::root/child::* /child::root/child::a /descendant::test:* /descendant::test:* /descendant::test:e /descendant::test:f /root/test:*/text() a/c[d=$var1] //root //c[@c1='vc1'] //c[starts-with(@c1, 'vc')] //c[contains(@c1, 'vc')] //c[string-length(@c1)=3] 'hello' local-name( /root/a ) # ------------------------- FUNCTION TESTS --------------------- # CONCAT concat( 'hello', 'world' ) concat( 'hello', $var1 ) concat( "add ", local-name( /root/a ) ) # BOOLEAN boolean( /root ) boolean( /root2 ) boolean( 10 ) boolean( -10 ) boolean( "hello world" ) boolean( "" ) # CEILING ceiling( 10.5 ) # CONTAINS contains( "aaab", "ab" ) contains( "aaab", "bb" ) contains( local-name( /root ), "root" ) # COUNT count( /root/* ) count( /root ) # FALSE false() # FLOOR floor( 10.5 ) # ID id( "1234" ) id( "f1" ) id( "f1 f2" ) # Lang *[lang('fr')] //a[lang( 'fr' )] //c[lang( 'fr' )] //c[lang( 'fr-CA' )] # Name name( /root/* ) name( /root/test:* ) # NamespaceURI namespace-uri( /root/* ) namespace-uri( /root/test:* ) /root/test:*[namespace-uri()='http://www.japisoft.com'] //e[namespace-uri()='http://www.japisoft.com'] # NormalizeSpace normalize-space( ' test1 ' ) normalize-space( 'test1 test2 test3 test4 ') //d[normalize-space()='Text for D'] # Not not(true()) not(false()) //e[not(namespace-uri()='http://www.japisoft.com')] # Number number(false()) number(true()) number(10) number("10.0") number( /root/* ) number( "10a" ) # Round round(1.5) round(-1.5) round(0) # StartsWith starts-with( "test1", "test" ) starts-with( "test", "test1" ) # String string( number( 'AA' ) ) string( "test" ) string( 10 ) string( true() ) string( false() ) string( /root/a ) # StringLength string-length( "test" ) string-length( /root/a ) string-length( normalize-space( /root/a ) ) # SubString substring("TEST", 1) substring("TEST", 1.5) # SubStringAfter substring-after( "10/11/13", "/" ) substring-after( "", "/" ) # SubStringBefore substring-before( "10/11/12", "/" ) substring-before( "", "/" ) # Sum sum( /root/g ) # Translate translate("bar","abc","ABC") # True true()
|