Есть ли способ выполнить строковый запрос XPath с использованием библиотеки XML Scala? - PullRequest
3 голосов
/ 19 ноября 2010

Имея XML-объект scala, могу ли я выполнить запрос строки xpath, например, "// records [@ title = 'scala']"?

В идеале это будет выглядеть так:

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

Я не могу вручную преобразовать все запросы xpath во внутренние вызовы метода xpath-ish в scala, так как моя программа будет динамически принимать запросы xpath.

Кроме того, встроенный JavaБиблиотека xml очень многословна, поэтому я бы хотел ее избежать.

Ответы [ 2 ]

4 голосов
/ 19 ноября 2010

Лучше всего (и всегда было, даже на Java) использовать JDOM.Я снабдил JDom следующей библиотекой, чтобы она была немного более дружественной к Scala:

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 (чтобы онможет использоваться повторно более одного раза), и вы хотите указать тип, возвращаемый запросом, в точке, где вы указываете текст запроса (не так, как класс XPath JDOM, который выбирает один из четырех методов, вызываемых при выполнении)время).

Пространства имен должны передаваться неявно (чтобы вы могли указать их один раз, а затем забыть о них), а привязка переменных 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 голос
/ 25 апреля 2016

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-документ (есть модуль NekoHTML для очистки HTML) и извлечь цель всех ссылок.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...