如果我有一个DOM,是否可以获得元素的反向XPath?例如,如果我有:
<start>
<nodes>
<node>
<name>Whatever</name>
</node>
<node>
<name>Whatever 2</name>
</node>
</nodes>
</start>
例如,如果我引用了名为Whatever 2
的节点,是否可以返回/start/nodes/node/name[. = "Whatever 2"]
?
答案 0 :(得分:3)
这是使用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]
这是一个非常简单的函数,以递归方式将DOM传递到文档根目录:
scala> def path: Node => String = {
| case document: Document => ""
| case node => path(node.getParentNode) + "/" + node.getNodeName
| }
path: org.w3c.dom.Node => String
我们选择第二个<name>
节点进行测试:
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
这也是我们的期望。
答案 1 :(得分:1)
正如其他人所指出的那样,如果你拥有的只是scala.xml.Node
,那么如果不花费大量的时间和空间,你就无法实现目标。
然而,如果你愿意让你的呼叫者跳过几个圈,并且你发现让你厌恶Java的想法,你可能会比尝试zipper更糟糕。
另请参阅Daniel Spiewak在Anti-XML中的implementation(可能有一天会取代Scala的内置XML支持)
答案 2 :(得分:0)
听起来你正在寻找像路径(Node)这样的函数:XPath?不幸的是,由于节点没有父引用,因此无法有效地使用scala.xml。选项包括: 1)在找到正确的节点后搜索树并正确识别。 2)使用支持父引用的另一个XML库(scala或java)... anti-xml等