Reputation: 11245
Scala's support for XML literals is fantastic for generating type safe XHTML markup; however, there's an annoying gotcha in that you must append literals together with the ++
operator when non-xml-literal code is included in a given block.
For example, this blows up:
import scala.xml._
def getNode() = <div>foo</div>
val node: NodeSeq =
<div>bar</div>
<div>baz</div>{
getNode()
}
error: type mismatch;
found : scala.xml.Elem
required: Int
getNode()
and while the below compiles, notice how you have to ++
after the non-xml-literal getNode()
call, otherwise only the tail Elem
is generated.
import scala.xml._
def getNode() = <div>foo</div>
val node: NodeSeq =
<div>bar</div>
<div>baz</div> ++ {
getNode()
}
<div>lonely node</div>
node: scala.xml.NodeSeq = NodeSeq(<div>bar</div>, <div>baz</div>, <div>foo</div>)
res6: scala.xml.Elem = <div>lonely node</div>
So, is there a way to implicitly chain XML literals interspersed with XML returning Scala code by default? As it stands you get a false sense of security when working with XML literals since the above example compiles but is effectively broken.
One way to explicitly force appending of literals would be to do something like:
import scala.xml._
def getNode() = <div>foo</div>
def nodify(elems: Elem*): NodeSeq = elems
nodify(
<div>bar</div>,
<div>baz</div>,
getNode(),
<div>not lonely node</div>
)
res8: scala.xml.NodeSeq = NodeSeq(<div>bar</div>, <div>baz</div>, <div>foo</div>, <div>not lonely node</div>)
but that's not so nice in that you have to wrap the XML literal blocks in a method call. At least it force appends, won't compile without.
Would love to have a clean XHTML markup DSL, please provide the secret sauce if you've got it! (or clue me in if I'm missing something blindingly obvious).
Thanks
Upvotes: 2
Views: 1056
Reputation: 11245
So, after hacking around a bit I've discovered that the explicit approach using varargs is the way to go.
Check it out, for generating html markup you're usually doing so within a layout or partial, something like:
object Crud extends Layout{
def apply[T <: Seq[Node]](title: String, args: (Symbol,String)*)(body: T*) {
header(...)
<body>
{body}
</body>
footer(...)
}
}
and then you want to implicitly chain the markup that comprises the body of above layout. As per above answers there's no elegant way to pull off implicit chaining. With var args however, you can have your cake and eat it too. Before implementing the varargs approach a form to create Foos looked like:
Crud(title, ...)(
<h1>{title}</h1>
<div> </div> ++
withForm(routes.Foo.save)(
fields(form) ++
crud.submit("Create")
) ++
css("foo") ++
css("bar")
)
with zero compile time guarantee that a ++ wasn't forgotten, totally error prone. With varargs as per above Crud apply signature foo form create looks like this:
Crud(title, ...)(
<h1>{title}</h1>
<div> </div>,
withForm(routes.Foo.save)(
fields(form),
crud.submit("Create")
),
css("foo"),
css("bar")
)
and if you omit a comma you get a compile time error, exactly what we want. On the "plus" side you can still use ++ if desired since Seq[Node] lies at the top of Scala XML type hierarchy, so Elem, NodeSeq, etc. doesn't matter, whatever it is it just has to derive from Seq[Node] and you're good to go, woohoo ;-)
Upvotes: 0
Reputation: 39587
As a refresher (for me), your braces are not an embedded Scala expression, which has to be content inside a tag.
Your error message says you're calling NodeBuffer.apply{f}
, so that's the enhancement below.
When you have two Elements in a row, you get a NodeBuffer
, and the compiler builds them with &+
.
scala> import xml._
import xml._
scala> def f = <div>foo</div>
f: scala.xml.Elem
scala> implicit class `autoappend Elem`(b: NodeBuffer) { def apply(e: Elem) = b &+ e }
defined class autoappend$u0020Elem
scala> :pa
// Entering paste mode (ctrl-D to finish)
val n =
<div>bar</div>
<div>baz</div>{
f
}
// Exiting paste mode, now interpreting.
n: scala.xml.NodeBuffer = ArrayBuffer(<div>bar</div>, <div>baz</div>, <div>foo</div>)
scala> val nn: NodeSeq = n
nn: scala.xml.NodeSeq = NodeSeq(<div>bar</div>, <div>baz</div>, <div>foo</div>)
You can still:
scala> n(1)
res0: scala.xml.Node = <div>baz</div>
scala> n(2)
res1: scala.xml.Node = <div>foo</div>
Doesn't everyone use interpolators these days? Is that a fad or a trend?
Upvotes: 1
Reputation: 7979
The best way I know of to handle this type of situation is to wrap the expressions in the no-op <xml:group/>
element:
import scala.xml._
def getNode() = <div>foo</div>
val node: NodeSeq = <xml:group>
<div>bar</div>
<div>baz</div>{
getNode()
}
</xml:group>
yields:
scala> node
res0: scala.xml.NodeSeq =
<div>bar</div>
<div>baz</div><div>foo</div>
Upvotes: 1