我有一个基本上看起来像这样的宏:
#macro( surround $x )
surround:$x
$bodyContent
/surround:$x
#end
调用#@surround("A")bunch o' stuff#end
产生“周围:一堆东西/周围:A”,如下所示:预期。调用#@surround("A")#@surround("B")more stuff#end#end
产生环绕声:一个环绕声:B更多的东西/ surround:B / surround:A正是我想要的。
但是现在我想使用另一个宏向上构建
#macro( annotated-surround $x $y )
#@surround( $x )
annotate:$y
$bodyContent
#end
#end
#annotated-surround( "C" "note" ) stuff #end
的预期扩展为环绕声:C批注:音符/环绕声:C
...但是这不起作用;我得到了带注释的环绕体的可怕的半无限扩展内容。
我已经阅读了Closure in Velocity template macros的答案,但仍然不太清楚我想做的事是否可行。
我愿意在#surround
和#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不会被内部调用粉碎?
很抱歉,我在这件事上找不到清晰的文档。
从最简单的开始,macro.provide.scope.control = true一定会为every宏调用创建一个单独的$ macro作用域对象。否则,正如您所注意到的,$ macro.parent将是胡说八道。 “作用域控件”的重点是为所讨论的VTL块类型提供一个显式的名称空间。您甚至可以执行Surround.provide.scope.control = true以在#@ surround bodyContent内部自动创建$ surround范围。
关于您的第一个问题,我对所发生的事情有些困惑。调用#@ annotate-surround和嵌套调用#@ surround都将使$ bodyContent引用可用。我是对的,这是在使用“错误的” $ bodyContent吗? $ bodyContent引用应属于最近的块宏调用。要在内部宏中引用外部宏的$ bodyContent,您可能需要#set($ macro.bodyContent = $ bodyContent),然后在内部宏中通过$ macro.parent.bodyContent
使用至于#递归怪异,我不了解其他东西,现在必须继续其他工作。我在当前的机器上没有检出Velocity也无济于事,所以我不能很快尝试一下。
问题是全局变量,名称冲突和延迟渲染的组合。
让我们逐步了解#@annotated-surround( "x" "y" )content#end
的渲染过程:
annotated-surround
宏。上下文映射包含:$x
=字符串x
$y
=字符串y
$bodyContent
=可渲染的content
-请注意,此字符串输出尚未评估。surround
宏。这会将上下文映射更新为:$x
=旧$x
=字符串x
$y
=字符串y
$bodyContent
=可呈现的annotate:$y\n$bodyContent
-请注意,此字符串输出仍未评估,它仍是模板代码。surround
的第一行,产生字符串surround:x
。surround
的第二行,该行引用了$bodyContent
。$bodyContent
的第一行将产生字符串annotate:y
。$bodyContent
的第二行,该行引用了$bodyContent
。$bodyContent
的第一行将产生字符串annotate:y
。$bodyContent
的第二行,该行引用了$bodyContent
。解决方案是删除部分问题的组合。全局变量和延迟渲染是Velocity工作方式的基本组成部分,因此您不能碰那些。留下名称冲突。您需要使用不同的名称来引用每个宏的$bodyContent
。可以通过在调用任何其他宏之前将其分配给每个宏中具有唯一名称的新变量,并在任何调用的宏主体中使用new变量来轻松实现,如下所示:
#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
此版本的渲染如下:
annotated-surround
宏。上下文映射包含:$x
=字符串x
$y
=字符串y
$bodyContent
=可渲染的content
-请注意,此字符串输出尚未评估。#set
指令,将一个变量添加到上下文映射:$annotated-surround-content
=当前$bodyContent
=可渲染的content
。surround
宏。这会将上下文映射更新为:$x
=旧$x
=字符串x
$y
=字符串y
$annotated-surround-content
=旧的$bodyContent
=可渲染的content
] >>$bodyContent
=可渲染的annotate:$y\n$annotated-surround-content
surround
的第一行,产生字符串surround:x
。surround
的第二行,该行引用了$bodyContent
。$bodyContent
的第一行将产生字符串annotate:y
。$bodyContent
的第二行,该行引用了$annotated-surround-content
。$annotated-surround-content
产生字符串content
。surround
的第三行,生成字符串/surround:x
。最终渲染的输出是surround:x annotate:y content /surround:x
。可以通过将这样的替换应用于另一个宏调用内容中所有出现的$bodyContent
的方式来泛化此方法,每次使用从宏名称中派生的变量名称来确保唯一性。但是,如果没有额外的区别来区分每个嵌套的调用,它将无法用于递归宏。
关于范围设置,所有要做的就是向上下文添加一个$macro
对象,该对象对于每个宏调用都是唯一的,并且可以用作映射。如果在两个嵌套宏调用的每一个中都将$macro.myVar
设置为不同的值,则当内部宏结束时,外部宏的值将保持不变。但是,这对解决$bodyContent
问题没有帮助,因为对宏$macro
中对$bodyContent
的任何引用都将在呈现时解析为最里面的宏。
关于#recursive( 4 )
中的最后4个字符,它来自具有局部作用域并按名称传递的宏参数的组合。对于除#recursive
之外的所有调用,参数$x
是对全局上下文变量$xMinusOne
的引用-当它们呈现after
行时,实际上解析为$x
的使用来查找全局上下文中$xMinusOne
的当前值。对于最外层调用,它是常量4
,而内层调用的参数在完成时超出范围,因此,当最外层调用到达最后一行时,它又回到4
。