8

我正在使用 FreeMarker 模板引擎从 web 服务的抽象描述中生成一些 php 类。我的问题是,当我在 FreeMarker 模板中调用宏时,宏会在宏调用之前插入没有左侧空格的文本。

示例模板.ftl:

<?php
    class ${class.name} {
        <@docAsComment class.doc/>

        <#list class.fields as field>
        $${field.name};
        </#list>
        <#-- ... -->
    }
?>

<#macro docAsComment doc>
/*
<#if doc.title != "">
* ${doc.title}
</#if>
<#list doc.content as content>
<#if content != ""> * ${content}</#if>
</#list>
*/
</#macro>

这将生成如下内容:

<?php
    class foo {
/*
 * foo
 * bar foo, bla
 */          

    $a;
    $b;
    }
?>

一种解决方案是将前导空格作为参数提交给宏,但这只会使模板更加不可读。有更好的解决方案吗?

4

4 回答 4

5

似乎docAsComment总是在代码生成中以相同的缩进级别调用。您可以将该缩进烘焙到宏中。

如果注释的缩进是可变的,则必须传入缩进级别。我不明白您关于使模板更难阅读的评论。它确实使宏更复杂一些。

调用将如下所示:

<@docAsComment class.doc 1/>

宏会变成这样:

<#macro docAsComment doc indent=1>
   <#local spc>${""?left_pad(indent * 4)}</#local>
${spc}/*
<#if doc.title != "">
${spc}* ${doc.title}
</#if>
<#list doc.content as content>
<#if content != "">${spc} * ${content}</#if>
</#list>
${spc}*/
</#macro>

还不错,真的。您可以通过缩进使宏更易于阅读:

<#macro docAsComment doc indent=1>
    <#local spc>${""?left_pad(indent * 4)}</#local>
    ${spc}/*<#lt>
    <#if doc.title != "">
        ${spc}* ${doc.title}<#lt>
    </#if>
    <#list doc.content as content>
        <#if content != "">${spc} * ${content}</#if><#lt>
    </#list>
    ${spc}*/<#lt>
</#macro>
于 2013-03-27T15:59:10.727 回答
3

今天,可以使用<#nt>. 空白文档对此进行了如下说明:

可以使用 nt 指令(对于 No Trim)禁用单行的空白剥离。

根据V2.3 变更日志,在以前的版本中,仅包含 FTL 标记的行会被修剪,<#include>自定义指令(如<@macroname>)除外。但在 V2.3 中,他们将此行为更改为始终修剪此类线条。所以,当你使用你的宏时,你可以放上<#nt>一行来防止修剪,所以,保持缩进。

<#macro test>
...<#t>
</#macro>

Example:
   - <@test /><#nt>

给出结果:

Example:
   - ...

您可以看到,在宏中,我定义了<#t>,这是因为宏内部的新行不会被修剪,并且总是会在您所在的位置给出一个新行<@macro>,因此在一部分中,我们修剪了空白,在另一部分,我们保留它!

编辑:

值得一提的是,由于某种原因,这只适用于一行。如果您的宏中有多行,它只保留第一行的缩进。到目前为止,我还没有找到解决方法,但我在 Freemarker JIRA中为此创建了一个问题。

例子:

<#macro test>
...
wow
</#macro>

Example:
   - <@test><#nt>

将导致:

Example:
   - ...
wow
于 2017-10-27T10:57:15.567 回答
1

这类问题(动态缩进)的通用解决方案是一个过滤器,它(基本)理解您生成的语言(PHP)并重新缩进代码。您可以将过滤器实现为Writer包装真实输出的过滤器Writer{如果它观察,}和标记的位置可能就足够了/**/我不确定)。

另一个更容易实现的解决方案是创建一个自定义 FreeMarker 指令,通过TemplateDirectiveModel在每行的开头简单地添加或删除作为参数给定的空格数量来过滤在其嵌套内容中生成的输出。然后你可以做类似的事情:

<@indent spaces=4>
   ...
</@indent>

使用它会使模板更复杂,但它仍然不像在每一行中插入缩进那样嘈杂。

于 2013-03-30T09:12:45.700 回答
1

对于那些希望在导入的宏前加上一些空格缩进的人,这里有一个执行工作的类:

public final static class IndentDirective
    implements TemplateDirectiveModel
{

  private static final String COUNT = "count";

  public void execute(Environment environment, Map parameters, TemplateModel[] templateModels,
      TemplateDirectiveBody body)
      throws TemplateException, IOException
  {
    Integer count = null;
    final Iterator iterator = parameters.entrySet().iterator();
    while (iterator.hasNext())
    {
      final Map.Entry entry = (Map.Entry) iterator.next();
      final String name = (String) entry.getKey();
      final TemplateModel value = (TemplateModel) entry.getValue();

      if (name.equals(COUNT) == true)
      {
        if (value instanceof TemplateNumberModel == false)
        {
          throw new TemplateModelException("The \"" + COUNT + "\" parameter " + "must be a number");
        }
        count = ((TemplateNumberModel) value).getAsNumber().intValue();
        if (count < 0)
        {
          throw new TemplateModelException("The \"" + COUNT + "\" parameter " + "cannot be negative");
        }
      }
      else
      {
        throw new TemplateModelException("Unsupported parameter '" + name + "'");
      }
    }
    if (count == null)
    {
      throw new TemplateModelException("The required \"" + COUNT + "\" parameter" + "is missing");
    }

    final String indentation = StringUtils.repeat(' ', count);
    final StringWriter writer = new StringWriter();
    body.render(writer);
    final String string = writer.toString();
    final String lineFeed = "\n";
    final boolean containsLineFeed = string.contains(lineFeed) == true;
    final String[] tokens = string.split(lineFeed);
    for (String token : tokens)
    {
      environment.getOut().write(indentation + token + (containsLineFeed == true ? lineFeed : ""));
    }
  }

}

您可以通过添加configuration.setSharedVariable("indent", new IndentDirective());到 FreeMarker 配置来集成它,然后通过插入在模板中使用它

<@indent count=4>
[whathever template code, including macro usage]
</@indent>
于 2019-07-15T10:36:17.237 回答