使用Scala更改XML命名空间

时间:2009-07-07 20:12:14

标签: xml scala

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

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

2 个答案:

答案 0 :(得分: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]
}

但这会产生意想不到的结果。范围正在添加到所有元素。那是因为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]
}

答案 1 :(得分:0)

这里的小错误。属性也可以具有限定名称。你也需要检查它们。