Scala:修改NodeSeq

时间:2011-01-12 08:39:29

标签: xml scala

我有这样的NodeSeq:

<foo>
<baz><bar key1="value1" key2="value2">foobar</bar></baz>
Blah blah blah
<bar key1="value3">barfoo</bar>
</foo>

我想为所有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循环中调用转换,我不能用新值替换它们,因为它们变得不可变。我该如何解决这个问题?

4 个答案:

答案 0 :(得分:3)

以下是您自己的解决方案的简化版本(使用Daniel的匹配逻辑变体):

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>

有可能这两种技术会产生不同的结果。

另一个区别是匹配代码稍微冗长:你需要一个案例来实际转换相关元素,一个案例用于递归处理子节点。但是,显示的示例可能会稍微重构一下。

答案 1 :(得分:1)

这个坏男孩完成了这项工作:

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
       }

答案 2 :(得分:1)

您的原始代码似乎是正确的。问题不在于它不能递归地工作(确实如此),而是在存在一个现有属性时发生的奇怪问题。

请看以下内容,除了我添加了一些用于调试的打印语句之外,它基本上与您的代码完全相同:

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 { <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 { <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>

我很想相信这是图书馆里的一个错误。

答案 3 :(得分:0)

尝试

 val rule = new RewriteRule() {
     override def transform(node: Node): Seq[Node] = {
       node match {
          case elem : Elem if elem.label == "bar"  => 
              (elem copy (child = this transform child)) % Attribute(None, "newKey", Text("newValue"), scala.xml.Null)
          case elem : Elem => elem copy (child = this transform child)
          case other => other
       }
     }
   }