2010-02-04 13 views
20

Ho un nodo XML che voglio aggiungere i bambini nel corso del tempo:Scala XML di costruzione: Aggiunta di bambini di nodi esistenti

val root: Node = <model></model> 

Ma non riesco a vedere metodi come addChild(), come vorrei piacerebbe scrivere qualcosa sulla falsariga di:

def addToModel() = { 
    root.addChild(<subsection>content</subsection>) 
} 

Così, dopo una singola chiamata a questo metodo l'XML radice sarebbe:

<model><subsection>content</subsection></model> 

L'unica classe che posso vedere che ha la possibilità di aggiungere un nodo è il NodeBuffer. Mi manca qualcosa di fondamentale qui?

+0

Vedo che non hai ha accettato una risposta ancora. Potresti voler fare un passo indietro e vedere se hai persino bisogno di usare il trasformatore di regole o la cerniera o anche "aggiungere un bambino a una nota". Può dipendere dal fatto se il documento XML di destinazione rispecchia sostanzialmente i dati lineari che contengono alcune relazioni genitore/figlio e quanto è grande. Ad esempio, è possibile mantenere un buffer di oggetto e alla fine eseguire un passaggio per convertire il buffer di oggetti in un documento xml. Oppure puoi prima creare l'xml 'children' e fare' {children} 'quando l'elaborazione genitore è terminata. – huynhjl

+0

@huynhjl Grazie per il consiglio. Devo tradurre un foglio di calcolo in xml. In questo modo esclude Rule Transformer mentre opera sulla mappatura da XML a XML. Il foglio di calcolo ha le informazioni sul nome del tag, ma è tutto lineare, quindi ho bisogno di reinserire le informazioni di nidificazione. Il modo in cui lo farei di solito è usando una pila e aggiungendola quando so che il tag corrente è un bambino e si apre quando so che il nodo corrente è completo. Ecco perché volevo aggiungere bambini a un nodo anonimo. –

risposta

28

iniziare bene con questo:

def addChild(n: Node, newChild: Node) = n match { 
    case Elem(prefix, label, attribs, scope, child @ _*) => 
    Elem(prefix, label, attribs, scope, child ++ newChild : _*) 
    case _ => error("Can only add children to elements!") 
} 

Il metodo ++ funziona qui perché è un childSeq[Node], e newChild è un Node, che si estende NodeSeq, che si estende Seq[Node].

Ora, questo non cambia nulla, perché XML in Scala è immutabile. Produrrà un nuovo nodo, con le modifiche richieste. L'unico costo è quello di creare un nuovo oggetto Elem, oltre a creare un nuovo Seq di bambini. I nodi figli, essi stessi, non sono copiati, solo per riferimento, il che non causa problemi perché sono immutabili.

Tuttavia, se si aggiungono i figli a un nodo verso il basso nella gerarchia XML, le cose si complicano. Un modo sarebbe utilizzare le cerniere, come descritto in this blog.

È tuttavia possibile utilizzare scala.xml.transform, con una regola che modificherà un nodo specifico per aggiungere il nuovo figlio. In primo luogo, scrivere una nuova classe trasformatore:

class AddChildrenTo(label: String, newChild: Node) extends RewriteRule { 
    override def transform(n: Node) = n match { 
    case n @ Elem(_, `label`, _, _, _*) => addChild(n, newChild) 
    case other => other 
    } 
} 

Quindi, utilizzare in questo modo:

val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).head 

Su Scala 2.7, sostituire head con first.

Esempio su Scala 2.7:

scala> val oldXML = <root><parent/></root> 
oldXML: scala.xml.Elem = <root><parent></parent></root> 

scala> val parentName = "parent" 
parentName: java.lang.String = parent 

scala> val newChild = <child/> 
newChild: scala.xml.Elem = <child></child> 

scala>  val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).first 
newXML: scala.xml.Node = <root><parent><child></child></parent></root> 

si potrebbe rendere più complessa per ottenere l'elemento giusto, se solo il genitore non è sufficiente. Tuttavia, se è necessario aggiungere il figlio a un genitore con un nome comune di un indice specifico, probabilmente è necessario seguire la procedura delle cerniere.

Ad esempio, se si dispone di <books><book/><book/></books> e si desidera aggiungere <author/> al secondo, ciò sarebbe difficile da fare con il trasformatore di regole. Avresti bisogno di un RewriteRule contro books, che sarebbe poi ottenere il suo child (che in realtà avrebbe dovuto essere chiamato children), trovare il n ° book in loro, aggiungere il nuovo bambino a quello, e poi ricomporre i bambini e costruire il nuovo nodo. È fattibile, ma le cerniere potrebbero essere più facili se devi farlo troppo.

