我正在学习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 次 |
最近记录: |