Xml 有没有办法得到一个;反向;Scala中的XPath?

Xml 有没有办法得到一个;反向;Scala中的XPath?,xml,scala,Xml,Scala,如果我有一个DOM,是否可以获取元素的反向XPath?例如,如果我有: <start> <nodes> <node> <name>Whatever</name> </node> <node> <name>Whatever 2</name> </node> </nodes> </start>

如果我有一个DOM,是否可以获取元素的反向XPath?例如,如果我有:

<start>
  <nodes>
    <node>
      <name>Whatever</name>
    </node>
    <node>
      <name>Whatever 2</name>
    </node>
  </nodes>
</start>

无论什么
随便什么2

例如,如果我有一个名为
whater 2
的节点引用,是否可以返回
/start/nodes/node/name[.=“whater 2”]

听起来像是在寻找类似路径(node):XPath的函数?不幸的是,使用scala.xml无法有效地做到这一点,因为节点没有父引用。选项包括: 1) 在找到正确的节点后,搜索树并正确识别id。
2) 使用另一个支持父引用…反XML等的XML库(scala或java)

这里有一个非常简单的方法,可以使用scala REPL中的java DOM API来遍历树:

首先,我们导入相关软件包并设置文档生成器和源:

scala> import org.w3c.dom._
import org.w3c.dom._

scala> import javax.xml.parsers._
import javax.xml.parsers._

scala> val factory = DocumentBuilderFactory.newInstance()
factory: javax.xml.parsers.DocumentBuilderFactory = ...

scala> val builder = factory.newDocumentBuilder()
builder: javax.xml.parsers.DocumentBuilder = ...

scala> val source = new org.xml.sax.InputSource()
source: org.xml.sax.InputSource = org.xml.sax.InputSource@7ecec7c6
现在分析示例文档:

scala> val content = """<start>
             <nodes>
               <node><name>Whatever</name></node>
               <node><name>Whatever 2</name></node>
             </nodes>
           </start>"""
content: java.lang.String = ...

scala> source.setCharacterStream(new java.io.StringReader(content))

scala> val document = builder.parse(source)
document: org.w3c.dom.Document = [#document: null]
我们选择第二个
节点进行测试:

scala> val node = document.getElementsByTagName("name").item(1)
node: org.w3c.dom.Node = [name: null]
我们得到了我们所期望的:

scala> path(node)
res1: String = /start/nodes/node/name
调整
path
函数以避免显式递归,或者在它向树上移动时收集更多信息(例如,在必要时指示位置以避免歧义)并不难:

scala> def path(element: Element) = {
     |   def sameName(f: Node => Node)(n: Node) =
     |     Stream.iterate(n)(f).tail.takeWhile(_ != null).filter(
     |       _.getNodeName == n.getNodeName
     |     ).toList
     |   val preceding = sameName(_.getPreviousSibling) _
     |   val following = sameName(_.getNextSibling) _
     |   "/" + Stream.iterate[Node](element)(_.getParentNode).map {
     |     case _: Document => None
     |     case e: Element => Some { (preceding(e), following(e)) match {
     |       case (Nil, Nil) => e.getTagName
     |       case (els, _)   => e.getTagName + "[" + (els.size + 1) + "]"
     |     }}
     |   }.takeWhile(_.isDefined).map(_.get).reverse.mkString("/")
     | }
path: (element: org.w3c.dom.Element)java.lang.String
请注意,我稍微更改了类型,以明确这只会为元素提供有效的XPath路径。我们可以测试:

scala> path(node.asInstanceOf[Element])
res13: java.lang.String = /start/nodes/node[2]/name

这也是我们所期望的。

正如其他人所指出的,如果您只有一个
scala.xml.Node
,那么如果不花费大量的时间和空间,您将无法实现您的目标

然而,如果您愿意让您的呼叫者跳过一些障碍,并且您发现使用Java的想法令人讨厌,那么您可能会做得比尝试使用Java更糟糕


另请参见Daniel Spiewak的《反XML》(可能有一天会取代Scala的内置XML支持)

注意,许多(可能是所有)节点都有多个XPath表达式。这对您来说可能很好,但这取决于您需要做什么。即使使用更通用的东西,我也可以,因为
/start/nodes/node
注意,如果您使用的是Scala内置的XML支持,它只会给您一些类似DOM的东西。举个例子,这让你描述的有点棘手。我也会接受使用Java LIB的解决方案,因为它们可以直接从Scala使用。谢谢Travis给我一个很好的答案!
scala> path(node.asInstanceOf[Element])
res13: java.lang.String = /start/nodes/node[2]/name