JXP

http://www.japisoft.com

Contact
: http://www.japisoft.com/feedback.html

v1.3.1


Features
:

JXP 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 :
  1. The library part : method getLibrary
  2. The navigator part : method getNavigator
  3. 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 JXP 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()




(c) 2006 JAPISOFT