2009-07-07 5 views
5

J'utilise scala pour charger un fichier XML à partir d'un fichier via la méthode scala.xml.XML.loadFile(). Les documents avec lesquels je travaille ont des espaces de noms déjà définis et je souhaite changer l'espace de noms en quelque chose d'autre en utilisant scala. Par exemple, un document a un xmlns de "http://foo.com/a" avec un préfixe "a" - je voudrais changer l'espace de noms et le préfixe pour le document à "http://foo.com/b" et "b" respectivement.Modification de l'espace de noms XML avec Scala

Cela semble facile et j'ai l'impression qu'il me manque quelque chose d'évident ici. Je n'ai pas de problème pour obtenir l'espace de noms du retour Elem à partir de la méthode référencée loadFile().

Répondre

9

La voici. Puisque NamespaceBinding est imbriqué (chaque ns a un parent, sauf TopScope), nous devons recurse pour corriger cela. En outre, chaque ns a un URI et un préfixe, et nous devons changer les deux.

La fonction ci-dessous ne modifie qu'un seul URI et préfixe particulier, et vérifie tous les espaces de noms, pour voir si le préfixe ou l'URI doit être modifié. Il va changer un préfixe ou un URI indépendant l'un de l'autre, ce qui pourrait ne pas être ce que l'on veut. Ce n'est pas un gros problème, cependant.

Pour le reste, il suffit de faire correspondre le motif sur Elem pour qu'il se répète dans chaque partie du XML. Ah, oui, ça change aussi le préfixe des éléments. Encore une fois, si ce n'est pas ce que l'on veut, c'est facile à changer.

Le code suppose qu'il n'y a pas besoin de se répéter dans les "autres" parties de XML - le reste sera généralement des éléments de texte. En outre, il suppose qu'il n'y a pas d'espace de noms ailleurs. Je ne suis pas expert en XML, donc je peux me tromper sur les deux points. Une fois de plus, il devrait être facile de changer cela - il suffit de suivre le modèle.

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

Cela produit un résultat inattendu, cependant. La portée est ajoutée à tous les éléments. En effet, NamespaceBinding ne définit pas une méthode equals, utilisant ainsi l'égalité de référence. J'ai ouvert un ticket pour cela, 2138, qui a déjà été fermé, donc Scala 2.8 n'aura pas ce problème.

Pendant ce temps, le code suivant fonctionnera correctement. Il conserve un cache d'espaces de noms. Il décompose également NamespaceBinding dans une liste avant de le gérer.

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

Bogue mineur ici. Les attributs peuvent également avoir des noms qualifiés. Vous devez également vérifier ceux-ci.

Questions connexes