代码之家  ›  专栏  ›  技术社区  ›  ed.

scala-修改XML中的嵌套元素

  •  36
  • ed.  · 技术社区  · 15 年前

    我正在学习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
         }
       }
     }
    

    写这个函数有更简洁的方法吗?

    7 回复  |  直到 11 年前
        1
  •  11
  •   GClaramunt    15 年前

    我认为最初的逻辑是好的。 这是同一个代码(我敢说吗?)更像鳞片的味道:

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

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

    1. 我摆脱了所有不必要的 括号
    2. 如果需要一个括号,它从 同一条线
    3. updateElements只定义了一个var 把它还给我,所以我把它处理掉了。 直接返回结果

    如果需要,也可以去掉updateElements。您希望将updateVersion应用于序列的所有元素。那就是 map method . 有了它,你就可以重写这行了

    case <subnode>{ ch @ _* }</subnode> => <subnode>{ updateElements( ch ) }</subnode>
    

    具有

    case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion (_)) }</subnode>
    

    由于更新版本只接受一个参数,我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
           }
    

    你怎么认为?

        2
  •  54
  •   Daniel C. Sobral    15 年前

    一直以来,没有人给出最恰当的答案!不过,现在我已经知道了,下面是我对它的新看法:

    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 属于 结点 . 它是一个抽象类,因此我们不能直接实例化它。通过添加定义,在这种情况下,重写 转型 方法,我们正在创建它的匿名子类。每个RewriteRule都需要关注一个任务,尽管它可以完成很多任务。

    下一堂课 RuleTransformer 将可变数量的 改写词 . 它的转换方法需要 结点 并返回 序列 属于 结点 ,通过应用 改写词 用来实例化它。

    这两个类都源自 BasicTransformer 它定义了一些不需要在更高层次上关注自己的方法。它是 apply 方法调用 转型 不过,两者都是 规则变压器 改写词 可以使用与之相关的句法糖。在这个例子中,前者是这样,而后者则不是。

    这里我们使用两个级别 规则变压器 ,因为第一个将过滤器应用于更高级别的节点,第二个将更改应用于通过过滤器的任何内容。

    提取器 Elem 也可以使用,这样就不需要关心名称空间之类的细节或者是否有属性。不是元素的内容 version 完全丢弃并替换为 2 . 如果需要,它也可以与之匹配。

    还要注意提取器的最后一个参数是 _* 而不是 _ . 这意味着这些元素可以有多个子元素。如果你忘记了 * ,匹配可能失败。在这个例子中,如果没有空格,匹配就不会失败。因为空白被翻译成 Text 元素,下面的单个空白 subnode 以防比赛失败。

    这段代码比其他的建议要大,但是它的优点是对XML结构的了解要比其他的少得多。它会更改调用的任何元素 版本 这低于——不管有多少个级别——一个名为 子节点 ,无论名称空间、属性等。

    而且…好吧,如果您有许多转换要做,递归模式匹配会很快变得不易实现。使用 改写词 规则变压器 ,您可以有效地替换 xslt 带有scala代码的文件。

        3
  •  13
  •   David Pollak    14 年前

    您可以使用Lift的CSS选择器转换并写入:

    "subnode" #> ("version *" #> 2)
    

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

        4
  •  6
  •   Daniel C. Sobral    15 年前

    从那以后,我学到了更多,并在另一个答案中提出了我认为是一个更好的解决方案。我还修复了这个,因为我注意到我没有解释 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 是否可以更改。

    第二和第三个case语句将对对象elem使用模式匹配器。这将一个元素分解为 全部的 它的组成部分。最后一个参数“children@\u*”将使children与任何内容的列表相匹配。或者更具体地说,是seq[节点]。然后我们用提取的部分重新构造元素,但将seq[node]传递给updateNodes,执行递归步骤。如果我们与元素匹配 子节点 ,然后我们把旗子改成 true ,启用版本更改。

    在最后一行中,我们使用node.thesq从node生成一个seq[节点],并且(0)得到作为结果返回的seq[节点]的第一个元素。因为updateNodes本质上是一个映射函数(用于…将yield转换为map),我们知道结果只有一个元素。我们通过了 false 标记以确保否 版本 将被更改,除非 子节点 元素是祖先。

    有一种稍有不同的方法,它更强大,但更冗长和晦涩:

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

    这个版本允许您更改任何“版本”标记,不管它是前缀、属性和作用域。

        5
  •  3
  •   Chris    13 年前

    Scales Xml 提供用于“就地”编辑的工具。当然,这一切都是不变的,但下面是按比例计算的解决方案:

    val subnodes = top(xml).\*("subnode"l).\*("version"l)
    val folded = foldPositions( subnodes )( p => 
      Replace( p.tree ~> "2"))
    

    类似xpath的语法是一个scales签名特性, l 在字符串指定之后,它应该没有名称空间(仅限本地名称)。

    foldPositions 迭代生成的元素并转换它们,将结果连接在一起。

        6
  •  1
  •   nafg    11 年前

    一种方法是使用透镜(例如scalaz)。见 http://arosien.github.io/scalaz-base-talk-201208/#slide35 为了一个非常清晰的陈述。

        7
  •  -2
  •   Germán    15 年前

    我真的不知道怎样才能做到优雅。fwiw,我将采用另一种方法:对您正在处理的信息使用自定义模型类,并对其进行XML和XML之间的转换。你可能会发现它是处理数据的更好方法,而且它更简洁。

    不过,有一种直接使用XML的好方法,我想看看。