【问题标题】:Scala: modify a NodeSeqScala:修改 NodeSeq
【发布时间】:2011-01-13 23:12:21
【问题描述】:

我有一个这样的 NodeSeq:

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

我想为所有bars 的属性添加一个新属性。我目前正在做:

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

【问题讨论】:

标签: xml scala


【解决方案1】:

这是您自己的解决方案的简化版本(使用 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>

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

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

【讨论】:

  • 非常好。我会说“由内而外”或“由内而外”取决于“案例”的顺序。
  • 不,这是因为 RuleTransformer 从每个最里面的元素开始转换。 (或者看起来,我没有查看源代码,只是查看行为。)如果您更改此特定示例中的案例顺序,例如将第二种情况放在顶部,“条”将永远不会匹配。
【解决方案2】:

这个坏小子做了这个工作:

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
       }

【讨论】:

  • 在转换中包含不相关的元素可能不是一个好习惯。
  • @Knut:我想知道是否可以将前两种情况扩展到任何标签,但我失败了:case Elem(prefix, tag, attribs, scope, content) =&gt; Elem(prefix, tag, attribs, scope, content.map(updateVersion )) 给出编译器错误,因为 Elem 的最后一个参数应该是 Node 而不是 NodeSeq。也许我应该迭代 NodeSeq。
【解决方案3】:

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

看看下面的代码,除了我添加了一些用于调试的打印语句之外,它与您的代码基本相同:

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>

我很想相信这是库中的错误。

【讨论】:

    【解决方案4】:

    试试

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

    【讨论】:

    • 这不起作用。 1) 在调用 copy 或 % 之前,您需要将 elem 转换为 Elem。编译器抱怨没有在节点上定义副本。 2)孩子是未定义的,你似乎试图做的事情并不是让它递归工作的必要条件。如果您删除副本和中间案例,它的工作原理与 OP 相同。
    • @Knut 我的错。 @,而不是 childrenchildren 是 Scala 的库错误,我的大脑拒绝接受。 :-)
    • 你需要做“这个变换 elem.child”。尽管如此,这实际上是提到的两种不同技术(由内而外/由外而内)的组合。它可以工作(尽管它会触发我的回答中描述的错误),但它会将整个树转换两次,这不是必需的。 (据我所知,我可能是错的,它以前发生过。)
    猜你喜欢
    • 2012-04-28
    • 2013-03-07
    • 2023-03-26
    • 2013-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-29
    相关资源
    最近更新 更多