Reputation: 8505
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. Every joint should be made into the according object and all the 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 trouble going through the xml code. For one thing, I am not sure I completely understand the syntax xml \\ "joint"
, which seems to produce a NodeSeq containing all tags.
Main problems:
xml \\ "...", Elem.child?,
xml \\ "@attribute"
, produces a concat of all attributes..?)Upvotes: 7
Views: 1534
Reputation: 7042
This can be done pretty 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 how lazyRead
is used so, that the reader for Joint
can be used recursively.
This blog post, talks about xtract in more detail: https://www.lucidchart.com/techblog/2016/07/12/introducing-xtract-a-new-xml-deserialization-library-for-scala/
Disclaimer: I work for Lucid Software, and am a major contributor to xtract.
Upvotes: 3
Reputation: 41646
There is also a solution with the 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 have posted the complete source here. The processing model is really sequential, but that works well since every open tag will indicate that we need to create a Joint object, optionally add it to the parent and store as the new parent. Close tags pops parent as necessary.
Upvotes: 0
Reputation: 297295
The operator \\
is an XPath-like operator. It will "select" all descendants with a certain characteristic.
This could be done in two passes like this:
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.
Upvotes: 6