2010-01-13 8 views
7

Je tente d'analyser ce document scala:XML récursifs dans 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> 

Je veux l'utiliser pour créer un squelette pour mon moteur 2d-animation. Chaque articulation devrait être faite dans l'objet correspondant et tous les enfants y seraient ajoutés.

Ainsi, cette partie devrait produire un résultat semblable à ceci:

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) 

Cependant, je ne parviens pas à passer par le code xml. Pour une chose, je ne suis pas sûr de comprendre complètement la syntaxe xml \\ "joint", qui semble produire un NodeSeq contenant toutes les balises.


Principaux problèmes:

  1. syntaxe compréhension du problème avec xml SCALA, à savoir xml \\ "...", Elem.child?,
  2. Problème obtenir un attribut d'un nœud parent sans avoir les attributs de tous les enfants (xml \\ "@attribute", produit une concat de tous les attributs ..?)
+0

J'ai fait quelque chose de très simple qui a fonctionné, désolé de ne pas l'afficher tout de suite. Je reviendrai avec une bonne réponse une fois que je serai sur mon ordinateur Linux à nouveau :) – Felix

Répondre

6

L'opérateur \\ est un opérateur XPath. Il va "sélectionner" tous les descendants avec une certaine caractéristique.

Cela pourrait se faire en deux passes comme ceci:

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

Je pense que je préférerais une solution récursive, mais cela devrait être tout à fait réalisable.

0

Il est également une solution avec le 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) 

Ceci est Scala 2.8.

J'ai publié la source complète here. Le modèle de traitement est vraiment séquentiel, mais cela fonctionne bien puisque chaque balise ouverte indiquera que nous devons créer un objet Joint, éventuellement l'ajouter au parent et le stocker en tant que nouveau parent. Fermer les tags apparaît parent comme nécessaire.

3

Cela peut être fait assez facilement en utilisant 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 _) 
} 

Notez comment lazyRead est utilisé ainsi, que le lecteur pour Joint peut être utilisé de manière récursive.

Ce blog, parle de xtract plus en détail: https://www.lucidchart.com/techblog/2016/07/12/introducing-xtract-a-new-xml-deserialization-library-for-scala/

Disclaimer: Je travaille pour le logiciel Lucid, et je suis un contributeur majeur à xtract.