+0

Grazie Daniel, una risposta molto completa (come al solito). La corrispondenza del modello Elem capisco. Ma dovrò capire l'esempio di RuleTransformer. –

+1

@Brian 'RuleTransformer' applica il suo elenco di' RewriteRules' in modo ricorsivo su tutti i nodi di un XML. Un 'RewriteRules' riceve un' Node' o 'Seq [Node]', e ne produce una versione modificata, o restituisce l'originale. L'ho usato spesso per rispondere alle domande XML su Scala, quindi puoi cercare me, Scala e XML per vedere ulteriori esempi. –

+0

Lo farò, grazie per i suggerimenti. –

2

Nel solito modo Scala, tutte le istanze di Node, Elem, ecc. Sono immutabili. È possibile lavorare il contrario:

scala> val child = <child>foo</child> 
    child: scala.xml.Elem = <child>foo</child> 

    scala> val root = <root>{child}</root> 
    root: scala.xml.Elem = <root><child>foo</child></root> 

Vedi http://sites.google.com/site/burakemir/scalaxbook.docbk.html per ulteriori informazioni.

+1

Questo è vero, ma, non conosco tutti i bambini prima del tempo. Devo aggiungere i bambini man mano che le informazioni diventano disponibili. Per la precisione sto convertendo un foglio di calcolo lineare in XML. Non so dove finisce un tag finché non finisco i bambini –

2

Dal XML sono immutable, è necessario crearne uno nuovo ogni volta che si desidera aggiungere un nodo, è possibile utilizzare Pattern matching aggiungere la nuovo nodo:

var root: Node = <model></model> 
    def addToModel(newNode: Node) = root match { 
     //match all the node from your model 
     // and make a new one, appending old nodes and the new one 
     case <model>{[email protected]_*}</model> => root = <model>{oldNodes}{newNode}</model> 
    } 
    addToModel(<subsection>content</subsection>) 
+0

Questo è molto più verboso di quanto mi aspettassi :) Ma ci andrò. Suppongo di poter usare la classe Elem per eseguire una corrispondenza generica in modo da poter aggiungere elementi a qualsiasi nodo. Come il nodo che voglio aggiungere a non è sempre chiamato "modello"? –

8

a Scala nodi XML sono immutabili, ma può fare questo:

var root = <model/> 

def addToModel(child:Node) = { 
    root = root match { 
    case <model>{[email protected] _*}</model> => <model>{children ++ child}</model> 
    case other => other 
    } 
} 

addToModel(<subsection>content</subsection>) 

riscrive una nuova xml, facendo una copia di quello vecchio e aggiungendo il nodo come un bambino.

Modifica: Brian ha fornito più informazioni e ho pensato che fosse diverso da abbinare.

Per aggiungere un bambino ad un nodo arbitrario 2.8 si può fare:

def add(n:Node,c:Node):Node = n match { case e:Elem => e.copy(child=e.child++c) } 

che restituirà una nuova copia del nodo padre con il bambino aggiunto. Supponendo che hai accatastati vostri figli nodi in quanto si sono resi disponibili:

scala> val stack = new Stack[Node]() 
stack: scala.collection.mutable.Stack[scala.xml.Node] = Stack() 

volta che hai capito il gioco è fatto con i bambini il recupero, è possibile effettuare una chiamata sul genitore per aggiungere tutti i bambini nello stack come questo :

stack.foldRight(<parent/>:Node){(c:Node,n:Node) => add(n,c)} 

non ho idea circa l'implicazione prestazioni di utilizzare Stack e foldRight quindi a seconda di quanti figli hai impilati, potrebbe essere necessario armeggiare ... quindi potrebbe essere necessario chiamare stack.clear troppo. Speriamo che questo si prenda cura della natura immutabile di Node ma anche il vostro processo man mano che ne avete bisogno.

+0

Non sono sicuro che '++' sia applicabile qui, dato che stai aggiungendo un singolo nodo ad una sequenza di essi. –

+0

Questo funziona comunque! Deve essere il disturbo di personalità multipla che Node ha anche con un NodeSeq? – huynhjl

+0

se questo viene utilizzato in un ciclo, l'istanza dell'oggetto temporaneo non sarà molto performante e utilizzerà la memoria non necessaria. Pensa a concatenazioni di stringhe immutabili in un ciclo for. Se stai creando un documento di grandi dimensioni in questo modo e svolgi questo tipo di attività con molti thread simultanei, questo sembra un divertente problema di produzione in fase di elaborazione :) –

2

