给定一个scala XML对象,我可以执行xpath字符串查询,例如“// entries [@ title ='scala']”吗?
理想情况下,它会像:
<a><b name='n1'></b></a>.xpath("//b[@name='n1']")
我无法手动将所有xpath查询转换为scala的内部xpath-ish方法调用,因为我的程序将动态接受xpath查询。
此外,内置的java xml库非常冗长,所以我想避免使用它。
答案 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模块)并提取所有链接的目标。