chris-twiner / scalesXml

Alternate Scala XML library
77 stars 16 forks source link

Puzzle regarding multiple xpath usage #21

Closed satyagraha closed 11 years ago

satyagraha commented 11 years ago

Hi there, I am having a bit of trouble when attempting to apply an xpath to the results of a previous xpath. In the code below, I should be able to select the level3 node returned by the first xpath, but no match. If I use // instead, that wrongly picks up both level3 nodes. Thanks for any ideas on this!

package com.test

import scales.utils._
import ScalesUtils._
import scales.xml._
import ScalesXml._
import Functions._
import org.xml.sax.XMLReader
import org.xml.sax.helpers.XMLReaderFactory
import scales.xml.jaxen._
import java.io.StringReader

object LocalFactoryPool extends SimpleUnboundedPool[XMLReader] with DefaultSaxSupport {

  // doesn't support xml version retrieval
  override def getXmlVersion(reader: XMLReader): AnyRef =
    null

  def create = {
    val reader: XMLReader = XMLReaderFactory.createXMLReader
    reader
  }

}

object TestNestedXpath {

  def xpathExpr(expr: String) =
    ScalesXPath(expr).withNameConversion(ScalesXPath.localOnly)

  def test = {
    val xmlText = """
      <level1>
        <level2>
          <level3>contents of level 3</level3>
          <shouldHideChildren>
            <level3>should not see</level3>
          </shouldHideChildren>
        </level2>
      </level1>      
    """

   val doc = loadXmlReader(new StringReader(xmlText), NoOptimisation, LocalFactoryPool)
   val root = top(doc)

   val level2 = xpathExpr("/level1/level2").xmlPaths(root).head
   println("found level2: " + string(level2))

   val level3matches = xpathExpr("/level3").xmlPaths(level2).toList
   println(level3matches.size match {
     case 1 => "pass"
     case x => "unexpected size: " + x
   })
  }

  def main(args: Array[String]): Unit = {
    test
  }

}
chris-twiner commented 11 years ago
val level3matches = xpathExpr("/level2/level3").xmlPaths(level2).toList

you are still at level2 for that query, where level2 is your new document. If you don't know what the node is that matches but you know which should be below use /*/level3 and skip the root elem.

I very much appreciate the running test case, very easy to see what the intent is.

chris-twiner commented 11 years ago

See #22 for another way

satyagraha commented 11 years ago

I tried something similar using the underlying Jaxen library (1.1.3) in Java with DomJ, as follows:

package com.test.jaxen;

import java.io.StringReader;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.AbstractNode;
import org.jaxen.dom4j.Dom4jXPath;

public class TestJaxen {

    private void test() throws Exception {
        String xmlText = "<level1 ><level2><level3>contents of level 3</level3><shouldHideChildren><level3>should not see</level3></shouldHideChildren></level2></level1>";

        SAXReader reader = new SAXReader();
        Document doc = reader.read(new StringReader(xmlText));

        Dom4jXPath xpath2 = new Dom4jXPath("/level1/level2");
        @SuppressWarnings("unchecked")
        List<AbstractNode> level2 = (List<AbstractNode>) xpath2.evaluate(doc);
        System.out.println("level2: " + level2 + " size: " + level2.size());

        Dom4jXPath xpath3 = new Dom4jXPath("level3");
        @SuppressWarnings("unchecked")
        List<AbstractNode> level3 = (List<AbstractNode>) xpath3.evaluate(level2);
        System.out.println("level3: " + level3 + " size: " + level3.size());
    }

    public static void main(String[] args) throws Exception {
        TestJaxen instance = new TestJaxen();
        instance.test();
    }

}

Now, that works as I expect, showing values of 1 in each output line. For the scales version, your first proposal works, but it obliges me to add the "/level2/" prefix whereas the native Jaxen one doesn't. If I try the bare "level3" xpath, I get an exception thus:

Exception in thread "main" java.lang.RuntimeException: couldn't get childaxis at scala.sys.package$.error(package.scala:27) at scala.Predef$.error(Predef.scala:123) at scales.utils.package$.error(package.scala:12) at scales.xml.jaxen.ScalesNavigator.getChildAxisIterator(JaxenNavigator.scala:240) at org.jaxen.expr.iter.IterableChildAxis.iterator(IterableChildAxis.java:75) at org.jaxen.expr.DefaultNameStep.evaluate(DefaultNameStep.java:199) at scales.xml.jaxen.ScalesDefaultLocationPath.evaluate(Expr.scala:26) at org.jaxen.expr.DefaultXPathExpr.asList(DefaultXPathExpr.java:102) at scales.xml.jaxen.ScalesBaseJaxenXPath.selectNodesForContext(ScalesBaseJaxenXPath.java:681) at scales.xml.jaxen.ScalesBaseJaxenXPath.selectNodes(ScalesBaseJaxenXPath.java:220) at scales.xml.jaxen.ScalesXPath.evaluate(JaxenNavigator.scala:112) at scales.xml.jaxen.ScalesXPath.xmlPaths(JaxenNavigator.scala:139) at com.test.scales.TestNestedXpath$.test(TestNestedXpath.scala:50) at com.test.scales.TestNestedXpath$.main(TestNestedXpath.scala:58) at com.test.scales.TestNestedXpath.main(TestNestedXpath.scala)

The alternative query "./level3" also raises the same exception. Perhaps you would like to clarify, and thanks for the earlier responses. FYI, this is with Scales 0.4.4 and Jaxen 1.1.3.

chris-twiner commented 11 years ago

As chatted above I raised #22 to cover this, ./level3 etc should work.

Using "level3" directly also works with the fix but seems exceptionally strange to me - auto adding ./, but if its a Jaxen implementation detail (can't find any spec to cover this) then at least Scales will continue to work that way :)

chris-twiner commented 11 years ago

fix in 0.4.5 just released

chris-twiner commented 11 years ago

marking as bug and release, although original not - the / syntax should only go for the root, the developer can use top(tree) to repoint the /, but ./ better explains the intention as well.