【问题标题】:Implicitly Appending Scala XML Literals隐式附加 Scala XML 文字
【发布时间】:2014-04-17 23:39:34
【问题描述】:

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

例如,这会爆炸:

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

在编译下面的代码时,请注意您必须如何在++ 之后 调用非xml 文字的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,如果你有的话,请提供秘诀! (或者如果我遗漏了一些非常明显的东西,请提示我)。

谢谢

【问题讨论】:

    标签: xml scala append literals implicit


    【解决方案1】:

    我知道处理这种情况的最佳方法是将表达式包装在无操作 &lt;xml:group/&gt; 元素中:

    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>
    

    【讨论】:

    • 事实证明,该解决方案提供了最小的句法噪音,同时似乎可以在各种 xml 类型组合(Elems、NodeSeq、NodeBuffer 等)中可靠地工作。谢谢
    • 抱歉,我刚刚提出了一个使用我刚刚发布的可变参数的解决方案。这里的所有答案本身都是有效的,所以不会被检查;-)
    【解决方案2】:

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

    您的错误消息显示您正在调用NodeBuffer.apply{f},因此这是下面的增强功能。

    当你连续有两个元素时,你会得到一个NodeBuffer,编译器会使用&amp;+ 构建它们。

    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>
    

    现在不是每个人都使用插值器吗?这是一种时尚还是一种趋势?

    【讨论】:

      【解决方案3】:

      所以,经过一番修改后,我发现使用可变参数的显式方法是可行的方法。

      检查一下,为了生成 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")    
      )
      

      零编译时间保证不会忘记 ++,完全容易出错。使用上面的可变参数 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],你很高兴,哇哦;-)

      【讨论】:

      • 使用预期的类型一切都会变得更容易,如果是函数调用则更容易。这与 OP 大不相同,OP 是一个简单的表达方式(我仍然喜欢我对大括号的挪用)。对于这个用例,您可以只编写一个收集 Node 表达式的宏——它们是否表示文字或调用与宏无关。不需要标点符号或运算符。
      • 我看了看,但是作为一个别名参数,(body: =&gt;Unit) 或类似的,它遇到了同样的解析问题,你需要一个在 xml 和代码之间的半。之后,宏可以组装块的Elem-valued 部分。
      • @som-snytt 不需要标点符号的宏确实很优雅,可以解决整个问题。写那个宏是另一回事,还没有走宏的道路(似乎不断涌入)。
      猜你喜欢
      • 1970-01-01
      • 2016-04-16
      • 2011-09-13
      • 2020-12-08
      • 2015-10-29
      • 1970-01-01
      • 2021-12-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多