2009-07-07 17 views
5

Sto utilizzando scala per caricare un file XML dal file tramite il metodo scala.xml.XML.loadFile(). I documenti con cui sto lavorando hanno spazi dei nomi già definiti e desidero cambiare lo spazio dei nomi in qualcos'altro usando scala. Ad esempio, un documento ha un xmlns di "http://foo.com/a" con un prefisso "a" - Mi piacerebbe cambiare lo spazio dei nomi e il prefisso del documento a "http://foo.com/b" e "b" rispettivamente.Modifica dello spazio dei nomi XML con Scala

Sembra facile e mi sento come se mi mancasse qualcosa di ovvio qui. Non ho problemi a recuperare lo spazio dei nomi dal ritorno Elem dal metodo di riferimento loadFile().

risposta

9

Eccolo. Poiché NamespaceBinding è nidificato (ogni ns ha un genitore, eccetto TopScope), abbiamo bisogno di recurse per risolverlo. Inoltre, ogni ns ha un URI e un prefisso, e dobbiamo cambiare entrambi.

La funzione seguente cambierà solo un particolare URI e prefisso e controllerà tutti gli spazi dei nomi, per vedere se il prefisso o l'URI devono essere modificati. Cambierà un prefisso o un URI indipendenti l'uno dall'altro, il che potrebbe non essere ciò che si desidera. Non è un grosso problema, tuttavia.

Come per il resto, solo la corrispondenza di modello su Elem per recurse in ogni parte dell'XML. Ah, sì, cambia anche il prefisso degli elementi. Di nuovo, se questo non è ciò che si desidera, è facile da cambiare.

Il codice presuppone che non sia necessario ricorrere a "altre" parti di XML - il resto di solito sarà Elementi di testo. Inoltre, presuppone che non ci sia spazio dei nomi altrove. Non sono esperto di XML, quindi potrei sbagliarmi su entrambi i fronti. Ancora una volta, dovrebbe essere facile cambiarlo, basta seguire lo schema.

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

Questo produce un risultato imprevisto, però. L'ambito viene aggiunto a tutti gli elementi. Questo perché NamespaceBinding non definisce un metodo equals, quindi utilizza l'uguaglianza di riferimento. Ho aperto un biglietto per questo, 2138, che è già stato chiuso, quindi Scala 2.8 non avrà questo problema.

Nel frattempo, il codice seguente funzionerà correttamente. Mantiene una cache di spazi dei nomi. Decompone anche NamespaceBinding in una lista prima di gestirla.

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

Minore bug qui. Gli attributi possono anche avere nomi qualificati. È necessario controllare anche quelli.

Problemi correlati