2010-01-13 23 views
7

sto cercando di analizzare questo documento in scala:XML ricorsivo a 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> 

voglio usarlo per creare uno scheletro per il mio motore 2d-animazione. Ogni giuntura dovrebbe essere fatta nel secondo oggetto e tutti i bambini aggiunti ad esso.

Quindi questa parte dovrebbe produrre un risultato simile a questo:

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) 

Tuttavia, sto avendo difficoltà passare attraverso il codice XML. Per prima cosa, non sono sicuro di comprendere completamente la sintassi xml \\ "joint", che sembra produrre un NodeSeq contenente tutti i tag.


problemi principali: la comprensione della sintassi

  1. problema con XML in scala, cioè xml \\ "...", Elem.child?,
  2. problema ottenendo un attributo da un nodo padre senza ottenere gli attributi da tutti i bambini (xml \\ "@attribute", produce un concat di tutti gli attributi ..?)
+0

Ho realizzato qualcosa di molto semplice che funzionava, mi dispiace di non averlo pubblicato subito. Tornerò con una bella risposta una volta che avrò di nuovo il mio computer Linux :) – Felix

risposta

6

L'operatore \\ è un operatore XPath. "Selezionerà" tutti i discendenti con una certa caratteristica.

Ciò potrebbe essere fatto in due passaggi come questo:

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)) 

penso io preferirei una soluzione ricorsiva, ma questo dovrebbe essere abbastanza praticabile.

0

v'è anche una soluzione con il 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) 

Questo è Scala 2.8.

Ho pubblicato la fonte completa here. Il modello di elaborazione è davvero sequenziale, ma funziona bene poiché ogni tag aperto indicherà che dobbiamo creare un oggetto Joint, opzionalmente aggiungerlo al genitore e archiviarlo come nuovo genitore. Chiudi tag pop genitore secondo necessità.

3

Questo può essere fatto abbastanza facilmente utilizzando 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 _) 
} 

Si noti come lazyRead è usato in modo, che il lettore per Joint può essere utilizzato in modo ricorsivo.

Questo post del blog, parla di Xtract più in dettaglio: https://www.lucidchart.com/techblog/2016/07/12/introducing-xtract-a-new-xml-deserialization-library-for-scala/

Disclaimer: io lavoro per Lucid Software, e sono un importante contributo per Xtract.