2010-04-02 25 views
20

Tengo un archivo XML en el que me gustaría asignar algunos atributos de in con un script. Por ejemplo:Cómo cambiar el atributo en el elemento XML de Scala

<a> 
    <b attr1 = "100" attr2 = "50"/> 
</a> 

pueden tener atributos escalado por un factor de dos:

<a> 
    <b attr1 = "200" attr2 = "100"/> 
</a> 

Esta página tiene una sugerencia para añadir atributos, pero no detalla una forma de asignar un atributo actual con una función (de esta manera haría que muy duro): http://www.scalaclass.com/book/export/html/1

lo que yo he llegado con es crear manualmente el XML (no Scala)-lista enlazada ... algo así como:

// a typical match case for running thru XML elements: 
case Elem(prefix, e, attributes, scope, children @ _*) => { 
var newAttribs = attributes 
for(attr <- newAttribs) attr.key match { 
    case "attr1" => newAttribs = attribs.append(new UnprefixedAttribute("attr1", (attr.value.head.text.toFloat * 2.0f).toString, attr.next)) 
    case "attr2" => newAttribs = attribs.append(new UnprefixedAttribute("attr2", (attr.value.head.text.toFloat * 2.0f).toString, attr.next)) 
    case _ => 
} 
Elem(prefix, e, newAttribs, scope, updateSubNode(children) : _*) // set new attribs and process the child elements 
} 

Es horrible, prolijo y innecesariamente reordena los atributos en la salida, lo que es malo para mi proyecto actual debido a un código de cliente incorrecto. ¿Hay una forma scala-esque de hacer esto?

+13

Estoy sorprendido de lo mal que parece ser la biblioteca en este sentido. –

+0

Muchas buenas respuestas aquí. También vea http://stackoverflow.com/a/23092226/35274 – Philippe

Respuesta

15

Bien, mejor esfuerzo, Scala 2.8. Necesitamos reconstruir atributos, lo que significa que tenemos que descomponerlos correctamente.Vamos a crear una función para la que:

import scala.xml._ 

case class GenAttr(pre: Option[String], 
        key: String, 
        value: Seq[Node], 
        next: MetaData) { 
    def toMetaData = Attribute(pre, key, value, next) 
} 

def decomposeMetaData(m: MetaData): Option[GenAttr] = m match { 
    case Null => None 
    case PrefixedAttribute(pre, key, value, next) => 
    Some(GenAttr(Some(pre), key, value, next)) 
    case UnprefixedAttribute(key, value, next) => 
    Some(GenAttr(None, key, value, next)) 
} 

continuación, vamos a descomponer los atributos encadenados en una secuencia:

def unchainMetaData(m: MetaData): Iterable[GenAttr] = 
    m flatMap (decomposeMetaData) 

En este punto, podemos manipular fácilmente esta lista:

def doubleValues(l: Iterable[GenAttr]) = l map { 
    case g @ GenAttr(_, _, Text(v), _) if v matches "\\d+" => 
    g.copy(value = Text(v.toInt * 2 toString)) 
    case other => other 
} 

Ahora, vuelva a encadenarlo de nuevo:

def chainMetaData(l: Iterable[GenAttr]): MetaData = l match { 
    case Nil => Null 
    case head :: tail => head.copy(next = chainMetaData(tail)).toMetaData 
} 

Ahora, sólo tenemos que crear una función para tomar el cuidado de estas cosas:

def mapMetaData(m: MetaData)(f: GenAttr => GenAttr): MetaData = 
    chainMetaData(unchainMetaData(m).map(f)) 

por lo que podemos utilizar de esta manera:

import scala.xml.transform._ 

val attribs = Set("attr1", "attr2") 
val rr = new RewriteRule { 
    override def transform(n: Node): Seq[Node] = (n match { 
    case e: Elem => 
     e.copy(attributes = mapMetaData(e.attributes) { 
     case g @ GenAttr(_, key, Text(v), _) if attribs contains key => 
      g.copy(value = Text(v.toInt * 2 toString)) 
     case other => other 
     }) 
    case other => other 
    }).toSeq 
} 
val rt = new RuleTransformer(rr) 

que finalmente le permiten hacer la traducción que quería:

rt.transform(<a><b attr1="100" attr2="50"></b></a>) 

Todo esto podría simplificarse si:

  • Attribute realidad define prefijo, clave y el valor, con un prefijo opcional
  • Attribute era una secuencia, no una cadena
  • Attribute había un mapa, mapKeys, mapValues ​​
  • Elem tenido un mapAttribute
+1

El diseño de la biblioteca parece haber tomado algunas decisiones extrañas. Has encontrado algo más versátil que yo, así que ... puntos para eso. – Dave

+1

Probé esto en Scala 2.9.1. Algunas cosas menores: el .toSeq que envuelve el RewriteRule parece ser redundante, ya que un Nodo es un Seq [Node]. También los atributos terminan invertidos. –

+1

Aquí está mi solución: def unchainMetaData (m: MetaData): Iterable [GenAttr] = m.flatMap (decomposeMetaData) .toList.reverse –

8

Así que si yo estuviera en su posición, creo que lo que realmente me gustaría estar escribiendo es algo así como:

case elem: Elem => elem.copy(attributes= 
    for (attr <- elem.attributes) yield attr match { 
    case [email protected]("attr1", _, _) => 
     attr.copy(value=attr.value.text.toInt * 2) 
    case [email protected]("attr2", _, _) => 
     attr.copy(value=attr.value.text.toInt * -1) 
    case other => other 
    } 
) 

Hay dos razones, este won 'T trabajar fuera de la caja:

  1. Attribute no tiene un método útil copy y
  2. Mapeo sobre un MetaData arroja un Iterable[MetaData] en lugar de una por lo que incluso algo tan simple como MetaDataelem.copy(attributes=elem.attributes.map(x => x)) fallará.

Para solucionar el primer problema, vamos a utilizar una implícita añadir un mejor método de copia de Attribute:

implicit def addGoodCopyToAttribute(attr: Attribute) = new { 
    def goodcopy(key: String = attr.key, value: Any = attr.value): Attribute = 
    Attribute(attr.pre, key, Text(value.toString), attr.next) 
} 

No se puede nombrar copy ya un método con ese nombre ya existe, así que simplemente lo llamaremos goodcopy. (Además, si alguna vez crea valores que son Seq[Node] en lugar de cosas que deberían convertirse en cadenas, podría ser un poco más cuidadoso con value, pero para nuestros fines actuales no es necesario.)

Para solucionar el segundo problema, vamos a utilizar una implícita para explicar cómo crear un MetaData de un Iterable[MetaData]:

implicit def iterableToMetaData(items: Iterable[MetaData]): MetaData = { 
    items match { 
    case Nil => Null 
    case head :: tail => head.copy(next=iterableToMetaData(tail)) 
    } 
} 

Entonces se puede escribir código más o menos como lo que he propuesto al principio:

scala> val elem = <b attr1 = "100" attr2 = "50"/> 
elem: scala.xml.Elem = <b attr1="100" attr2="50"></b> 

scala> elem.copy(attributes= 
    | for (attr <- elem.attributes) yield attr match { 
    |  case [email protected]("attr1", _, _) => 
    |  attr.goodcopy(value=attr.value.text.toInt * 2) 
    |  case [email protected]("attr2", _, _) => 
    |  attr.goodcopy(value=attr.value.text.toInt * -1) 
    |  case other => other 
    | } 
    |) 
res1: scala.xml.Elem = <b attr1="200" attr2="-50"></b> 
11

Esta es la forma en que puede hacerlo usando Scala 2.10:

import scala.xml._ 
import scala.xml.transform._ 

val xml1 = <a><b attr1="100" attr2="50"></b></a> 

val rule1 = new RewriteRule { 
    override def transform(n: Node) = n match { 
    case e @ <b>{_*}</b> => e.asInstanceOf[Elem] % 
     Attribute(null, "attr1", "200", 
     Attribute(null, "attr2", "100", Null)) 
    case _ => n 
    } 
} 

val xml2 = new RuleTransformer(rule1).transform(xml1) 
1

Con la ayuda de Scalate's Scuery y sus selectores CSS3 y transformadas:

def modAttr(name: String, fn: Option[String] => Option[String])(node: Node) = node match { 
    case e: Elem => 
    fn(e.attribute(name).map(_.toString)) 
     .map { newVal => e % Attribute(name, Text(newVal), e.attributes.remove(name)) } 
     .getOrElse(e) 
} 

$("#foo > div[bar]")(modAttr("bar", _ => Some("hello"))) 

- Esto transforma por ejemplo este

<div id="foo"><div bar="..."/></div> 

en

<div id="foo"><div bar="hello"/></div>` 
+1

Scuery ahora vive [en github] (http://scalate.github.io/scalate/documentation /scuery.html) – millhouse

+0

Gracias por eso, buenas noticias también (significa que está vivo); actualizado la respuesta. –

+0

... lástima que el rastreador de problemas permanezca en Assembla, ¡podría haberse migrado a Github también! –

0

me pareció más fácil para crear un fragmento de XML por separado y combinar. Este fragmento de código demuestra también la eliminación de los elementos, la adición de elementos adicionales y el uso de variables en un literal XML:

val alt = orig.copy(
    child = orig.child.flatMap { 
    case b: Elem if b.label == "b" => 
     val attr2Value = "100" 
     val x = <x attr1="200" attr2={attr2Value}/> //////////////////// Snippet 
     Some(b.copy(attributes = b.attributes.append(x.attributes))) 

    // Will remove any <remove-me some-attrib="specific value"/> elems 
    case removeMe: Elem if isElem(removeMe, "remove-me", "some-attrib" -> "specific value") => 
     None 

    case keep => Some(keep) 
    } 
    ++ 
     <added-elem name="..."/> 

// Tests whether the given element has the given label 
private def isElem(elem: Elem, desiredLabel: String, attribValue: (String, String)): Boolean = { 
    elem.label == desiredLabel && elem.attribute(attribValue._1).exists(_.text == attribValue._2) 
} 

Para otros recién llegados a Scala XML, también tendrá que añadir un separate Scala module utilizar XML en el código Scala .

Cuestiones relacionadas