2011-01-12 10 views
6

Ich habe eine NodeSeq wie folgt aus:Scala: Ändern eines NodeSeq

<foo>
<baz><bar key1="value1" key2="value2">foobar</bar></baz>
Blah blah blah
<bar key1="value3">barfoo</bar>
</foo>

Ich möchte Attribute ein neues Attribut auf alle bar s hinzuzufügen. Ich mache gerade:

Aber das Problem ist, dass es nur auf 1 Knoten funktioniert. Ich möchte es rekursiv auf allen Knoten arbeiten, und wenn ich die Transformation innerhalb einer for-Schleife aufrufen, kann ich sie nicht durch neue Werte ersetzen, da sie unveränderlich werden. Wie kann ich das lösen?

+1

Siehe auch http://stackoverflow.com/questions/970675/scala-modifying-nested-elements-in-xml – GClaramunt

+0

Meine aktuelle Lösung ist eine modifizierte Version Ihrer Antwort in diesem Thread, sehr geschätzt. – parsa

Antwort

3

ist eine vereinfachte Version Ihrer eigenen Lösung (Daniels Variante der Anpassungslogik):

def updateBar(node: Node): Node = node match { 
    case elem @ Elem(_, "bar", _, _, child @ _*) => elem.asInstanceOf[Elem] % Attribute(None, "newKey", Text("newValue"), Null) copy(child = child map updateBar) 
    case elem @ Elem(_, _, _, _, child @ _*) => elem.asInstanceOf[Elem].copy(child = child map updateBar) 
    case other => other 
} 

Beachten Sie, dass die großen Unterschiede zwischen diesem und Ihrem ursprünglichen Code ist, dass dies ein Knoten aus den Prozessen außerhalb in, wie hier gezeigt, wo ich einige print-Anweisungen hinzugefügt habe, wie in meiner ersten Antwort:

scala> updateBar(<foo><bar>blabla</bar></foo>) 
processing '<foo><bar>blabla</bar></foo>' 
processing '<bar>blabla</bar>' 
processing 'blabla' 
result: 'blabla' 
result: '<bar newKey="newValue">blabla</bar>' 
result: '<foo><bar newKey="newValue">blabla</bar></foo>' 
res1: scala.xml.Node = <foo><bar newKey="newValue">blabla</bar></foo> 

Während Ihr ursprünglicher Code funktioniert vom innen nach außen (vereinfachtes Beispiel):

scala> xf { <a><b><c/></b></a> } 
transforming '<c></c>' 
result: '<c></c>' 
transforming '<b><c></c></b>' 
result: '<b><c></c></b>' 
transforming '<a><b><c></c></b></a>' 
result: '<a><b><c></c></b></a>' 
res4: scala.xml.Node = <a><b><c></c></b></a> 

Es gibt wahrscheinlich Fälle, in denen diese beiden Techniken zu unterschiedlichen Ergebnissen führen wird.

Der andere Unterschied ist, dass der übereinstimmende Code etwas ausführlicher ist: Sie benötigen einen Fall für die tatsächliche Transformation des relevanten Elements und einen Fall für die rekursive Verarbeitung der Unterknoten. Das gezeigte Beispiel könnte allerdings etwas umgestaltet werden.

+0

Sehr schön. Ich würde sagen, das "Inside Out" oder "Outside In" hängt von der Reihenfolge der Fälle ab. – parsa

+2

Nein, weil der RuleTransformer seine Transformation mit jedem innersten Element startet. (So ​​scheint es, ich habe den Quellcode nicht betrachtet, nur das Verhalten.) Wenn Sie die Reihenfolge der Fälle in diesem speziellen Beispiel ändern und z. Setzen Sie den zweiten Fall oben, der "Balken" wird nie übereinstimmen. –

1

Das Bad Boy hat den Job:

def updateVersion(node : Node) : Node = node match { 
     case <foo>{ ch @ _* }</foo> => <foo>{ ch.map(updateVersion)}</foo> 
     case <baz>{ ch @ _* }</baz> => <baz>{ ch.map(updateVersion) }</baz> 
     case Elem(prefix, "bar", attribs, scope, [email protected]_*) => Elem(prefix, "bar", attribs append Attribute(None, "key3", Text("value3"), scala.xml.Null) , scope, content:_*) 
     case other @ _ => other 
     } 
+0

