我正在学习scala,我正在寻找更新某个xml中的嵌套节点。我有一些工作,但我想知道它是否是最优雅的方式。
我有一些xml:
val InputXml : Node =
<root>
<subnode>
<version>1</version>
</subnode>
<contents>
<version>1</version>
</contents>
</root>
我想更新子节点中的版本节点,但不更新内容中的节点。
这是我的功能:
def updateVersion( node : Node ) : Node =
{
def updateElements( seq : Seq[Node]) : Seq[Node] =
{
var subElements = for( subNode <- seq ) yield
{
updateVersion( subNode )
}
subElements
}
node match
{
case <root>{ ch @ _* }</root> =>
{
<root>{ updateElements( ch ) }</root>
}
case <subnode>{ ch @ _* }</subnode> =>
{
<subnode>{ updateElements( ch ) }</subnode>
}
case <version>{ contents }</version> =>
{
<version>2</version>
}
case other @ _ =>
{
other
}
}
}
有没有更简洁的方式来编写这个函数?
答案 0 :(得分:55)
这一次,并没有人真正给出最合适的答案!不过,现在我已经了解了它,这是我对它的新看法:
import scala.xml._
import scala.xml.transform._
object t1 extends RewriteRule {
override def transform(n: Node): Seq[Node] = n match {
case Elem(prefix, "version", attribs, scope, _*) =>
Elem(prefix, "version", attribs, scope, Text("2"))
case other => other
}
}
object rt1 extends RuleTransformer(t1)
object t2 extends RewriteRule {
override def transform(n: Node): Seq[Node] = n match {
case sn @ Elem(_, "subnode", _, _, _*) => rt1(sn)
case other => other
}
}
object rt2 extends RuleTransformer(t2)
rt2(InputXml)
现在,做一些解释。类RewriteRule
是抽象的。它定义了两种方法,都称为transform
。其中一个只需Node
个Sequence
,另一个Node
transform
。它是一个抽象类,所以我们不能直接实例化它。通过添加定义,在这种情况下覆盖其中一个RuleTransformer
方法,我们正在创建它的匿名子类。每个RewriteRule都需要关注一个任务,尽管它可以做很多。
接下来,课程RewriteRule
将可变数量的Node
作为参数。它的变换方法需要Sequence
并返回Node
RewriteRule
,通过应用用于实例化的每个BasicTransformer
。
这两个类派生自apply
,它定义了一些不需要在更高层次上关注自己的方法。不过,transform
方法调用RuleTransformer
,因此RewriteRule
和RuleTransformer
都可以使用与之关联的语法糖。在这个例子中,前者和后者没有。
这里我们使用两个Elem
级别,因为第一个级别将过滤器应用于更高级别的节点,第二个级别将更改应用于通过过滤器的任何过程。
还使用了提取器version
,因此无需关注命名空间等细节或是否存在属性。并非元素2
的内容被完全丢弃并替换为_*
。如果需要,它也可以匹配。
另请注意,提取器的最后一个参数是_
,而不是*
。这意味着这些元素可以有多个孩子。如果您忘记了Text
,则匹配可能会失败。在示例中,如果没有空格,匹配不会失败。由于空格被转换为subnode
个元素,version
下的单个空格会使匹配失败。
此代码比其他提出的建议更大,但它的优点是对XML结构的了解远远少于其他建议。它会更改任何名为subnode
的元素 - 无论多少级别 - 一个名为RewriteRule
的元素,无论名称空间,属性等等。
此外......好吧,如果你要进行很多转换,递归模式匹配就会变得很快。使用RuleTransformer
和xslt
,您可以使用Scala代码有效地替换{{1}}个文件。
答案 1 :(得分:13)
您可以使用Lift的CSS Selector Transforms并编写:
"subnode" #> ("version *" #> 2)
请参阅http://stable.simply.liftweb.net/#sec:CSS-Selector-Transforms
答案 2 :(得分:11)
我认为最初的逻辑是好的。 这与(我敢说什么?)Scala-ish风味的代码相同:
def updateVersion( node : Node ) : Node = {
def updateElements( seq : Seq[Node]) : Seq[Node] =
for( subNode <- seq ) yield updateVersion( subNode )
node match {
case <root>{ ch @ _* }</root> => <root>{ updateElements( ch ) }</root>
case <subnode>{ ch @ _* }</subnode> => <subnode>{ updateElements( ch ) }</subnode>
case <version>{ contents }</version> => <version>2</version>
case other @ _ => other
}
}
它看起来更紧凑(但实际上是相同的:))
如果你愿意,你也可以摆脱updateElements。您希望将updateVersion应用于序列的所有元素。这是map method。有了它,你可以重写行
case <subnode>{ ch @ _* }</subnode> => <subnode>{ updateElements( ch ) }</subnode>
与
case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion (_)) }</subnode>
由于更新版本只需要1个参数,我99%肯定你可以省略它并写下:
case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion) }</subnode>
结束于:
def updateVersion( node : Node ) : Node = node match {
case <root>{ ch @ _* }</root> => <root>{ ch.map(updateVersion )}</root>
case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion ) }</subnode>
case <version>{ contents }</version> => <version>2</version>
case other @ _ => other
}
您怎么看?
答案 3 :(得分:6)
我已经学到了更多,并在另一个答案中提出了我认为是一个优秀的解决方案。我已经解决了这个问题,因为我注意到我没有考虑subnode
限制。
感谢您的提问!我刚刚在处理XML时学到了一些很酷的东西。这是你想要的:
def updateVersion(node: Node): Node = {
def updateNodes(ns: Seq[Node], mayChange: Boolean): Seq[Node] =
for(subnode <- ns) yield subnode match {
case <version>{ _ }</version> if mayChange => <version>2</version>
case Elem(prefix, "subnode", attribs, scope, children @ _*) =>
Elem(prefix, "subnode", attribs, scope, updateNodes(children, true) : _*)
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(prefix, label, attribs, scope, updateNodes(children, mayChange) : _*)
case other => other // preserve text
}
updateNodes(node.theSeq, false)(0)
}
现在,解释。第一个和最后一个案例陈述应该很明显。最后一个用于捕获非元素的XML部分。或者,换句话说,文本。但请注意,在第一个语句中,对标志进行测试以指示是否可以更改version
。
第二和第三个案例陈述将使用针对对象Elem的模式匹配器。这会将元素分解为所有其组成部分。最后一个参数“children @ _ *”将匹配子项列表中的任何内容。或者,更具体地说,Seq [Node]。然后我们用我们提取的部分重建元素,但是将Seq [Node]传递给updateNodes,进行递归步骤。如果我们匹配元素subnode
,那么我们将标志mayChange更改为true
,从而更改版本。
在最后一行中,我们使用node.theSeq从Node生成Seq [Node],并使用(0)获取作为结果返回的Seq [Node]的第一个元素。由于updateNodes本质上是一个map函数(对于... yield被转换为map),我们知道结果只有一个元素。我们传递false
标记,以确保除非version
元素是祖先,否则不会更改subnode
。
有一种略微不同的方式,它更强大,但更冗长和模糊:
def updateVersion(node: Node): Node = {
def updateNodes(ns: Seq[Node], mayChange: Boolean): Seq[Node] =
for(subnode <- ns) yield subnode match {
case Elem(prefix, "version", attribs, scope, Text(_)) if mayChange =>
Elem(prefix, "version", attribs, scope, Text("2"))
case Elem(prefix, "subnode", attribs, scope, children @ _*) =>
Elem(prefix, "subnode", attribs, scope, updateNodes(children, true) : _*)
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(prefix, label, attribs, scope, updateNodes(children, mayChange) : _*)
case other => other // preserve text
}
updateNodes(node.theSeq, false)(0)
}
此版本允许您更改任何“版本”标记,无论其前缀,属性和范围如何。
答案 4 :(得分:3)
Scales Xml提供了“就地”编辑工具。当然这一切都是不可改变的,但这里是Scales的解决方案:
val subnodes = top(xml).\*("subnode"l).\*("version"l)
val folded = foldPositions( subnodes )( p =>
Replace( p.tree ~> "2"))
XPath like语法是Scales签名功能,字符串后面的l
指定它不应该没有命名空间(仅限本地名称)。
foldPositions
遍历生成的元素并对其进行转换,将结果重新组合在一起。
答案 5 :(得分:1)
一种方法是镜片(例如scalaz's)。请参阅http://arosien.github.io/scalaz-base-talk-201208/#slide35以获得非常清晰的演示文稿。
答案 6 :(得分:0)
检查该项目:https://github.com/geirolz/advxml
这是一个基于RuleTransformer(标准scala xml库)和Cats的简单库,旨在简化XML转换和序列化。
答案 7 :(得分:0)
如果 2021 年有任何可怜的人仍然需要使用 Scala 处理 XML,那么这里还有一个我觉得特别好的基于库的解决方案:
import scala.xml._
import jstengel.ezxml.core.SimpleWrapper.ElemWrapper
import jstengel.ezxml.core.XmlPath.\~
val InputXml: Elem = <root>
<subnode>
<version>1</version>
</subnode>
<contents>
<version>1</version>
</contents>
</root>
(InputXml \~ "subnode" \~ "version").transformTarget(_ => <version>2</version>)
有问题的图书馆:https://github.com/JulienSt/ezXML(谢谢你,JulienSt 先生!)
答案 8 :(得分:-2)
我真的不知道如何优雅地完成这项工作。 FWIW,我会采用不同的方法:为您正在处理的信息使用自定义模型类,并为其转换Xml。您可能会发现它是一种更好的处理数据的方式,而且它更加苛刻。
然而,有一种很好的方法可以直接使用Xml,我希望看到它。