Recursive XML in scala

I am trying to parse this document in 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> 

I want to use it to create a skeleton for my 2d animation engine. Each connection must be made in the corresponding facility and all children added to it.

So this part should produce a result similar to this:

 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) 

However, I am having difficulty using xml code. First, I'm not sure I fully understand the xml \\ "joint" syntax, which seems to create NodeSeq containing all the tags.


Main problems:

  • The syntax for understanding the problem with xml in scala, i.e. xml \\ "...", Elem.child?,
  • The problem with getting the attribute of the parent node without getting the attributes from all the children ( xml \\ "@attribute" , creates a concat of all the attributes ..?)
+7
xml scala recursive-query
source share
3 answers

The \\ operator is an XPath-like operator. He will "choose" all descendants with a specific characteristic.

This can be done in two passes:

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

I think I would prefer a recursive solution, but this should be quite workable.

+6
source share

This can be done quite easily using 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 _) } 

Note that lazyRead used so that the reader for Joint can be used recursively.

This blog post talks in more detail about xtract: https://www.lucidchart.com/techblog/2016/07/12/introducing-xtract-a-new-xml-deserialization-library-for-scala/

Disclaimer: I work at Lucid Software and am a major contributor to xtract.

+3
source share

There is also a solution with 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) 

This is Scala 2.8.

I published the full source here . The processing model is indeed consistent, but it works well, since each open tag indicates that we need to create a "Joint" object, it is not necessary to add it to the parent element and save it as a new parent. Closing tags push the parent element if necessary.

0
source share