Scala - 修改xml中的嵌套元素

ed.*_*ed. 37 xml scala

我正在学习scala,我正在寻找更新某个xml中的嵌套节点.我有一些工作,但我想知道它是否是最优雅的方式.

我有一些xml:

val InputXml : Node =
<root>
    <subnode>
        <version>1</version>
    </subnode>
    <contents>
        <version>1</version>
    </contents>
</root>
Run Code Online (Sandbox Code Playgroud)

我想更新版本的节点的子节点,而不是一个在内容.

这是我的功能:

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
     }
   }
 }
Run Code Online (Sandbox Code Playgroud)

有没有更简洁的方式来编写这个函数?

Dan*_*ral 56

这一次,没有人真正给出了最合适的答案!不过,现在我已经了解了它,这是我对它的新看法:

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)
Run Code Online (Sandbox Code Playgroud)

现在,进行一些解释.这门课RewriteRule是抽象的.它定义了两种方法,都称为transform.其中一人带一个Node,另一个是SequenceNode.它是一个抽象类,所以我们不能直接实例化它.通过添加定义,在这种情况下覆盖其中一个transform方法,我们正在创建它的匿名子类.每个RewriteRule都需要关注一个任务,尽管它可以做很多事.

接下来,class RuleTransformer将参数作为可变数量的参数RewriteRule.它的变换方法需要Node和返回SequenceNode运用每一个,RewriteRule用来进行实例化.

这两个类派生自BasicTransformer,它定义了一些方法,人们不需要在更高层次上关注自己.它的apply方法调用transform,虽然如此,两者RuleTransformerRewriteRule可以用与它相关的语法糖.在示例中,前者执行,后者则不执行.

这里我们使用两个级别RuleTransformer,第一个将过滤器应用于更高级别的节点,第二个级别将更改应用于通过过滤器的任何过程.

Elem还使用了提取器,因此无需关注命名空间等细节或是否存在属性.并不是说元素的内容version被完全丢弃并替换为2.如果需要,它也可以匹配.

另请注意,提取器的最后一个参数是_*,而不是_.这意味着这些元素可以有多个孩子.如果忘了*,匹配可能会失败.在示例中,如果没有空格,匹配不会失败.因为空格被转换为Text元素,所以下面的单个空格subnode会使匹配失败.

此代码比所提出的其他建议更大,但它的优点是对XML结构的了解远远少于其他建议.它会更改version下面调用的任何元素- 无论多少级别 - 一个名为的元素subnode,无论名称空间,属性等.

此外......好吧,如果你要进行很多转换,递归模式匹配会变得很快.使用RewriteRuleRuleTransformer,您可以xslt使用Scala代码有效地替换文件.


Dav*_*lak 13

您可以使用Lift的CSS Selector Transforms并编写:

"subnode" #> ("version *" #> 2)
Run Code Online (Sandbox Code Playgroud)

http://stable.simply.liftweb.net/#sec:CSS-Selector-Transforms


GCl*_*unt 12

我认为最初的逻辑是好的.这个代码(我敢说吗?)是一个更加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
   }
 }
Run Code Online (Sandbox Code Playgroud)

它看起来更紧凑(但实际上是相同的:))

  1. 我摆脱了所有不必要的括号
  2. 如果需要括号,它将从同一行开始
  3. updateElements只定义一个var并返回它,所以我摆脱了它并直接返回结果

如果你愿意,你也可以摆脱updateElements.您希望将updateVersion应用于序列的所有元素.这是地图方法.有了它,你可以重写这一行

case <subnode>{ ch @ _* }</subnode> => <subnode>{ updateElements( ch ) }</subnode>
Run Code Online (Sandbox Code Playgroud)

case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion (_)) }</subnode>
Run Code Online (Sandbox Code Playgroud)

由于更新版本只需要1个参数,我99%肯定你可以省略它并写:

case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion) }</subnode>
Run Code Online (Sandbox Code Playgroud)

最后:

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
       }
Run Code Online (Sandbox Code Playgroud)

你怎么看?


Dan*_*ral 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)
}
Run Code Online (Sandbox Code Playgroud)

现在,解释.第一个和最后一个案例陈述应该很明显.最后一个用于捕获非元素的XML部分.或者,换句话说,文本.但是,请注意第一个语句中对标志的测试,以指示是否version可以更改.

第二个和第三个case语句将使用对象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)
}
Run Code Online (Sandbox Code Playgroud)

此版本允许您更改任何"版本"标记,无论其前缀,属性和范围如何.