FreshMarker User Directives (1)

Zur Vereinfachung der FreshMarker Templates sollen wiederkehrende Teile als Funktionen und Macros formulierbar sein. Statt immer wieder ähnliche Teile im Template einzufügen, sollen diese durch parametrisierbare Aufrufe ersetzt werden. Wie in FreeMarker wird dies in FreshMarker über User Directives realisiert.

<p><b>Nachname</b>: ${lastname}</p>
<p><b>Vorname</b>: ${firstname}</p>

Hier wird in jedem Paragraph ein Label in Fettschrift und mit einem Doppelpunkt davon getrennt ein variabler Wert ausgegeben. Mit einem dafür geschriebenen Macro entry, ergibt sich folgendes Template.

<@entry label='Nachname' value=lastname/>
<@entry label='Vorname'  value=firstname/>

Die User Directive bietet den Vorteil, dass Änderungen für alle Eintrage nur noch einmal an zentraler Stelle erfolgen müssen. Die @ Notation entspricht dabei dem Aufruf einer User Directive, wie sie auch in FreeMarker verwendet wird.

Damit eine User Directive verwendet werden kann, muss sie definiert werden. Dafür bieten sich die Definition über ein Macro oder als Java Implementierung an. Gemeinsam ist beiden Ansätzen, dass ein spezielles UserDirectiveFragment im Template eine UserDirective Implementierung aufruft. Die UserDirective ist ein Interface das von den Java Implementierungen direkt implementiert wird und für das eine spezielle Implementierung für Macros existiert.

public class UserDirectiveFragment implements Fragment {

  private final String directive;
  private final Map<String, TemplateObject> namedArgs;
  private final BlockFragment body;

  public UserDirectiveFragment(String directive, Map<String, TemplateObject> namedArgs, BlockFragment body) {
    this.directive = directive;
    this.namedArgs = namedArgs;
    this.body = body;
  }

  @Override
  public void process(ProcessContext context) {
    context.getEnvironment().getDirective(directive).execute(context, namedArgs, body);
  }
}

Das UserDirectiveFragment kennt den Namen der User Directive und die Parameter, mit denen die User Directive aufgerufen werden soll. Zusätzlich enthält das Fragment noch ein BlockFragment, das den Inhalt der User Directive beschreibt. In der process Methode wird die entsprechende Implementierung der User Directive aus dem Environment geholt und deren execute Methode aufgerufen.

Die weiter oben dargestellte User Directive enthält keinen Inhalt. Im Beitrag Eigene FreeMarker Direktiven in Java implementieren wurde die User Directive oneliner für FreeMarker vorgestellt. Diese entfernt alle Zeilenumbrüche aus dem generierten Text, um mit dem entstehenden Einzeiler beispielsweise Aufrufparameter zu befüllen.

<@oneliner>
  <#list values as v>
    ${v?count}='${v}'
    <#if v?has_next>,</#if>
  </#list>
</@oneliner>

Das hier dargestellte Template erzeugt für die Liste Hund, Katze, Maus eine kommaseparierte Liste 1='Hund',2='Katze',3='Maus'

Die Implementierung in FreshMarker sieht der in FreeMarker recht ähnlich.

public class OneLinerDirective implements UserDirective {

  @Override
  public void execute(ProcessContext context, Map<String, TemplateObject> args, BlockFragment body) {
    if (body == null) {
      throw new ProcessException("one-liner body missing");
    }
    FlattenFilterWriter writer = new FlattenFilterWriter(context.getWriter());
    body.process(new ProcessContext(new WriterEnvironment(writer, context.getEnvironment()), context));
  }
}

Diese Implementierung ersetzt das aktuelle Environment durch einen Decorator, der den aktuellen Writer durch einen FlattenFilterWriter ersetzt. Mit diesem Environment wird dann der Inhalt der User Directive in body verarbeitet und alle Ausgaben durch den FlattenFilterWriter geleitet.

Um die OneLinerDirective einzusetzen, muss sie natürlich der Configuration bekannt sein. Dies kann über den dafür erweiterten PluginProvider Mechanismus erfolgen oder, der Einfachheit halber, über eine entsprechende Methode registerUserDirective.

templateLoader.putTemplate("test", "<@oneliner>\n<#list values as v>\n${v}\n<#if v?has_next>;</#if>\n</#list>\n</@oneliner>");
configuration.registerUserDirective( "oneliner", new OneLinerDirective());
Template template = configuration.getTemplate("test");
assertEquals("test: 1; 2; 3", template.process(Map.of("values", List.of(1,2,3))));

Damit ist der erste Teil der Implementierung von User Directives auch schon beendet und im nächsten Teil dreht es sich um die Bereitstellung von Macros und ihrer Schleifenvariablen

Leave a Comment