2009-07-07 27 views
5

Używam scala do ładowania pliku XML z pliku za pomocą metody scala.xml.XML.loadFile(). Dokumenty, z którymi pracuję, mają już zdefiniowane przestrzenie nazw i chcę zmienić przestrzeń nazw na coś innego, używając scala. Na przykład dokument ma xmlns "http://foo.com/a" z prefiksem "a" - Chciałbym zmienić przestrzeń nazw i prefiks dla dokumentu odpowiednio na "http://foo.com/b" i "b".Zmienianie przestrzeni nazw XML za pomocą Scala

Wydaje się łatwe i czuję, że brakuje tu czegoś oczywistego. Nie mam problemu z uzyskaniem przestrzeni nazw od zwracanej Elem z przywołanej metody loadFile().

Odpowiedz

9

Oto ona. Ponieważ NamespaceBinding jest zagnieżdżony (każdy ns ma element nadrzędny, z wyjątkiem TopScope), musimy się wycofać, aby to naprawić. Ponadto każdy ns ma identyfikator URI i prefiks, a my musimy zmienić oba.

Poniższa funkcja zmieni tylko jeden konkretny identyfikator URI i prefiks, a także sprawdzi wszystkie przestrzenie nazw, aby sprawdzić, czy zmiany wymagają prefiks lub URI. Zmieni to przedrostek lub URI niezależnie od siebie, co może nie być tym, co jest potrzebne. Ale to nie jest wielka sprawa.

Co do reszty, wystarczy dopasować wzorzec Elem do rekurencji w każdej części kodu XML. Ach, tak, zmienia również prefiks elementów. Ponownie, jeśli nie jest to pożądane, łatwo je zmienić.

Kod zakłada, że ​​nie ma potrzeby powtarzania się w "innych" częściach XML - reszta będzie zwykle tekstem. Zakłada też, że nie ma gdzie indziej przestrzeni nazw. Nie jestem ekspertem od XML, więc mógłbym się mylić w obu przypadkach. Po raz kolejny zmiana powinna być łatwa - wystarczy wykonać wzór.

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] 
} 

Daje to nieoczekiwany wynik. Zakres jest dodawany do wszystkich elementów. Dzieje się tak, ponieważ NamespaceBinding nie definiuje metody równości, a więc używa równości odniesienia. Otworzyłem dla niego bilet, 2138, który został już zamknięty, więc Scala 2.8 nie będzie miała tego problemu.

Tymczasem poniższy kod będzie działał poprawnie. Zachowuje pamięć podręczną obszarów nazw. Rozkłada on również NamespaceBinding na listę przed jej użyciem.

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

Drobny błąd tutaj. Atrybuty mogą również mieć kwalifikowane nazwy. Musisz również to sprawdzić.

Powiązane problemy