2009-07-07 167 views
5

我正在使用scala通過scala.xml.XML.loadFile()方法從文件加載XML文件。我正在使用的文檔已經定義了名稱空間,我希望使用scala將名稱空間更改爲其他名稱空間。例如,文檔的xmlns爲「http://foo.com/a」,前綴爲「a」 - 我想將文檔的名稱空間和前綴分別更改爲「http://foo.com/b」和「b」。使用Scala更改XML名稱空間

看起來很簡單,我覺得我錯過了這裏明顯的東西。從參考的loadFile()方法獲得返回Elem的命名空間我沒有問題。

回答

9

這是。由於NamespaceBinding是嵌套的(每個ns都有一個父節點,TopScope除外),所以我們需要遞歸解決該問題。另外,每個ns都有一個URI和一個前綴,我們需要改變它們。

下面的函數只會改變一個特定的URI和前綴,它會檢查所有的名稱空間,看看是否需要改變前綴或URI。它會改變彼此獨立的前綴或URI,這可能不是想要的。不過,這並不是一件大事。

至於其餘的,只是在Elem模式匹配,以遞歸到XML的每個部分。啊,是的,它也改變了元素的前綴。再次,如果這不是我們想要的,那很容易改變。

該代碼假定不需要遞歸到XML的「其他」部分 - 其餘部分通常是文本元素。另外,它假設其他地方沒有命名空間。我不是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] 
} 
0

這裏有一些小問題。屬性也可以具有限定名稱。你也需要檢查這些。