基于层次结构生成XML文档

时间:2014-12-12 20:06:33

标签: xml scala recursion

我的数据库表格如下:

Category
  id
  parent_id
  path_url
  name
  sort_order

所以一行可能如下:

1  NULL  /cars       cars    1
2  1     /cars/honda honda   2

我在列表中加载了所有类别:

   val categories: List[Category] = .....

现在我想生成这种层次结构的XML表示,如:

<root>
  <category id="1" path_url="/cars" name="cars">
     <category id="5" path_url="/cars/honda" name="honda">
        <category id="12" path_url="/cars/honda/accord" name="accord"></category>
        <category id="3" path_url="/cars/honda/civic" name="civic"></category>
     </category>
     <category id="15" path_url="/cars/ford" name="ford">
          <category id="12" path_url="/cars/ford/escort" name="escort"></category>
     </category>
  </category>
  <category id="23423" path_url="/food" name="food>
  ....
  </category>
  ...
</root>

关键点:

  1. 子项目应按排序(INT)
  2. 排序

1 个答案:

答案 0 :(得分:2)

这是parent pointer tree数据结构。这样的事情可能有所帮助:

import scala.xml._

case class Category(parentId: Int, id: Int, name: String, order: Ordering[Category])

val rootOrder = Ordering.by[Category, String](_.name) //ordering example

def unflatten(c: List[Category])(implicit rootOrder: Ordering[Category]): NodeSeq = <root>{
  unflatten(0, c.groupBy(_.parentId))
}</root>

def unflatten(id: Int, all: Map[Int, List[Category]])(implicit order: Ordering[Category]): NodeSeq = 
  all.get(id).map(_.sorted.map(c => 
     <category id = {c.id.toString} name = {c.name}>{
         unflatten(c.id, all)(c.order) // attaching sub-elements
     }</category>)).getOrElse(Nil) // or nothing

scala> unflatten(List(Category(0, 1, "aaa", rootOrder), Category (1,2, "bbb", rootOrder), Category(0, 3, "aaa", rootOrder)))(rootOrder)
res11: scala.xml.NodeSeq = <root><category id="1" name="aaa"><category id="2" name="bbb"></category></category><category id="3" name="aaa"></category></root>

Ordering应作为隐式参数传递给sorted函数。

它不是尾递归,但需要一个大小等于结构中最大嵌套级别的堆栈 - 因此你在内存中有一个完整的类别列表并且它适合 - 堆栈不会超过它。如果你需要更具可扩展性的东西 - 最好使用你的数据库来解决问题,比如Oracle中的CONECT BY: http://docs.oracle.com/cd/B19306_01/server.102/b14200/queries003.htm

您还可以使用groupBy从其叶子开始遍历树(它将是尾递归),类似于:

import scala.annotation._

type Cat = Category

case class Node(cat: Cat, children: List[Node]) {
   /** can't be added as implicit because of potential stackoverflow */
   val xml: scala.xml.NodeSeq = <category id = {cat.id.toString} name = {cat.name}>{
       implicit val sort = cat.order.on[Node](_.cat)   
       children.sorted.map(_.xml) // attaching sub-elements
   }</category>
}

@tailrec def unflatten(leafs: List[Node], acc: Map[Int, Node]) : List[Node] = {
    val parents = 
       for((id, children) <- leafs.groupBy(_.cat.parentId);
            p <- acc.get(id)) 
       yield id -> Node(p.cat, (p.children ++ children))
    if (parents.nonEmpty) unflatten(parents.values.toList, acc ++ parents) else all.values.toList.filter(_.cat.parentId == 0)
 }

 def unflatten(cats: List[Cat]): List[Node] = {
     val grouped = cats.groupBy(_.id).mapValues(_.map(Node(_, Nil)).head) //Map[Int, Node]
     val byParent = cats.groupBy(_.parentId)
     val leafs = grouped.filter(x => !byParent.contains(x._1)).values.toList //no children      
     unflatten(leafs, grouped)    
 }

 def unflattenXml(cats: List[Cat])(implicit ord: Ordering[Node] = rootOrder.on(_.cat)) = 
    <root>{unflatten(cats).sorted.map(_.xml)}</root>

scala> unflatten(List(Category(0, 1, "aaa", rootOrder), Category (1,2, "bbb", rootOrder), Category(0, 3, "aaa", rootOrder)))
res15: List[Node] = 
List(Node(Category(0,1,aaa,scala.math.Ordering$$anon$9@2412c5e9),
       List(Node(Category(1,2,bbb,scala.math.Ordering$$anon$9@2412c5e9),List()))), 
     Node(Category(0,3,aaa,scala.math.Ordering$$anon$9@2412c5e9),List()))

scala> unflattenXml(List(Category(0, 1, "aaa", rootOrder), Category (1,2, "bbb", rootOrder), Category(0, 3, "aaa", rootOrder)))
res29: scala.xml.Elem = <root><category id="1" name="aaa"><category id="2" name="bbb"></category></category><category id="3" name="aaa"></category></root>

如果您正在寻找消息来源: