在Scala中修改XML而不发生变异?

时间:2012-06-21 02:15:22

标签: xml scala

我正在尝试替换XML片段,并且需要一个累加器。假设我有一个填空的问题存储为XML,如下所示:

val q = <text>The capitals of Bolivia are <blank/> and <blank/>.</text>

在某些时候,我想要将这些空白转换成HTML输入元素,我需要能够区分第一个和第二个,所以我可以检查它们。 (忽略这样一个事实,在这种情况下,两个首都可以按任意顺序出现 - 这是我以后要处理的头疼。)

感谢StackOverflow上一些可爱的答案,我制作了以下解决方案:

import scala.xml._
import scala.xml.transform._

class BlankReplacer extends BasicTransformer {
  var i = 0

  override def transform(n: Node): NodeSeq = n match {
    case <blank/> => {
      i += 1
      <input name={ "blank.%d".format(i) }/>
    }
    case elem: Elem => elem.copy(child=elem.child.flatMap(transform _))
    case _ => n
  }
}

这个工作得相当好。我每次想要开始重新编号时都必须创建一个new BlankReplacer(),但它几乎可以工作:

scala> new BlankReplacer()(q)
res6: scala.xml.Node = <text>The capitals of Bolivia are <input name="blank.1"></input> and <input name="blank.2"></input>.</text>

这是问题所在。有没有一种简单的方法可以避免每次更换<blank/>时我必须做的突变?我所拥有的并不会让我觉得太可怕,但我认为如果我每次将问题转换为HTML时都没有创建BlankReplacer类的新实例,那么这可能会更清晰。我确定有办法把它变成累加器,但我不知道该怎么做。

谢谢! 托德

2 个答案:

答案 0 :(得分:4)

这正是Anti-XML的拉链designed to solve所带来的问题:

  

换句话说,我们从XML树开始,我们深入研究了这一点   使用选择器的树,我们派生了该结果集的新版本   进行一些修改(在我们的例子中,是一个新属性),现在我们   我想回到原来的树上,除了   我们在肠道里做了一些修改。这就是拉链   为...

在你的情况下,你可以这样做:

import com.codecommit.antixml._

def replaceBlanks(el: Elem) = {
  var i = 0
  (el \\ "blank").map { _ =>
    i += 1
    <input name={"blank.%d".format(i)}/>.convert
  }.unselect
}

或者你可以避免var使用this answer中的技巧:

def replaceBlanks(el: Elem) = {
  val blanks = el \\ "blank"

  (0 until blanks.size).foldLeft(blanks) {
    case (z, i) => z.updated(i, z(i).copy(
      name = "input",
      attrs = Attributes("name" -> "blank.%d".format(i + 1)))
    )
  }.unselect
}

现在我们可以将该方法应用于您的元素(将其转换为com.codecommit.antixml.Elem后):

scala> println(replaceBlanks(q.convert))
<text>The capitals of Bolivia are <input name="blank.1"/> and <input name="blank.2"/>.</text>

诀窍是我们可以使用\\挖掘树,就像使用scala.xml一样,但与scala.xml不同,我们可以对生成的“节点集”进行修改(实际上是一个拉链),然后使用unselect将它们放回原来的上下文中。

答案 1 :(得分:1)

Scales Xml提供折叠路径,允许您“修改”树并累积......

import scales.utils._
import ScalesUtils._
import scales.xml._
import ScalesXml._

// the xml example
val q = <("text") /( "The capitals of Bolivia are ", <("blank")," and ",<("blank"),".")

// which elems to fold on?
val p = top(q) \\* "blank"

val f = foldPositions(p, p.size){ // size is used as the starting point for the fold
  case (question, path) =>
(question - 1, Replace( <("input") /@ ("name" -> ("blank."+question) )) )
  }

// f is an either, so we assuming its working here, and ._1 is the accumalator, _.2 the Path
val newTree = f.left.get._2.tree

唯一的怪癖是它以反向文档顺序累积,还有一个非累积版本。当某些变形具有破坏性时,这允许变换的组合(例如,更改子变量然后在另一个变换中删除它只是起作用)。

折叠本身的输入是任何可路径的路径,只要它们在同一个树中,允许您根据需要组合查询。

See here for more details on how to Fold Xml in Scales