我正在学习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,另一个是Sequence的Node.它是一个抽象类,所以我们不能直接实例化它.通过添加定义,在这种情况下覆盖其中一个transform方法,我们正在创建它的匿名子类.每个RewriteRule都需要关注一个任务,尽管它可以做很多事.
接下来,class RuleTransformer将参数作为可变数量的参数RewriteRule.它的变换方法需要Node和返回Sequence的Node运用每一个,RewriteRule用来进行实例化.
这两个类派生自BasicTransformer,它定义了一些方法,人们不需要在更高层次上关注自己.它的apply方法调用transform,虽然如此,两者RuleTransformer并RewriteRule可以用与它相关的语法糖.在示例中,前者执行,后者则不执行.
这里我们使用两个级别RuleTransformer,第一个将过滤器应用于更高级别的节点,第二个级别将更改应用于通过过滤器的任何过程.
Elem还使用了提取器,因此无需关注命名空间等细节或是否存在属性.并不是说元素的内容version被完全丢弃并替换为2.如果需要,它也可以匹配.
另请注意,提取器的最后一个参数是_*,而不是_.这意味着这些元素可以有多个孩子.如果忘了*,匹配可能会失败.在示例中,如果没有空格,匹配不会失败.因为空格被转换为Text元素,所以下面的单个空格subnode会使匹配失败.
此代码比所提出的其他建议更大,但它的优点是对XML结构的了解远远少于其他建议.它会更改version下面调用的任何元素- 无论多少级别 - 一个名为的元素subnode,无论名称空间,属性等.
此外......好吧,如果你要进行很多转换,递归模式匹配会变得很快.使用RewriteRule和RuleTransformer,您可以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)
它看起来更紧凑(但实际上是相同的:))
如果你愿意,你也可以摆脱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)
你怎么看?
我已经学到了更多,并在另一个答案中提出了我认为是一个优秀的解决方案.我也修好了这个,因为我注意到我没有考虑到这个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)
此版本允许您更改任何"版本"标记,无论其前缀,属性和范围如何.
| 归档时间: |
|
| 查看次数: |
13806 次 |
| 最近记录: |