Рекурсивный XML в scala - PullRequest
       28

Рекурсивный XML в scala

7 голосов
/ 13 января 2010

Я пытаюсь разобрать этот документ в 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>

Я хочу использовать его для создания каркаса для моего движка 2d-анимации. Каждый сустав должен быть превращен в соответствующий объект, и все дети должны быть добавлены к нему.

Таким образом, эта часть должна дать результат, подобный следующему:

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)

Однако у меня возникают проблемы с прохождением кода xml. Во-первых, я не уверен, что полностью понимаю синтаксис xml \\ "joint", который, похоже, создает NodeSeq, содержащий все теги.


Основные проблемы:

  1. Проблема понимания синтаксиса с xml в scala, т.е. xml \\ "...", Elem.child?,
  2. Проблема при получении атрибута из родительского узла без получения атрибутов от всех дочерних элементов (xml \\ "@attribute", создает конкатат всех атрибутов ..?)

Ответы [ 3 ]

6 голосов
/ 13 января 2010

Оператор \\ является XPath-подобным оператором.Он "выберет" всех потомков с определенной характеристикой.

Это можно сделать в два этапа, например:

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

Я думаю, что предпочел бы рекурсивное решение, но этовполне работоспособный.

3 голосов
/ 12 июля 2016

Это можно сделать довольно легко, используя 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 _)
}

Обратите внимание, как lazyRead используется так, что считыватель для Joint может использоваться рекурсивно.

В этом блоге более подробно рассказывается об экстракте: https://www.lucidchart.com/techblog/2016/07/12/introducing-xtract-a-new-xml-deserialization-library-for-scala/

Отказ от ответственности: я работаю в Lucid Software и являюсь основным участником xtract.

0 голосов
/ 24 января 2010

Существует также решение с 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)

Это Scala 2.8.

Я разместил полный источник здесь . Модель обработки действительно последовательная, но она работает хорошо, поскольку каждый открытый тег будет указывать, что нам нужно создать объект Joint, при желании добавить его в родительский объект и сохранить в качестве нового родительского объекта. При необходимости закрывайте теги, открывая родительский элемент.

...