Sono d'accordo sul fatto che devi lavorare con XML "al contrario". Tenere presente che non è necessario disporre dell'intero documento XML quando le informazioni diventano disponibili, è sufficiente comporre l'XML quando l'applicazione deve leggerlo.

Mantenete il vostro stato di sottosezione come volete, quando avete bisogno dell'XML, avvolgetelo tutto insieme.

val subsections : List[Elem] 

    def wrapInModel(f : => Elem) = { 
    <model>{f}</model> 
    } 

    wrapInModel(subsections) 

o

def wrapInModel(f : => Elem) = { 
    <model>{f}</model> 
    } 
    wrapInModel(<subsection>content</subsection>) 
1

Scales Xml consente una semplice in atto modifiche tramite ripiegatura XPaths, aggiungendo nei bambini ad un particolare nodo secondario adatta a destra in questo approccio.

Vedere In-Place Transformations per ulteriori dettagli.

6

Dal Scala 2.10.0 il costruttore di Elem esempio è cambiato, se si desidera utilizzare una soluzione ingenua scritto da @ Daniel C. Sobral, dovrebbe essere:

xmlSrc match { 
    case xml.Elem(prefix, label, attribs, scope, child @ _*) => 
     xml.Elem(prefix, label, attribs, scope, child.isEmpty, child ++ ballot : _*) 
    case _ => throw new RuntimeException 
} 

Per me, funziona molto bene.

0

implemento il mio metodo 'appendChild' nel modo seguente:

def appendChild(elem: Node, child: Node, names: String) = { 
    appendChild(elem, child, names.split("/")) 
    } 

    private def appendChild(elem: Node, child: Node, names: Array[String]) = { 
    var seq = elem.child.diff(elem \ names.head) 
    if (names.length == 1) 
     for (re <- elem \ names.head) 
     seq = seq ++ re.asInstanceOf[Elem].copy(child = re.child ++ child) 
    else 
     for (subElem <- elem \ names.head) 
     seq = seq ++ appendChild(subElem, child, names.tail) 
    elem.asInstanceOf[Elem].copy(child = seq) 
    } 

Il metodo aggiunge ai bambini di nodi in modo ricorsivo. Nell'istruzione 'if' chiama semplicemente il metodo 'copia' della classe Elem per produrre nuove istanze di bambini affetti (quelli potrebbero essere plurali). Quindi in 'else' le chiamate ricorsive al metodo 'appendChild' verificano che il risultante XML venga ricostruito. Prima di "if-else" ci sono delle sequenze costruite da bambini non colpiti. Alla fine, dobbiamo copiare questa sequenza sull'elemento di origine.

val baz = <a><z x="1"/><b><z x="2"/><c><z x="3"/></c><z x="4"/></b></a> 
println("Before: \n" + XmlPrettyPrinter.format(baz.toString())) 

val res = appendChild(baz, <y x="5"/>, "b/c/z") 
println("After: \n" + XmlPrettyPrinter.format(res.toString())) 

Risultati:

Before: 
<a> 
    <z x="1"/> 
    <b> 
    <z x="2"/> 
    <c> 
     <z x="3"/> 
    </c> 
    <z x="4"/> 
    </b> 
</a> 

After: 
<a> 
    <z x="1"/> 
    <b> 
    <z x="2"/> 
    <z x="4"/> 
    <c> 
     <z x="3"> 
     <y x="5"/> 
     </z> 
    </c> 
    </b> 
</a> 
1

vostra definizione di root è in realtà un oggetto Elem, una sottoclasse di nodo, quindi se si lascia cadere la digitazione Nodo inutili (che nasconde la sua attuazione) si potrebbe effettivamente fare un + + su di esso poiché la classe Elem ha questo metodo.

val root = <model/> 
val myChild = <myChild/> 
root.copy(child = root.child ++ myChild) 

Scala ev:

root: scala.xml.Elem = <model/> 
myChild: scala.xml.Elem = <mychild/> 
res2: scala.xml.Elem = <model><mychild/></model> 

Dal momento che ogni Elem e ogni nodo è un NodeSeq è possibile aggiungere questi abbastanza efficace, anche se ciò che si desidera aggiungere i è una sequenza sconosciuta:

val root = <model/> 
//some node sequence of unknown subtype or structure 
val children: scala.xml.NodeSeq = <node1><node2/></node1><node3/> 
root.copy(child = root.child ++ children) 

scala ev:

root: scala.xml.Elem = <model/> 
children: scala.xml.NodeSeq = NodeSeq(<node1><node2/></node1>, <node3/>) 
res6: scala.xml.Elem = <model><node1><node2/></node1><node3/></model> 
Problemi correlati