5

我有一个基本上看起来像这样的宏:

#macro( surround $x )
  surround:$x
    $bodyContent
  /surround:$x
#end

调用#@surround("A")bunch o' stuff#end按预期产生“环绕:一堆东西/环绕:A”。调用#@surround("A")#@surround("B")more stuff#end#end产生环绕:A环绕:B更多的东西/环绕:B /环绕:A这正是我想要的。

但现在我想用另一个宏向上构建

#macro( annotated-surround $x $y )
  #@surround( $x )
    annotate:$y
    $bodyContent
  #end
#end

的预期扩展#annotated-surround( "C" "note" ) stuff #end是环绕:C annotate:note stuff /surround:C

...但这不起作用;我得到了带注释的环绕体内容的可怕的半无限扩展。

我已经阅读了Velocity 模板宏中的 Closure的答案,但仍然不太清楚我想做的事情是否可行。

我愿意在#surroundand 的定义中做任意棘手的事情#annotated-surround,但我不希望这些宏的用户看到任何复杂性。整个想法是简化他们的生活。

只要我有你的耳朵:设置macro.provide.scope.control=true应该是“宏中的本地命名空间”。这是什么意思?提供的命名空间是否独立于默认上下文,但在所有宏的所有调用之间共享一个这样的空间?或者是否为每个宏调用提供了单独的上下文,甚至是递归的?它必须是后者,因为$macro.parent,对吧?

还有一个问题。考虑以下宏:

#macro( recursive $x )
  #if($x == 0)
    zero
  #else
    $x before . . . 
    #set($xMinusOne = $x - 1)
    #recursive($xMinusOne)
    . . . $x after
  #end
#end

#recursive( 4 )产量:

4 之前。. . 3 之前。. . 2 之前。. . 1 之前。. . 零 。. . 0 之后。. . 0 之后。. . 0 之后。. . 4 后

现在我明白了所有那些“0”的出现:只有一个全局 $x,所以在递归调用中分配给它会破坏它并且它不会被恢复。但是最后的“4”到底是从哪里来的呢?就此而言,我的第一个“环绕”宏是如何工作到任意深度的?为什么它的最终 $x 不会在内部调用中被破坏?

很抱歉这么冗长,但我一直无法在这件事上找到明确的文件。

4

2 回答 2

1

问题在于全局变量、名称冲突和延迟渲染的组合。

让我们来看看渲染过程#@annotated-surround( "x" "y" )content#end

  1. 渲染进入annotated-surround宏。上下文映射包含:
    1. $x= 字符串x
    2. $y= 字符串y
    3. $bodyContent= 可渲染content- 请注意尚未评估此字符串的输出。
  2. 第一行的渲染进入surround宏。这会将上下文映射更新为:
    1. $x= 旧$x= 字符串x
    2. $y= 字符串y
    3. $bodyContent= Renderable annotate:$y\n$bodyContent- 注意这个字符串的输出还没有被评估,它仍然是模板代码。
  3. 渲染输出 的第一行surround,生成字符串surround:x
  4. 渲染开始评估surround引用的第二行$bodyContent
    1. 渲染第一行$bodyContent会产生 String annotate:y
    2. 渲染开始评估$bodyContent引用的第二行$bodyContent
      1. 渲染第一行$bodyContent会产生 String annotate:y
      2. 渲染开始评估$bodyContent引用的第二行$bodyContent
        1. 等等

解决方案是删除问题组合的一部分。全局变量和延迟渲染是 Velocity 工作原理的基本部分,因此您无法触及它们。这留下了名称冲突。您需要的是$bodyContent用不同的名称引用每个宏。这很容易通过在调用任何其他宏之前将其分配给每个宏中具有唯一名称的新变量来实现,并在任何调用的宏的主体中使用新变量,如下所示:

#macro( surround $x )
  surround:$x
    $bodyContent
  /surround:$x
#end

#macro( annotated-surround $x $y )
  #set( $annotated-surround-content = $bodyContent )
  #@surround( $x )
    annotate:$y
    $annotated-surround-content
  #end
#end

这个版本的渲染是这样的:

  1. 渲染进入annotated-surround宏。上下文映射包含:
    1. $x= 字符串x
    2. $y= 字符串y
    3. $bodyContent= 可渲染content- 请注意尚未评估此字符串的输出。
  2. 第一行的渲染执行#set指令,将一个变量添加到上下文映射:$annotated-surround-content= current $bodyContent= Renderable content
  3. 第二行的渲染进入surround宏。这会将上下文映射更新为:
    1. $x= 旧$x= 字符串x
    2. $y= 字符串y
    3. $annotated-surround-content= 旧$bodyContent= 可渲染content
    4. $bodyContent= 可渲染annotate:$y\n$annotated-surround-content
  4. 渲染输出 的第一行surround,生成字符串surround:x
  5. 渲染开始评估surround引用的第二行$bodyContent
    1. 渲染第一行$bodyContent会产生 String annotate:y
    2. 渲染开始评估$bodyContent引用的第二行$annotated-surround-content
      1. 渲染$annotated-surround-content产生 String content
  6. 渲染输出第三行surround,生成字符串/surround:x

最终渲染的输出是surround:x annotate:y content /surround:x. 通过将此类替换应用于另一个宏调用的内容内的所有出现,可以推广这种方法$bodyContent,每次使用从宏名称派生的变量名称以确保唯一性。但是,如果没有额外的东西来区分每个嵌套调用,它就不适用于递归宏。

关于范围设置,所做的只是将一个$macro对象添加到上下文中,该对象对于每个宏调用都是唯一的,并且可以用作映射。如果您$macro.myVar在两个嵌套宏调用中的每一个中设置不同的值,则外部宏的值将在内部宏完成时保持不变。但是,这对解决问题没有帮助$bodyContent,因为对$macro宏内部的任何引用$bodyContent都将在呈现时解析为最内层的宏。

关于最后 4 个 from #recursive( 4 ),它来自具有本地范围并按名称传递的宏参数的组合。除了最外层的调用之外#recursive,参数$x是对全局上下文变量的引用$xMinusOne——当它们渲染after行时,使用实际上被解析为在全局上下文中$x查找当前值。$xMinusOne对于最外面的调用,它是常量 value 4,并且内部调用的参数在完成时超出范围,因此当最外面的调用到达最后一行时,它又回到了 being 4

于 2019-12-12T03:01:48.237 回答
0

从最简单的开始,macro.provide.scope.control=true 肯定会为每个宏调用创建一个单独的 $macro 范围对象。否则,正如您所注意到的, $macro.parent 将是无稽之谈。“范围控制”的全部意义在于为所讨论的 VTL 块的类型提供明确的命名空间。您甚至可以使用 round.provide.scope.control=true 在#@surround bodyContent 中自动创建 $surround 范围。

关于你的第一个问题,我对发生的事情有点困惑。对#@annotate-surround 的调用和对#@surround 的嵌套调用都将使$bodyContent 引用可用。我是对的,发生的事情是使用了“错误的” $bodyContent 吗?$bodyContent 引用应该属于最近的块宏调用。要在内部宏中引用外部宏的 $bodyContent,您可能需要 #set( $macro.bodyContent = $bodyContent ),然后在内部宏中通过 $macro.parent.bodyContent 使用它

至于#recursive 的怪异,我不知道,现在必须开始其他工作。我没有在我现在的机器上检查 Velocity 也无济于事,所以我不能快速尝试。

于 2012-10-19T22:10:30.470 回答