使用Scala更改XML命名空间

tod*_*ddk 5 xml scala

我正在使用scala通过该scala.xml.XML.loadFile()方法从文件加载XML文件.我正在使用的文档已经定义了名称空间,我希望使用scala将名称空间更改为其他名称空间.例如,文档的xmlns为" http://foo.com/a ",前缀为"a" - 我想将文档的名称空间和前缀更改为" http://foo.com/ b "和"b"分别.

看起来很简单,我觉得我在这里遗漏了一些明显的东西.Elem从引用loadFile()方法的返回中获取命名空间没有问题.

Dan*_*ral 9

这里是.由于NamespaceBinding是嵌套的(每个ns都有一个父,但TopScope除外),我们需要递归来修复它.此外,每个ns都有一个URI和一个前缀,我们需要更改它们.

下面的函数只会更改一个特定的URI和前缀,它将检查所有名称空间,以查看前缀或URI是否需要更改.它将改变彼此独立的前缀或URI,这可能不是所需的.但是,解决这个问题并不是什么大问题.

至于其余的,只需在Elem上进行模式匹配即可递归到XML的每个部分.啊,是的,它也改变了元素的前缀.同样,如果那不是想要的东西,那么很容易改变.

代码假定不需要递归到XML的"其他"部分 - 其余部分通常是Text元素.此外,它假设其他地方没有命名空间.我不是XML方面的专家,所以我两个方面都错了.再一次,应该很容易改变 - 只需按照模式.

def changeNS(el: Elem, 
             oldURI: String, newURI: String, 
             oldPrefix: String, newPrefix: String): Elem = {
  def replace(what: String, before: String, after: String): String =
    if (what == before) after else what

  def fixScope(ns: NamespaceBinding): NamespaceBinding =
    if(ns == TopScope)
      TopScope
    else new NamespaceBinding(replace(ns.prefix, oldPrefix, newPrefix),
                              replace(ns.uri, oldURI, newURI),
                              fixScope(ns.parent))

  def fixSeq(ns: Seq[Node]): Seq[Node] = for(node <- ns) yield node match {
    case Elem(prefix, label, attribs, scope, children @ _*) => 
      Elem(replace(prefix, oldPrefix, newPrefix), 
           label, 
           attribs, 
           fixScope(scope), 
           fixSeq(children) : _*)
    case other => other
  }
  fixSeq(el.theSeq)(0).asInstanceOf[Elem]
}
Run Code Online (Sandbox Code Playgroud)

但是,这会产生意想不到的结果.范围正在添加到所有元素.那是因为NamespaceBinding没有定义equals方法,因此使用引用相等.我已经为它开了一张票,2138,已经关闭了,所以Scala 2.8不会有这个问题.

同时,以下代码将正常工作.它保留了名称空间的缓存.它还会在处理之前将NamespaceBinding分解为一个列表.

  def changeNS(el: Elem, 
             oldURI: String, newURI: String, 
             oldPrefix: String, newPrefix: String): Elem = {
  val namespaces = scala.collection.mutable.Map.empty[List[(String, String)],NamespaceBinding]

  def replace(what: String, before: String, after: String): String =
    if (what == before) after else what

  def unfoldNS(ns: NamespaceBinding): List[(String, String)] = ns match {
    case TopScope => Nil
    case _ => (ns.prefix, ns.uri) :: unfoldNS(ns.parent)
    }

  def foldNS(unfoldedNS: List[(String, String)]): NamespaceBinding = unfoldedNS match {
    case knownNS if namespaces.isDefinedAt(knownNS) => namespaces(knownNS)
    case (prefix, uri) :: tail =>
      val newNS = new NamespaceBinding(prefix, uri, foldNS(tail))
      namespaces(unfoldedNS) = newNS
      newNS
    case Nil => TopScope
  }

  def fixScope(ns: NamespaceBinding): NamespaceBinding =
    if(ns == TopScope)
      ns
    else {
      val unfoldedNS = unfoldNS(ns)
      val fixedNS = for((prefix, uri) <- unfoldedNS) 
                    yield (replace(prefix, oldPrefix, newPrefix), replace(uri, oldURI, newURI))

      if(!namespaces.isDefinedAt(unfoldedNS))
        namespaces(unfoldedNS) = ns  // Save for future use

      if(fixedNS == unfoldedNS)
        ns
      else 
        foldNS(fixedNS)
    }

  def fixSeq(ns: Seq[Node]): Seq[Node] = for(node <- ns) yield node match {
    case Elem(prefix, label, attribs, scope, children @ _*) => 
      Elem(replace(prefix, oldPrefix, newPrefix), 
           label, 
           attribs, 
           fixScope(scope), 
           fixSeq(children) : _*)
    case other => other
  }
  fixSeq(el.theSeq)(0).asInstanceOf[Elem]
}
Run Code Online (Sandbox Code Playgroud)