2011-01-12 15 views
6

Tengo un NodeSeq así:Scala: modificar un NodeSeq

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

Quiero añadir un nuevo atributo a atributos todos bar s'. Actualmente estoy haciendo:

val rule = new RewriteRule() { 
    override def transform(node: Node): Seq[Node] = { 
     node match { 
      case Elem(prefix, "bar", attribs, scope, [email protected]_*) => Elem(prefix, "bar", attribs append Attribute(None, "newKey", Text("newValue"), scala.xml.Null) , scope, content:_*) 
      case other => other 
     } 
    } 
    } 

Pero el problema es que solo funciona en 1 nodo. Quiero que trabaje de forma recursiva en todos los nodos, y si invoco la transformación dentro de un bucle for, no puedo reemplazarlos con nuevos valores ya que se vuelven inmutables. ¿Como puedo resolver esto?

+1

Véase también http://stackoverflow.com/questions/970675/scala-modifying-nested-elements-in-xml – GClaramunt

+0

Mi solución real es una versión modificada de su respuesta en ese hilo, muy apreciada. – parsa

Respuesta

3

Aquí es una versión simplificada de su propia solución (utilizando la variante de la lógica de juego de Daniel):

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 
} 

Tenga en cuenta que las principales diferencias entre esto y su código original es que éste procesa los nodos de la afuera en, como se muestra aquí, donde he añadido algunas declaraciones de impresión como en mi primera respuesta:

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> 

Mientras que su código original funciona desde el interior hacia fuera (ejemplo simplificado):

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> 

Probablemente hay casos en los que estas dos técnicas producirá resultados diferentes.

La otra diferencia es que el código coincidente es un poco más detallado: necesita un caso para la transformación real del elemento relevante, y un caso para el procesamiento recursivo de los subnodos. Sin embargo, el ejemplo que se muestra probablemente podría refactorizarse un poco.

+0

Muy bonito. Yo diría que "de adentro hacia afuera" o de "afuera adentro" depende del orden de los "casos". – parsa

+2

No, es porque RuleTransformer comienza su transformación con cada elemento más interno. (O al menos eso parece, no he analizado el código fuente, solo el comportamiento). Si cambia el orden de los casos en este ejemplo particular y p. Ej. coloque la segunda caja en la parte superior, la "barra" nunca coincidirá. –

1

Este chico malo cumplió con su cometido:

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, Dios! ... ¿Has votado tu propia respuesta también? :) –

+0

Puede no ser una buena práctica incluir elementos irrelevantes en la transformación. –

+1

@Kevin: ¡diablos, no! – parsa

0

Trate

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

Esto no funciona. 1) Debes lanzar elem a Elem antes de invocar copy o%. El compilador se queja de que la copia no está definida en el nodo. 2) los niños no están definidos, y lo que parece que estás tratando de hacer no es necesario para que funcione de forma recursiva. Si quita la copia y el medio caso, funciona de manera idéntica a OP. –

+0

@Knut Mi error. El '@', no los 'niños'. Lo de "niños" es el error de biblioteca de Scala, que mi cerebro se niega a aceptar. :-) –

+0

Tienes que hacer "this transform elem.child". Aún así, esto es en realidad una combinación de las dos técnicas mencionadas (dentro-afuera/afuera-adentro). Funciona (aunque desencadena el error descrito en mi respuesta), pero transforma todo el árbol dos veces, lo que no es necesario. (Por lo que puedo ver, podría estar equivocado, ha sucedido antes.) –

1

Su código original parece ser correcta. El problema no es que no funcione recursivamente (sí), sino un problema extraño que ocurre cuando hay exactamente un atributo existente.

vistazo a la siguiente, que es básicamente idéntica a su código, excepto que he añadido algunas declaraciones de impresión para la depuración:

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) 

Ahora probarlo:

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> 

Vemos que para un elemento sin atributos, la transformación da como resultado la adición del nuevo atributo y el resultado devuelto también es correcto. (No sé por qué la transformación se produce dos veces.)

Sin embargo, cuando hay un atributo existente:

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> 

El resultado de la transformación es correcta, pero no se propaga a la final ¡resultado!

Pero cuando hay dos (o más) los atributos existentes, todo está bien:

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> 

estoy tentado a creer que esto es un error en la biblioteca.

Cuestiones relacionadas