有没有办法使用Scala的XML库执行XPath字符串查询?

时间:2010-11-19 18:24:01

标签: scala xpath

给定一个scala XML对象,我可以执行xpath字符串查询,例如“// entries [@ title ='scala']”吗?

理想情况下,它会像:

<a><b name='n1'></b></a>.xpath("//b[@name='n1']")

我无法手动将所有xpath查询转换为scala的内部xpath-ish方法调用,因为我的程序将动态接受xpath查询。

此外,内置的java xml库非常冗长,所以我想避免使用它。

2 个答案:

答案 0 :(得分:4)

您最好的选择是(并且始终是,甚至是Java)使用JDOM。我使用以下库对JDom进行了拉伸操作,以便更加友好:

import org.jdom._
import org.jdom.xpath._
import scala.collection.JavaConversions
import java.util._
import scala.collection.Traversable


package pimp.org.jdom{
   object XMLNamespace{
      def apply(prefix:String,uri:String) = Namespace.getNamespace(prefix,uri)
      def unapply(x:Namespace) = Some( (x.getPrefix, x.getURI) )
   }
   object XMLElement{
      implicit def wrap(e:Element) = new XMLElement(e)
      def unapply(x:Element) = Some( (x.getName, x.getNamespace) )
   }
   class XMLElement(underlying:Element){
      def attributes:java.util.List[Attribute] =
         underlying.getAttributes.asInstanceOf[java.util.List[Attribute]]
      def children:java.util.List[Element] =
         underlying.getChildren.asInstanceOf[java.util.List[Element]]
      def children(name: String): java.util.List[Element] =
         underlying.getChildren(name).asInstanceOf[java.util.List[Element]]
      def children(name: String, ns: Namespace): java.util.List[Element] =
         underlying.getChildren(name, ns).asInstanceOf[java.util.List[Element]]
   }
}

package pimp.org.jdom.xpath{
   import pimp.org.jdom._

   //instances of these classes are not thread safe when xpath variables are used

   class SingleNodeQuery[NType](val expression:String)(implicit namespaces:Traversable[Namespace]=null){
      private val compiled=XPath.newInstance(expression)

      if (namespaces!=null){
         for ( ns <- namespaces ) compiled.addNamespace(ns.getPrefix,ns.getURI)
      }

      def apply(startFrom:Any,variables:(String,String)*)={
         variables.foreach{ x=> compiled.setVariable(x._1,x._2)}
         compiled.selectSingleNode(startFrom).asInstanceOf[NType]
      }
   }

   class NodesQuery[NType](val expression:String)(implicit namespaces:Traversable[Namespace]=null){
      private val compiled=XPath.newInstance(expression)

      if (namespaces!=null){
         for ( ns <- namespaces ) compiled.addNamespace(ns.getPrefix,ns.getURI)
      }

      def apply(startFrom:Any,variables:(String,String)*)={
         variables.foreach{ x=> compiled.setVariable(x._1,x._2)}
         compiled.selectNodes(startFrom).asInstanceOf[java.util.List[NType]]
      }
   }

   class NumberValueQuery(val expression:String)(implicit namespaces:Traversable[Namespace]=null){
      private val compiled=XPath.newInstance(expression)

      if (namespaces!=null){
         for ( ns <- namespaces ) compiled.addNamespace(ns.getPrefix,ns.getURI)
      }

      def apply(startFrom:Any,variables:(String,String)*)={
         variables.foreach{ x=> compiled.setVariable(x._1,x._2)}
         compiled.numberValueOf(startFrom).intValue
      }
   }

   class ValueQuery(val expression:String)(implicit namespaces:Traversable[Namespace]=null){
      private val compiled=XPath.newInstance(expression)

      if (namespaces!=null){
         for ( ns <- namespaces ) compiled.addNamespace(ns.getPrefix,ns.getURI)
      }

      def apply(startFrom:Any,variables:(String,String)*)={
         variables.foreach{ x=> compiled.setVariable(x._1,x._2)}
         compiled.valueOf(startFrom)
      }
   }

}

我写这篇文章时的想法是,一般情况下,您希望提前编译每个XPath查询(以便可以多次重复使用),并且您希望指定查询返回的类型你在哪里指定查询的文本(不像JDOM的XPath类那样选择在执行时调用的四种方法之一)。

命名空间应该隐式传递(所以你可以指定它们然后忘记它们),并且XPath变量绑定应该在查询时可用。

您可以像这样使用库:(可以推断显式类型注释 - 我将它们包含在内只是为了说明。)

val S = XMLNamespace("s","http://www.nist.gov/speech/atlas")
val XLink = XMLNamespace("xlink", "http://www.w3.org/1999/xlink")
implicit val xmlns= List(S, XLink)

private val anchorQuery=new ValueQuery("s:AnchorRef[@role=$role]/@xlink:href")

val start:String=anchorQuery(region,"role"->"start")
val end:String=anchorQuery(region,"role"->"end")

//or

private val annotationQuery=new NodesQuery[Element]("/s:Corpus/s:Analysis/s:AnnotationSet/s:Annotation")

for(annotation:Element <- annotationQuery(doc)) {
  //do something with it
}

我想我应该想出一些方法向公众发布这个。

答案 1 :(得分:1)

kantan.xpath就是这么做的。这是我刚刚在REPL中输入的内容:

import kantan.xpath._
import kantan.xpath.ops._

"<a><b name='n1'></b></a>".evalXPath[Node]("//b[@name='n1']")

,其中Node类型参数描述了希望从XML文档中提取的类型。一个更明确的例子可能是:

new URI("http://stackoverflow.com").evalXPath[List[URI]]("//a/@href")

这将下载stackoverflow主页,将其评估为XML文档(有一个用于HTML清理的NekoHTML模块)并提取所有链接的目标。