【问题标题】:Groovy Node.depthFirst() returning a List of Nodes and Strings?Groovy Node.depthFirst() 返回节点和字符串列表?
【发布时间】:2012-12-07 13:16:33
【问题描述】:

我希望有人能指出我在这里遗漏的一些明显的东西。我觉得我已经这样做了一百次了,今晚出于某种原因,由此产生的行为让我陷入了循环。

我正在从公共 API 读取一些 XML。我想从某个节点('body' 中的所有内容)中提取所有文本,其中还包括各种子节点。简单示例:

<xml>
    <metadata>
        <article>
            <body>
                <sec>
                    <title>A Title</title>
                    <p>
                        This contains 
                        <italic>italics</italic> 
                        and
                        <xref ref-type="bibr">xref's</xref>
                        .
                    </p>
                </sec>
                <sec>
                    <title>Second Title</title>
                </sec>
            </body>
        </article>
    </metadata>
</xml>

所以最终我想遍历所需节点内的树(同样是“body”)并提取包含在其自然顺序中的所有文本。很简单,所以我只写了这个 Groovy 小脚本……

def xmlParser = new XmlParser()
def xml = xmlParser.parseText(rawXml)
xml.metadata.article.body[0].depthFirst().each { node ->
    if(node.children().size() == 1) {
        println node.text()
    }   
}

...继续以“没有方法签名:java.lang.String.children()”而爆炸。所以我在想自己“等等,什么?我要疯了吗?” Node.depthFirst() 应该只返回一个节点列表。我添加了一点“instanceof”检查,果然,我得到了 Node 对象和 String 对象的组合。具体来说,不在同一行的实体内的行将作为字符串返回,即“This contains”和“and”。其他一切都是节点(正如预期的那样)。

我可以轻松解决这个问题。但是,这似乎不是正确的行为,我希望有人能指出我正确的方向。

【问题讨论】:

  • 据我所知,Node.depthFirst 的行为与您在 groovy 1.7 中所期望的一样。在 groovy 2.0+ 中,我看到节点/字符串的结果相同。

标签: xml groovy xml-parsing xmlnode xmlslurper


【解决方案1】:

我很确定这是正确的行为(尽管我一直发现 XmlSlurper 和 XmlParser 的 API 很糟糕)。您可以迭代的所有东西都应该实现一个节点接口 IMO,并且可能有一个 typeTEXT,您可以使用它来了解从它们那里获取文本。

那些文本节点是有效节点,在许多情况下,您会想要点击它们,因为它在 XML 中进行深度优先遍历。如果它们没有被返回,那么您检查子节点大小是否为 1 的算法将不起作用,因为某些节点(如 &lt;p&gt; 标记)在其下方同时具有混合文本和元素。

另外,为什么depthFirst 不会始终返回文本是唯一子节点的所有文本节点,例如上面的italic,这让事情变得更糟。

我倾向于使用 groovy 方法的签名来让运行时找出处理每个节点的正确方法(而不是使用类似 instanceof 的方法),如下所示:

def rawXml = """<xml>
    <metadata>
        <article>
            <body>
                <sec>
                    <title>A Title</title>
                    <p>
                        This contains 
                        <italic>italics</italic> 
                        and
                        <xref ref-type="bibr">xref's</xref>
                        .
                    </p>
                </sec>
                <sec>
                    <title>Second Title</title>
                </sec>
            </body>
        </article>
    </metadata>
</xml>"""

def processNode(String nodeText) {
    return nodeText
}

def processNode(Object node) {
   if(node.children().size() == 1) {
       return node.text()
   }
}

def xmlParser = new XmlParser()
def xml = xmlParser.parseText(rawXml)
def xmlText = xml.metadata.article.body[0].'**'.findResults { node ->
    processNode(node)
}

println xmlText.join(" ")

打印

A Title This contains italics and xref's .  Second Title

或者,XmlSlurper 类可能会做更多您想要/期望的事情,并且从text() 方法获得更合理的输出集。如果你真的不需要对结果进行任何类型的 DOM 遍历(XmlParser 是“更好”的),我建议XmlSlurper

def xmlParser = new XmlSlurper()
def xml = xmlParser.parseText(rawXml)
def bodyText = xml.metadata.article.body[0].text()
println bodyText

打印:

A Title
                    This contains 
                    italics 
                    and
                    xref's
                    .
                Second Title

【讨论】:

  • 最后一点是我真正想要的,也正是我所期待的。我本可以发誓我试过了,但我一定是每次都把 depthFirst() 放在那里,这让我陷入了循环。
  • 哦,事实上 GPathResult.text() 的行为与 Node.text() 非常不同。我希望文档能提供更多信息...
  • 您能否详细说明双“*”的含义
  • @Shashank.gupta40 '**'depthFirst() 的简写
猜你喜欢
  • 2011-05-13
  • 2023-04-04
  • 2020-08-26
  • 1970-01-01
  • 2011-07-20
  • 2016-11-27
  • 1970-01-01
  • 2019-08-05
  • 2015-06-15
相关资源
最近更新 更多