Skip to content

Instantly share code, notes, and snippets.

@ScalaWilliam
Last active April 6, 2018 17:18
Show Gist options
  • Save ScalaWilliam/7527145 to your computer and use it in GitHub Desktop.
Save ScalaWilliam/7527145 to your computer and use it in GitHub Desktop.
Automatic XML printing for case classes in Scala. Tested with Scala 2.10.3.
<Human>
<title>Mr.</title>
<name>Johnny</name>
<coats>
<Coat>
<brand>BOSS</brand>
</Coat>
<Coat>
<brand xsi:nil="true"/>
</Coat>
<Coat>
<brand>Armani</brand>
</Coat>
</coats>
</Human>
<list>
<list>
<item>a</item>
<item>b</item>
</list>
<list>
<item>5</item>
<item>6</item>
</list>
<list>
<item>
<Human>
<title>Mr.</title>
<name>Johnny</name>
<coats>
<Coat>
<brand>BOSS</brand>
</Coat>
<Coat>
<brand xsi:nil="true"/>
</Coat>
<Coat>
<brand>Armani</brand>
</Coat>
</coats>
</Human>
</item>
<item>
<Human>
<title>Mr.</title>
<name>Johnny</name>
<coats>
<Coat>
<brand>BOSS</brand>
</Coat>
<Coat>
<brand xsi:nil="true"/>
</Coat>
<Coat>
<brand>Armani</brand>
</Coat>
</coats>
</Human>
</item>
</list>
</list>
<Human>
<title>Mr.</title>
<name>Johnny</name>
<coats>
<Coat>
<brand>BOSS</brand>
</Coat>
<Coat>
<brand xsi:nil="true"/>
</Coat>
<Coat>
<brand>Armani</brand>
</Coat>
</coats>
</Human>
<list>
<list>
<item>a</item>
<item>b</item>
</list>
<list>
<item>5</item>
<item>6</item>
</list>
<list>
<item>
<Human>
<title>Mr.</title>
<name>Johnny</name>
<coats>
<Coat>
<brand>BOSS</brand>
</Coat>
<Coat>
<brand xsi:nil="true"/>
</Coat>
<Coat>
<brand>Armani</brand>
</Coat>
</coats>
</Human>
</item>
<item>
<Human>
<title>Mr.</title>
<name>Johnny</name>
<coats>
<Coat>
<brand>BOSS</brand>
</Coat>
<Coat>
<brand xsi:nil="true"/>
</Coat>
<Coat>
<brand>Armani</brand>
</Coat>
</coats>
</Human>
</item>
</list>
</list>
package com.v.utils
object TypeMagic {
import scala.tools.scalap.scalax.rules.scalasig.MethodSymbol
import scala.reflect.runtime.universe._
class Memoize1[-T, +R](f: T => R) extends (T => R) {
private[this] val vals = scala.collection.mutable.Map.empty[T, R]
def apply(x: T): R = vals.getOrElseUpdate(x, f(x))
}
def getType[T](clazz: Class[T])(implicit runtimeMirror: Mirror) =
runtimeMirror.classSymbol(clazz).toType
implicit val mirror = runtimeMirror(getClass.getClassLoader)
type productable[T] = Class[T with Product]
def getFieldsL(clazz: productable[_]) = this.synchronized {{
val args = getType(clazz).member(nme.CONSTRUCTOR).asMethod.paramss.head
val argNames = for ( a <- args ) yield a.name.decoded
for {
field <- clazz.getDeclaredFields
name = field.getName
if argNames contains name
} yield (name, field)
}.toMap}
def makeFieldGetterF(fields: Map[String, java.lang.reflect.Field]):(Product => Map[String, Any]) =
(input: Product) => {
for {
(name, field) <- fields.toSeq
madeAccessible = field.setAccessible(true)
value = field.get(input)
}
yield (name, value) }.toMap
def makeFieldGetter[T<:Product](clazz:productable[_]) = makeFieldGetterF(getFieldsL(clazz))
val getFieldGetter = new Memoize1(makeFieldGetter)
def getFields(a: Product) = getFieldGetter(a.getClass.asInstanceOf[productable[_]])(a)
val getFieldsOf = getFields(_)
}
package com.v.utils
object XmlPrinting {
object HeadNode {
implicit def headNode(nodes: Traversable[scala.xml.Node]):scala.xml.Node = nodes.head
}
import TypeMagic.getFieldsOf
implicit class pretty[A](a:A) {
def xmlString: Traversable[scala.xml.Node] = a match {
case list: Traversable[_] =>
<list>{list.collect{
case subList: Traversable[_] => subList.xmlString
case item=>{<item>{item.childXmlString}</item>}
}}</list>
case x:Any => x.childXmlString
}
def childXmlString: Traversable[scala.xml.Node] = a match {
case list: Traversable[_] =>
for ( item <- list; sub <- item.childXmlString )
yield sub
case product: Product =>
<node>{
for {
(title, value) <- getFieldsOf(product)
base = <node/>.copy(label = title)
} yield value match {
case Some(x) => base.copy(child = x.childXmlString.toSeq)
case None => <node xsi:nil="true"/>.copy(label = title)
case x => base.copy(child = x.childXmlString.toSeq)
}
}</node>.copy(label=product.productPrefix)
case null =>
<nil xsi:nil="true"/>
case x => scala.xml.Text(x.toString)
}
}
}
package com.v.utils
/**
*
* Scala XML object printing example.
* 2013 - William Narmontas https://vynar.com/
*
*/
/*
Relevant:
1. http://stackoverflow.com/questions/15067872/get-scala-type-for-a-java-lang-classt-in-scala-2-10
2. http://docs.scala-lang.org/overviews/reflection/overview.html
3. http://www.encodedknowledge.com/2013/01/scala-2-10-reflection-experiments/
4. http://stackoverflow.com/questions/4697534/knowing-if-a-scala-object-is-an-instance-of-case-class
5. http://stackoverflow.com/questions/7525142/how-to-programmatically-determine-if-the-the-class-is-a-case-class-or-a-simple-c
6. http://stackoverflow.com/questions/15730155/why-dont-scala-case-class-fields-reflect-as-public
7. http://stackoverflow.com/questions/2224251/reflection-on-a-scala-case-class
8. http://stackoverflow.com/questions/16079113/scala-2-10-reflection-how-do-i-extract-the-field-values-from-a-case-class
9. http://stackoverflow.com/questions/15718506/scala-how-to-print-case-classes-like-pretty-printed-tree
10. https://github.com/nikita-volkov/sext/blob/master/src/main/scala/sext/package.scala#L118-L173
11. https://github.com/nikita-volkov/sext
12. http://stackoverflow.com/questions/15718506/scala-how-to-print-case-classes-like-pretty-printed-tree
13. https://github.com/ymasory/labeled-tostring
14. http://docs.scala-lang.org/tutorials/tour/case-classes.html
*/
object XmlPrintingTest extends App {
import XmlPrinting._
import HeadNode.headNode
case class Coat(brand: Option[String])
case class Human(title: String, name: String, coats: Seq[Coat])
val human = Human("Mr.", "Johnny", List(Coat(Option("BOSS")), Coat(None), Coat(Option("Armani"))))
val printer = new scala.xml.PrettyPrinter(80, 2)
println(printer.format(human.xmlString))
println(printer.format(List(List("a","b"),List(5,6),List(human,human)).xmlString))
println(printer.format(human.xmlString))
println(printer.format(List(List("a","b"),List(5,6),List(human,human)).xmlString))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment