Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / General / June 2006

Tip: Looking for answers? Try searching our database.

Is this the right way to use javax.xml.xpath?

Thread view: 
Larry Coon - 05 Jun 2006 15:41 GMT
I'm trying to figure out the right way to use the xpath api
under 1.5.  I'm not sure I'm doing it the right way when a
node has multiple children -- I can get to the data, but my
technique seems kludgy.  The samples I found online tell you
how to get a NodeList, but don't really help you from there.

First, here is a sample document (XPathTest.xml):

<?xml version="1.0" encoding="utf-8"?>

<test>
 <one>This is a single item</one>

 <many>
   <item>This is one of many items #1</item>
   <item>This is one of many items #2</item>
   <item>This is one of many items #3</item>
 </many>
</test>

-----

Here's an SSCCE.  The technique I use to get the single item
is easy enough, but I start to scratch my head when I go after
the many items:

import java.io.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;

public class TestXPath  {
 public TestXPath(String configFileName) {
   try {
     DocumentBuilderFactory domFactory =
         DocumentBuilderFactory.newInstance();

     DocumentBuilder builder = domFactory.newDocumentBuilder();
     Document document = builder.parse(new File(configFileName));

     XPathFactory  xFactory = XPathFactory.newInstance();
     XPath xPath = xFactory.newXPath();

     String s1 = xPath.evaluate("/test/one", document);
     System.out.println("One item: " + s1);

     NodeList nodes = (NodeList)
        xPath.evaluate("/test/many/item",
        document, XPathConstants.NODESET);

     int length = nodes.getLength();
     System.out.println("Number of items: " + length);

     for (int i = 0; i < length; i++) {
       String s2 = xPath.evaluate("/test/many/item["
          + (i + 1) + "]", document);

       System.out.println("Many items #" + i + ": " + s2);
     }
   }
   catch (Exception x) { x.printStackTrace(); }
 }

 public static void main(String[] args) {
   new TestXPath("XPathTest.xml");
 }
}

-----

I realize that a NodeList isn't Iterable, so I need to use a for loop
to get the individual items.  But the only thing I'm using the NodeList
for is to get a count -- I go back to the DOM to retrieve the actual
data.  This doesn't seem right.  Should I be using XPath on the NodeList
to get at the data more directly?  I tried various ways, and didn't get
anything to work.  Any advice?
Chris Smith - 05 Jun 2006 17:34 GMT
> I'm trying to figure out the right way to use the xpath api
> under 1.5.  I'm not sure I'm doing it the right way when a
> node has multiple children -- I can get to the data, but my
> technique seems kludgy.  The samples I found online tell you
> how to get a NodeList, but don't really help you from there.

Once you've got a NodeList, you would typically fall back to the normal
DOM API to extract the data from the node.  If you need (or just want)
to perform further XPath queries, you can pass the Node from the
NodeList as context.  For example...

public class TestXPath
{
   public static void main(String[] args) throws Exception
   {
       DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
       DocumentBuilder b = f.newDocumentBuilder();
       Document d = b.parse(new InputSource(new ByteArrayInputStream(
           "<test><many><item>a</item><item>b</item></many></test>"
           .getBytes("US-ASCII"))));

       XPathFactory xf = XPathFactory.newInstance();
       XPath xp = xf.newXPath();

       NodeList nl = (NodeList) xp.evaluate(
           "/test/many/item", d, XPathConstants.NODESET);

       for (int i = 0; i < nl.getLength(); i++)
       {
           Node n = nl.item(i);
           System.out.println(xp.evaluate(".", n));
       }
   }
}

Signature

Chris Smith - Lead Software Developer / Technical Trainer
MindIQ Corporation

Larry Coon - 05 Jun 2006 18:22 GMT
> Once you've got a NodeList, you would typically fall back to the normal
> DOM API to extract the data from the node.

Thanks for the reply, Chris.  This is one of the things that made
me want to ask -- falling back on the DOM API once I got to a certain
point seemed really inelegant.  I assumed it _couldn't_ be the right
way to do it.

> If you need (or just want)
> to perform further XPath queries, you can pass the Node from the
> NodeList as context.  For example...

(example snipped)

Ah, I tried various ways of doing evalutate() on the node, but I
never used the expression ".".  I guess that warrants a "duh."

What I'd really like to do is eliminate the iteration altogether.
The contents are being used to populate a Collection, so I'd love
to be able to just make one call to addAll().  Is there a set-based
XPath operation that I can use as the argument to addAll()?
Chris Smith - 05 Jun 2006 18:52 GMT
> What I'd really like to do is eliminate the iteration altogether.
> The contents are being used to populate a Collection, so I'd love
> to be able to just make one call to addAll().  Is there a set-based
> XPath operation that I can use as the argument to addAll()?

Not that I can see.  However, you can write the following once, in a
utility lib somewhere:

public class Nodes
{
   public static List<Node> asList(final NodeList nl)
   {
       return new AbstractList<Node>() {
           @Override
           public int size()
           {
               return nl.getLength();
           }

           @Override
           public Node get(int index)
           {
               return nl.item(index);
           }
       };
   }
}

And,

   col.addAll(Nodes.asList(xpath.evaluate(...),
       XPathConstants.NODESET)));

Is that close to what you want?  Feel free to drop the @Override and
<Node> from the code above if you aren't using Java 5.0 for the project.

Signature

Chris Smith - Lead Software Developer / Technical Trainer
MindIQ Corporation

Larry Coon - 05 Jun 2006 19:41 GMT
> Not that I can see.  However, you can write the following once, in a
> utility lib somewhere:

(snipped)

Ah, that makes perfect sense -- thanks!
Larry Coon - 05 Jun 2006 21:16 GMT
> Not that I can see.  However, you can write the following once, in a
> utility lib somewhere:
[quoted text clipped - 25 lines]
>
> Is that close to what you want?

On second look, it isn't quite what I want.  I don't want to populate
a collection of Nodes, I want to populate a collection of Strings.  I
suppose the approach is to send the XPath to asList(), so get() can use
it to evaluate() the node and return the resulting String?
Chris Smith - 05 Jun 2006 21:56 GMT
> On second look, it isn't quite what I want.  I don't want to populate
> a collection of Nodes, I want to populate a collection of Strings.  I
> suppose the approach is to send the XPath to asList(), so get() can use
> it to evaluate() the node and return the resulting String?

Sure, that'll work.

Let me clarify something: I used the XPath instance to extract the text
from a node in my first reply only because it is more general.  I
assumed that in practice you may want to perform more operations to
which XPath is well-suited.  XPath is a way to find your way around an
XML file; it's not a replacement for DOM.  If all you want is the text
of a node, just call getTextContent() on the Node, and you're done.  
XPath is good for navigation, which is clumsy and painful in the DOM,
but XPath is clumsy and painful for direct operations on a single node
of an XML document.

In a less trivial example where XPath is really appropriate, it would
perhaps still be cleaner for the implementation of Nodes.asList to
construct its own XPath instance, since there's no reason to assume that
the original NodeList comes from XPath code and therefore no reason to
assume that the caller has an XPath instance to supply.

Also, you'll probably want to rename my Nodes.asList to something else,
since it no longer does what the name suggests.

If you again want to be somewhat general, you can write:

   public static interface Filter<S,T>
   {
       public T exec(S arg);
   }

   public static <S,T> List<T> mapList(List<S> source, Filter<S,T> f)
   {
       List<T> r = new ArrayList<T>(source.size());
       for (S from : source) r.add(f.exec(from));
       return r;
   }

and then,

   col.addAll(MyUtil.mapList(
       Nodes.asList(xpath.evaluate(...)),
       new Filter<Node,String>() {
           public String exec(Node arg)
           {
               return arg.getTextContent();
           }
       }));

Signature

Chris Smith - Lead Software Developer / Technical Trainer
MindIQ Corporation

Larry Coon - 13 Jun 2006 06:01 GMT
> Sure, that'll work.
>
> Let me clarify something:

(clarification snipped)

Thanks Chris, I appreciate all the help.

Larry Coon
University of California


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.