Oh Liebes ... hast du deine eigene Antwort auch aufgefrischt? :) –

+0

Es ist möglicherweise keine gute Vorgehensweise, irrelevante Elemente in die Transformation einzubeziehen. –

+1

@Kevin: Nein, nein! – parsa

0

Versuchen

val rule = new RewriteRule() { 
    override def transform(node: Node): Seq[Node] = { 
     node match { 
      case elem : Elem if elem.label == "bar" => 
       (elem copy (child = this transform child)) % Attribute(None, "newKey", Text("newValue"), scala.xml.Null) 
      case elem : Elem => elem copy (child = this transform child) 
      case other => other 
     } 
    } 
    } 
+0

Das funktioniert nicht. 1) Du musst Elem vor dem Aufruf von copy oder% zu Elem casten. Der Compiler beschwert sich, dass Kopie nicht auf Knoten definiert ist. 2) Kinder sind undefiniert, und was Sie scheinbar zu tun versuchen, ist nicht notwendig, um es rekursiv arbeiten zu lassen. Wenn Sie die Kopie und den mittleren Fall entfernen, funktioniert es identisch mit OPs. –

+0

@Knut Mein Fehler. Das '@', nicht die 'Kinder'. Das "Kinder" -Ding ist Scalas Bibliotheksfehler, den mein Gehirn nicht akzeptiert. :-) –

+0

Du musst "diese transform elem.child" machen. Dies ist jedoch eine Kombination der beiden genannten Techniken (Inside-Out/Outside-In). Es funktioniert (obwohl es den in meiner Antwort beschriebenen Fehler auslöst), aber es transformiert den gesamten Baum zweimal, was nicht notwendig ist. (Soweit ich sehen kann, könnte ich mich irren, es ist schon einmal passiert.) –

1

Ihr Original-Code korrekt zu sein scheint. Das Problem ist nicht, dass es nicht rekursiv funktioniert (es tut), sondern ein seltsames Problem, das auftritt, wenn es genau ein vorhandenes Attribut gibt.

Blick auf die folgenden, die im Wesentlichen identisch mit Ihrem Code ist außer ich einige print-Anweisungen für das Debuggen hinzugefügt haben:

val rule = new RewriteRule() { 
    override def transform(node: Node): Seq[Node] = { 
     println("transforming '" + node + "'") 
     val result = node match { 
      case elem @ Elem(prefix, label @ "bar", attribs, scope, children @ _*) => 
       Elem(prefix, label, attribs append Attribute(None, "newKey", Text("newValue"), Null), scope, children: _*)   
      case other => other 
     } 
     println("result: '" + result + "'") 
     result 
    } 
} 

object xf extends RuleTransformer(rule) 

Jetzt haben wir es testen:

scala> xf { <bar/> } 
transforming '<bar></bar>' 
result: '<bar newKey="newValue"></bar>' 
transforming '<bar></bar>' 
result: '<bar newKey="newValue"></bar>' 
res0: scala.xml.Node = <bar newKey="newValue"></bar> 

Wir sehen, dass für Bei einem Element ohne Attribute führt die Transformation dazu, dass das neue Attribut hinzugefügt wird und das zurückgegebene Ergebnis ebenfalls korrekt ist. (Ich weiß nicht, warum die Transformation zweimal auftritt.)

Wenn es jedoch ein existierendes Attribut:

scala> xf { <bar key1="value1"/> } 
transforming '<bar key1="value1"></bar>' 
result: '<bar key1="value1" newKey="newValue"></bar>' 
res1: scala.xml.Node = <bar key1="value1"></bar> 

Das Ergebnis der Transformation korrekt ist, aber es wird auf den letzten nicht weitergegeben Ergebnis!

Aber wenn es zwei (oder mehr) vorhandene Attribute ist, ist alles in Ordnung:

scala> xf { <bar key1="value1" key2="value2"/> } 
transforming '<bar key1="value1" key2="value2"></bar>' 
result: '<bar key2="value2" key1="value1" newKey="newValue"></bar>' 
transforming '<bar key1="value1" key2="value2"></bar>' 
result: '<bar key2="value2" key1="value1" newKey="newValue"></bar>' 
res2: scala.xml.Node = <bar key2="value2" key1="value1" newKey="newValue"></bar> 

Ich bin versucht zu glauben, dies ein Fehler in der Bibliothek ist.Hier