隐式附加Scala XML文字

时间:2014-03-13 10:10:39

标签: xml scala append literals implicit

Scala对XML文字的支持非常适合生成类型安全的XHTML标记;然而,当给定块中包含非xml-literal代码时,必须将文字与++运算符一起附加,这是一个烦人的问题。

例如,这会爆发:

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

虽然以下内容进行了编译,但请注意非++调用之后的getNode() ,否则只有尾部Elem为生成。

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>

那么,有没有办法隐式链接XML文字,其中散布着XML,默认返回Scala代码?由于上面的示例编译但实际上已被破坏,因此在处理XML文字时会出现虚假的安全感。

明确强制附加文字的一种方法是执行以下操作:

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

但这并不是很好,因为你必须在方法调用中包装XML文字块。至少它强制附加,不会没有编译。

很想拥有一个干净的XHTML标记DSL,如果你有它,请提供秘密酱! (或者如果我遗漏了一些令人目眩的明显事物,请提醒我。)

由于

3 个答案:

答案 0 :(得分:1)

我知道处理这种情况的最好方法是将表达式包装在no-op <xml:group/>元素中:

import scala.xml._

def getNode() = <div>foo</div>

val node: NodeSeq = <xml:group>
  <div>bar</div>
  <div>baz</div>{
    getNode()
  }
</xml:group>

的产率:

scala> node
res0: scala.xml.NodeSeq = 

<div>bar</div>
<div>baz</div><div>foo</div>

答案 1 :(得分:1)

作为复习(对我而言),您的大括号不是嵌入式Scala表达式,它必须是标记内容。

您的错误消息显示您正在调用NodeBuffer.apply{f},以便进行以下增强。

如果连续有两个元素,则会得到NodeBuffer,编译器会使用&+构建它们。

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

你仍然可以:

scala> n(1)
res0: scala.xml.Node = <div>baz</div>

scala> n(2)
res1: scala.xml.Node = <div>foo</div>

这些天不是每个人都使用插补器吗?这是一种时尚还是潮流?

答案 2 :(得分:0)

所以,经过一段时间的黑客攻击后,我发现使用varargs的显式方法是可行的方法。

检查出来,为了生成html标记,你通常会在布局或部分内容中执行此操作,例如:

object Crud extends Layout{
  def apply[T <: Seq[Node]](title: String, args: (Symbol,String)*)(body: T*) {
    header(...)
    <body>
      {body}
    </body>
    footer(...)
  }
}

然后你想隐式链接包含上面布局主体的标记。根据上面的答案,没有优雅的方法来实现隐式链接。但是,使用var args,你可以吃蛋糕,也可以吃。在实现varargs方法之前,创建Foos的表单看起来像:

Crud(title, ...)(
  <h1>{title}</h1>
  <div>&nbsp;</div> ++
  withForm(routes.Foo.save)(
    fields(form) ++
    crud.submit("Create")
  ) ++
  css("foo") ++ 
  css("bar")    
)

零编译时保证不会遗忘一个++,完全容易出错。使用上面的varargs Crud应用签名foo表单创建看起来像这样:

Crud(title, ...)(
  <h1>{title}</h1>
  <div>&nbsp;</div>,
  withForm(routes.Foo.save)(
    fields(form),
    crud.submit("Create")
  ),
  css("foo"),
  css("bar")    
)

如果省略逗号,则会出现编译时错误,正是我们想要的。在“加号”方面,如果需要,您仍然可以使用++,因为Seq [Node]位于Scala XML类型层次结构的顶部,因此Elem,NodeSeq等无关紧要,无论它是什么,它必须从中派生出来Seq [Node]你很高兴,哇喔; - )