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