Last active
April 6, 2018 17:18
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(_) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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