2010-01-13 32 views
7

estoy tratando de analizar este documento en Scala:XML recursiva en Scala

<?xml version="1.0"?> 
<model> 
    <joint name="pelvis"> 
      <joint name="lleg"> 
        <joint name="lfoot"/> 
      </joint> 
      <joint name="rleg"> 
        <joint name="rfoot"/> 
      </joint> 
    </joint> 
</model> 

quiero utilizarlo para crear un esqueleto para mi motor 2d-animación. Cada articulación se debe hacer en el objeto correspondiente y todos los niños agregados a él.

Así que esta parte debe producir un resultado similar al siguiente:

j = new Joint("pelvis") 
lleg = new Joint("lleg") 
lfoot = new Joint("lfoot") 
rleg = new Joint("rleg") 
rfoot = new Joint("rfoot") 
lleg.addJoint(lfoot) 
rleg.addJoint(rfoot) 
j.addJoint(lleg) 
j.addJoint(rleg) 

Sin embargo, estoy teniendo problemas para pasar por el código XML. Por un lado, no estoy seguro de entender por completo la sintaxis xml \\ "joint", que parece producir un NodeSeq que contiene todas las etiquetas.


problemas principales:

  1. problema para entender la sintaxis de XML en Scala, es decir xml \\ "...", Elem.child?,
  2. Problema al obtener un atributo de un nodo padre sin obtener los atributos de todos los niños (xml \\ "@attribute", produce una concat de todos los atributos ...?)
+0

Hice algo muy simple que funcionó, lo siento por no publicarlo de inmediato. Volveré con una buena respuesta una vez que vuelva a mi computadora linux :) – Felix

Respuesta

6

El operador \\ es un operador parecido a XPath. Se "seleccionará" a todos los descendientes con una característica determinada.

Esto podría hacerse en dos pasadas de esta manera:

val jointSeq = xml \\ "joint" 
val jointMap = scala.collection.mutable.Map[String, Joint] 

// First pass, create all joints 
for { 
    joint <- jointSeq 
    names <- joint attribute "name" 
    name <- names 
} jointMap(name) = new Joint(name) 

// Second pass, assign children 
for { 
    joint <- jointSeq 
    names <- joint attribute "name" 
    name <- names 
    child <- joint \ "joint" // all direct descendants "joint" tags 
    childNames <- child attribute "name" 
    childName <- childNames 
} jointMap(name).addJoint(jointMap(childName)) 

creo que preferiría una solución recursiva, pero esto debe ser bastante viable.

0

Hay también una solución con el scala.xml.pull.XMLEventReader:

val source = Source.fromPath("...") // or use fromString 

var result: Joint = null 

val xer = new XMLEventReader(source) 
val ancestors = new Stack[Joint]() 

while (xer.hasNext) { 
    xer.next match { 
    case EvElemStart(_, "joint", UnprefixedAttribute(_, name, _), _) => 
     val joint = new Joint(name.toString) 
     if (ancestors.nonEmpty) 
     ancestors.top.addJoint(joint) 
     ancestors.push(joint) 
    case EvElemEnd(_, "joint") => 
     result = ancestors.pop 
    case _ => 
    } 
} 

println(result) 

Esto es Scala 2.8.

He publicado la fuente completa here. El modelo de procesamiento es realmente secuencial, pero funciona bien, ya que cada etiqueta abierta indicará que necesitamos crear un objeto Joint, opcionalmente agregarlo al padre y almacenarlo como el nuevo padre. Las etiquetas de cierre revelan el elemento principal según sea necesario.

3

Esto se puede hacer bastante fácilmente usando xtract.

case class Joint(name: String, joints: Seq[Joint]) 
object Joint { 
    implicit val reader: XmlReader[Joint] = (
    attribute[String]("name") and 
    (__ \ "joint").lazyRead(seq(reader)) 
)(apply _) 
} 

Nota cómo se utiliza lazyRead es así, que el lector de Joint se puede utilizar de forma recursiva.

Esta entrada del blog, habla de Xtract con más detalle: https://www.lucidchart.com/techblog/2016/07/12/introducing-xtract-a-new-xml-deserialization-library-for-scala/

responsabilidad: yo trabajo para Lucid Software, y soy un contribuyente importante a Xtract.