Xml Scala:修改NodeSeq
我有这样一个问题:Xml Scala:修改NodeSeq,xml,scala,Xml,Scala,我有这样一个问题: foobar 废话废话 巴福 我想向所有bars'属性添加一个新属性。我目前正在做: val rule = new RewriteRule() { override def transform(node: Node): Seq[Node] = { node match { case Elem(prefix, "bar", attribs, scope, content@_*) => Elem(prefix, "ba
foobar
废话废话
巴福
我想向所有bar
s'属性添加一个新属性。我目前正在做:
val rule = new RewriteRule() {
override def transform(node: Node): Seq[Node] = {
node match {
case Elem(prefix, "bar", attribs, scope, content@_*) => Elem(prefix, "bar", attribs append Attribute(None, "newKey", Text("newValue"), scala.xml.Null) , scope, content:_*)
case other => other
}
}
}
但问题是它只在1个节点上工作。我希望它在所有节点上递归工作,如果我在
for
循环中调用转换,我不能用新值替换它们,因为它们变得不可变。我怎样才能解决这个问题呢?这个坏男孩完成了任务:
def updateVersion( node : Node ) : Node = node match {
case <foo>{ ch @ _* }</foo> => <foo>{ ch.map(updateVersion )}</foo>
case <baz>{ ch @ _* }</baz> => <baz>{ ch.map(updateVersion ) }</baz>
case Elem(prefix, "bar", attribs, scope, content@_*) => Elem(prefix, "bar", attribs append Attribute(None, "key3", Text("value3"), scala.xml.Null) , scope, content:_*)
case other @ _ => other
}
def updateVersion(节点:节点):节点=节点匹配{
案例{ch@*}=>{ch.map(updateVersion)}
案例{ch@*}=>{ch.map(updateVersion)}
案例要素(前缀“bar”、属性、范围、,content@_*)=>Elem(前缀“bar”、attribs append属性(无、“key3”、文本(“value3”)、scala.xml.Null)、范围、内容:*)
案例其他@\=>其他
}
试试看
您的原始代码似乎是正确的。问题不在于它不能递归地工作(确实如此),而是当只有一个现有属性时会出现一个奇怪的问题 请看以下内容,除了我添加了一些用于调试的打印语句外,它们基本上与您的代码相同:
val rule = new RewriteRule() {
override def transform(node: Node): Seq[Node] = {
println("transforming '" + node + "'")
val result = node match {
case elem @ Elem(prefix, label @ "bar", attribs, scope, children @ _*) =>
Elem(prefix, label, attribs append Attribute(None, "newKey", Text("newValue"), Null), scope, children: _*)
case other => other
}
println("result: '" + result + "'")
result
}
}
object xf extends RuleTransformer(rule)
现在我们测试它:
scala> xf { <bar/> }
transforming '<bar></bar>'
result: '<bar newKey="newValue"></bar>'
transforming '<bar></bar>'
result: '<bar newKey="newValue"></bar>'
res0: scala.xml.Node = <bar newKey="newValue"></bar>
scala>xf{}
转化“
结果:“”
转化“
结果:“”
res0:scala.xml.Node=
我们看到,对于没有属性的元素,转换会导致添加新属性,并且返回的结果也是正确的。(我不知道为什么转换会发生两次。)
但是,当存在现有属性时:
scala> xf { <bar key1="value1"/> }
transforming '<bar key1="value1"></bar>'
result: '<bar key1="value1" newKey="newValue"></bar>'
res1: scala.xml.Node = <bar key1="value1"></bar>
scala>xf{}
转化“
结果:“”
res1:scala.xml.Node=
转换的结果是正确的,但不会传播到最终结果
但当存在两个(或更多)现有属性时,一切正常:
scala> xf { <bar key1="value1" key2="value2"/> }
transforming '<bar key1="value1" key2="value2"></bar>'
result: '<bar key2="value2" key1="value1" newKey="newValue"></bar>'
transforming '<bar key1="value1" key2="value2"></bar>'
result: '<bar key2="value2" key1="value1" newKey="newValue"></bar>'
res2: scala.xml.Node = <bar key2="value2" key1="value1" newKey="newValue"></bar>
scala>xf{}
转化“
结果:“”
转化“
结果:“”
res2:scala.xml.Node=
我很想相信这是库中的一个bug。这里是您自己解决方案的简化版本(使用Daniel的匹配逻辑变体): 请注意,此代码与原始代码之间的主要区别在于,此代码从外到内处理节点,如图所示,我在第一个答案中添加了一些打印语句:
scala> updateBar(<foo><bar>blabla</bar></foo>)
processing '<foo><bar>blabla</bar></foo>'
processing '<bar>blabla</bar>'
processing 'blabla'
result: 'blabla'
result: '<bar newKey="newValue">blabla</bar>'
result: '<foo><bar newKey="newValue">blabla</bar></foo>'
res1: scala.xml.Node = <foo><bar newKey="newValue">blabla</bar></foo>
scala>updateBar(blabla)
处理“blabla”
处理“blabla”
处理“blabla”
结果:“布拉布拉”
结果:“布拉布拉”
结果:“布拉布拉”
res1:scala.xml.Node=blabla
虽然您的原始代码是由内而外工作的(简化示例):
scala>xf{}
转化“
结果:“”
转化“
结果:“”
转化“
结果:“”
res4:scala.xml.Node=
在某些情况下,这两种技术可能会产生不同的结果
另一个区别是匹配的代码稍微详细一些:您需要一种情况用于相关元素的实际转换,另一种情况用于递归处理子节点。不过,所示的示例可能需要进行一些重构。另请参见我的实际解决方案是该线程中答案的修改版本,非常感谢。在转换中包含不相关的元素可能不是一个好做法。@Knut:我想知道是否可以将前两个案例扩展到任何标记,但我失败了:
case Elem(prefix,tag,attribs,scope,content)=>Elem(prefix,tag,attribs,scope,content.map(updateVersion))
给出编译器错误,因为Elem的最后一个参数应该是节点而不是节点seq。也许我应该迭代节点seq。这不起作用。1)在调用copy或%之前,需要将Elem强制转换为Elem。编译器抱怨没有在节点上定义副本。2) 儿童是未定义的,您似乎正在尝试做的事情并不是让它递归工作所必需的。如果你删除副本和中间的案例,它的工作原理与OP的相同。@Knut我的错误。@
,而不是子项
。孩子们的问题是Scala的库错误,我的大脑拒绝接受。:-)你需要做“这个转换元素。孩子”。不过,这实际上是所提到的两种不同技术(由内而外/由外而内)的结合。它是有效的(尽管它触发了我在回答中描述的bug),但它会将整个树转换两次,这是不必要的。(据我所知,我可能错了,这以前发生过。)很好。我会说“由内而外”或“由外而内”取决于“案例”的顺序。不,这是因为RuleTransformer从每个最里面的元素开始转换。(或者看起来,我没有看源代码,只是看行为。)如果在这个特定示例中更改案例的顺序,例如,将第二个案例放在顶部,则“条”将永远不会匹配。
def updateBar(node: Node): Node = node match {
case elem @ Elem(_, "bar", _, _, child @ _*) => elem.asInstanceOf[Elem] % Attribute(None, "newKey", Text("newValue"), Null) copy(child = child map updateBar)
case elem @ Elem(_, _, _, _, child @ _*) => elem.asInstanceOf[Elem].copy(child = child map updateBar)
case other => other
}
scala> updateBar(<foo><bar>blabla</bar></foo>)
processing '<foo><bar>blabla</bar></foo>'
processing '<bar>blabla</bar>'
processing 'blabla'
result: 'blabla'
result: '<bar newKey="newValue">blabla</bar>'
result: '<foo><bar newKey="newValue">blabla</bar></foo>'
res1: scala.xml.Node = <foo><bar newKey="newValue">blabla</bar></foo>
scala> xf { <a><b><c/></b></a> }
transforming '<c></c>'
result: '<c></c>'
transforming '<b><c></c></b>'
result: '<b><c></c></b>'
transforming '<a><b><c></c></b></a>'
result: '<a><b><c></c></b></a>'
res4: scala.xml.Node = <a><b><c></c></b></a>