Difference between revisions of "Template Engine"

From PCGen Wiki
Jump to: navigation, search
(Add initial freemarker documentation)
(Add doco for equipsetloop directive)
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
{| align="right"
 +
  | __TOC__
 +
  |}
 
== Template Engine Sub Project ==
 
== Template Engine Sub Project ==
  
Line 31: Line 34:
 
* Support for including other files, e.g. macro libraries or common blocks
 
* Support for including other files, e.g. macro libraries or common blocks
 
* Calculations and state within the output template
 
* Calculations and state within the output template
* Built in syntax support to [http://freemarker.org/editors.html many text editors]
+
* Built-in syntax support in [http://freemarker.org/editors.html many text editors]
  
 
Here's a simple example FreeMarker PCGen template
 
Here's a simple example FreeMarker PCGen template
Line 69: Line 72:
 
# [http://freemarker.org/ Main FreeMarker page]
 
# [http://freemarker.org/ Main FreeMarker page]
  
In addition, see testsuite/base-xml.ftl for afully worked example of xml output.
+
In addition, see testsuite/base-xml.ftl for a fully worked example of xml output.
  
 
=== Creating a Sheet ===
 
=== Creating a Sheet ===
  
FreeMarker sheets are detected by PCGen by their file name suffix. A template ending in .ftl will be processed by FreeMarker rather than by PCGen's legacy output engine. The extension of the file to be produced should be immediately before that. So a template to produce html output might be named csheet-first.html.ftl or chseet-compact-html.ftl and PCGen would pick up that it was a template to be processed by FreeMarker template that would produce output of a html file.
+
FreeMarker sheets are detected by PCGen by their file name suffix. A template ending in .ftl will be processed by FreeMarker rather than by PCGen's legacy output engine. The extension of the file to be produced should be immediately before that and preceded by a . or -. So a template to produce html output might be named csheet-first.html.ftl or chseet-compact-html.ftl and PCGen would pick up that it was a template to be processed by FreeMarker template that would produce output of a html file.
  
 
=== PCGen's Custom Functions ===
 
=== PCGen's Custom Functions ===
  
To provide output of PCGen characters we need to be able to access the character data and the output tags.  
+
To provide output of PCGen characters we need to be able to access the character data and the output tags. The following tags are provided for that purpose.
  
 
==== pcstring ====
 
==== pcstring ====
  
This tag evaluates a PCGen export token for the current character and returns the value as a string. e.g. <@pcstring tag="PLAYERNAME"/> or ${pcstring('PLAYERNAME')}  
+
This tag evaluates a PCGen export token for the current character and returns the value as a string. It may be called as a directive (with a single parameter named 'tag') or as a function (with a single unnamed parameter).  
 +
 
 +
''Examples''
 +
<nowiki>
 +
<@pcstring tag="PLAYERNAME"/>
 +
<@pcstring tag="CLASSABB.${class}"/>
 +
${pcstring('CLASSABB.${class}')}
 +
<#if (pcstring('SPELLLISTCLASS.${class}')?length > 0)>present</#if></nowiki>
  
 
==== pcvar ====
 
==== pcvar ====
  
This tag allows character variable values to be exported to a FreeMarker template. It evaluates a variable for the current character and returns the value as a number. e.g. ${pcvar("CL=Fighter")}  
+
This tag allows character variable values to be exported to a FreeMarker template. It evaluates a variable for the current character and returns the value as a number.  
 +
 
 +
''Examples''
 +
<nowiki>
 +
<#assign numClasses=pcvar('countdistinct("CLASSES")')/>
 +
${pcvar('count("ABILITIES","CATEGORY=FEAT","TYPE=Metamagic","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")')}
 +
${pcvar("CL=Fighter")}
 +
</nowiki>
  
 
==== pcboolean ====
 
==== pcboolean ====
  
pcboolean allows character boolean values to be exported to a FreeMarker template. It evaluates an export token for the current character and returns the value as a boolean. e.g. ${pcboolean("WEAPON.0.ISTYPE.Double")}
+
pcboolean allows character boolean values to be exported to a FreeMarker template. It evaluates an export token for the current character and returns the value as a boolean.  
 +
 
 +
''Examples''
 +
<nowiki>
 +
<#if pcboolean('WEAPON.${weap}.ISTYPE.Double')>
 +
<#if (pcboolean('HASVAR:Manifester.OR.HASVAR:PsychicWarriorManifester')) ></nowiki>
  
 
==== loop ====
 
==== loop ====
  
The loop tag provides a way to loop a certain number of times. Unlike the inbuilt list directive it can loop zero times.
+
The loop tag provides a way to repeat content a certain number of times. Unlike the inbuilt list directive it can loop zero times.
  
Parameters
+
'''Parameters'''
  
 
* from (optional) - The starting value, defaults to 0.
 
* from (optional) - The starting value, defaults to 0.
Line 101: Line 123:
 
* step (optional) - The amount to increment b each loop, defaults to 1.
 
* step (optional) - The amount to increment b each loop, defaults to 1.
  
In addition up to two loopvars may be specified. The first will be populated with the current index value of the loop and the second will be a boolean indicating if there are more iterations of the loop to go.
+
In addition up to two loopvars may be specified. The first will be populated with the current index value of the loop and the second will be a boolean indicating if there are more iterations of the loop to go. We recommend that the second variable, if present, is named after the first variable with _has_next added. e.g. class, class_has_next . This is in order to match the list directive, which provides a similarly named variable automatically.
  
Nested content is output once for each loop
+
''Example''
 +
<nowiki>
 +
<@loop from=0 to=pcvar('countdistinct("CLASSES")')-1 ; class , class_has_next >
 +
<@pcstring tag="CLASSABB.${class}"/>
 +
<@pcstring tag="CLASS.${class}.LEVEL"/><#if class_has_next>,</#if>
 +
</@loop></nowiki>
 +
might produce
 +
<nowiki>
 +
Rog 11, Wiz 5</nowiki>
  
== Phase 1 ==
+
==== equipsetloop ====
  
The aim would be to utilise the already built output system and add the ability to
+
The equipsetloop tag provides a way to repeat content for each of a character's equipment sets.  
# Detect Freemarker templates and use the Freemarker engine to process them
 
# Add custom directives that can be called from a template (output sheet) to allow the existing output tags to be used.
 
  
This would give us a simple change-over and use our existing well tested output code base.
+
'''Parameters'''
  
 +
None
  
Example of outputting an export tag
+
''Example''
 
  <nowiki>
 
  <nowiki>
<@pcstring tag="NAME"/>
+
<@equipsetloop>
<@pcstring tag="SPELLMEM.0.1.1.1.NAME"/>
+
  <b>Equipment Set : </b> ${pcstring("EQSET.NAME")}. Equipment:
<@pcstring tag="PLAYERNAME"/></nowiki>
+
  <@loop from=0 to=pcvar('COUNT[EQUIPMENT.MERGELOC]')-1 ; equip , equip_has_next >
 
+
    ${pcstring("EQ.MERGELOC.${equip}.NAME")}<#if equip_has_next>, </#if>
Example of accessing a character variable value:
+
  </@loop><#lt><#-- Equipment -->
 +
  </equipmentset><br/>
 +
</@equipsetloop></nowiki>
 +
might produce
 
  <nowiki>
 
  <nowiki>
${pcvar("CL=Fighter")}
+
<b>Equipment Set : </b> Travelling Gear. Equipment: Backpack, Longsword, Traveller's Outfit<br/>
${pcvar("CL=Rogue")}
+
<b>Equipment Set : </b> Council. Equipment: Coin purse, Noble's Outfit, Rapier<br/></nowiki>
${pcvar("count(\"ABILITIES\",\"CATEGORY=FEAT\",\"TYPE=Metamagic\",\"VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY\")")}</nowiki>
 
  
We would need to ensure that freemarker templates can be used in html, and PDF output styles.
 
  
See [http://freemarker.org/docs/dgui_misc_userdefdir.html User Defined Directives] and [http://freemarker.org/docs/pgui_config_sharedvariables.html Shared Variables] for information on creating custom directives/tags.
 
  
For example, the sheet
+
== Phase 1 ==
<nowiki>
 
<!-- Produced on ${.now?date} at ${.now?time} using template ${.template_name} -->
 
<h1>Freemarker Sheet for <@pcstring tag="NAME"/> - ${.now?date}</h1>
 
  
<p> Character Name: <@pcstring tag="NAME"/> </p>
+
The aim of phase 1 is to utilise the already built output system and add the ability to
<p> Player Name: <@pcstring tag="PLAYERNAME"/> </p>
+
# Detect Freemarker templates and use the Freemarker engine to process them
 +
# Add custom directives that can be called from a template (output sheet) to allow the existing output tags to be used.
  
<p> Fighter: ${pcvar("CL=Fighter")} </p>
+
This gives us a simple change-over and use of our existing well tested output tags.
<p> Rogue: ${pcvar("CL=Rogue")} </p></nowiki>
 
  
produced the output
+
We need to ensure that freemarker templates can be used in html, and PDF output styles.
<nowiki>
 
<!-- Produced on 29/10/2013 at 9:44:47 PM using template csheet_test-html.ftl -->
 
<h1>Freemarker Sheet for Ronald 'Surefingers' Millbridge - 29/10/2013</h1>
 
  
<p> Character Name: Ronald 'Surefingers' Millbridge </p>
+
See [http://freemarker.org/docs/dgui_misc_userdefdir.html User Defined Directives] and [http://freemarker.org/docs/pgui_config_sharedvariables.html Shared Variables] for information on creating custom directives/tags.
<p> Player Name: James </p>
 
 
 
<p> Fighter: 0 </p>
 
<p> Rogue: 11 </p>
 
 
 
<p>Rog 11,Wiz 2</p></nowiki>
 
  
 
=== 1b Assisted Looping ===
 
=== 1b Assisted Looping ===
Line 166: Line 183:
 
</#if></nowiki>
 
</#if></nowiki>
  
Note The if test is to allow the loop yo be skipped if the list is empty.
+
Note The if test is to allow the loop to be skipped if the list is empty.
  
Now this is a bit verbose so I have instead created a custom directive to allow looping a number of times. This also deals with the empty loop scenario.
+
Now this is a bit verbose so I have instead created a custom 'loop' directive to allow looping a number of times. This also deals with the empty loop scenario. See [[#loop|loop]]
  
<nowiki>
 
<@loop from=0 to=pcvar('countdistinct("CLASSES")')-1 ; class , class_has_next >
 
<@pcstring tag="CLASSABB.${class}"/>
 
<@pcstring tag="CLASS.${class}.LEVEL"/><#if class_has_next>,</#if>
 
</@loop>
 
</nowiki>
 
 
=== 1c Object Output ===
 
=== 1c Object Output ===
  

Latest revision as of 04:26, 22 February 2014

Template Engine Sub Project

The aim of the project is to examine the Freemarker library with a view to using it to replace our export engine. In particular it would cover functionality logic functions like looping and if tests. This would leave our code to deal with the important things like TO HIT values etc.

We would do this in three phases:

  1. Add support for Freemarker templates for output
  2. Once sufficient sheets have been converted/rewritten, deprecate support for old sheet types
  3. Finally after a few releases, remove support for the old sheet types.

There are a few reasons for exploring this change

  • Add a richer set of logic commands to enable more customisable output
  • Simpler more consistent logic commands and more widely understood syntax
  • Remove one of the more complex and difficult to maintain sections of the PCGen code base.

Status

Currently working on phase 1.

FreeMarker support has been added to PCGen and is available in the trunk autobuilds. It will first be available in the 6.03.00 alpha release scheduled for February 2014.

Progress is being tracked in CODE-2418 FreeMarker Output Generation - Phase 1

FreeMarker Template How-To

Introduction to FreeMarker

FreeMarker is a template engine that has now been incorporated into PCGen's export system as an alternative to our venerable export syntax. The FreeMarker engine brings some important benefits:

  • Industry standard syntax
  • Support for macros to output parameterised blocks
  • Improved flow-control and logic instructions
  • Support for including other files, e.g. macro libraries or common blocks
  • Calculations and state within the output template
  • Built-in syntax support in many text editors

Here's a simple example FreeMarker PCGen template

<!-- Produced on ${.now?date} at ${.now?time} using template ${.template_name} -->
<h1>Freemarker Sheet for <@pcstring tag="NAME"/> - ${.now?date}</h1>

<p> Character Name: <@pcstring tag="NAME"/> </p>
<p> Player Name: <@pcstring tag="PLAYERNAME"/> </p>

<p> Fighter: ${pcvar("CL=Fighter")} </p>
<p> Rogue: ${pcvar("CL=Rogue")} </p>

<p><@loop from=0 to=pcvar('countdistinct("CLASSES")')-1 ; class , class_has_next >
	<@pcstring tag="CLASSABB.${class}"/> <#t>
	<@pcstring tag="CLASS.${class}.LEVEL"/><#if class_has_next>,</#if><#t>
</@loop></p>

which produces the output

<!-- Produced on 29/10/2013 at 9:44:47 PM using template csheet_test-html.ftl -->
<h1>Freemarker Sheet for Ronald 'Surefingers' Millbridge - 29/10/2013</h1>

<p> Character Name: Ronald 'Surefingers' Millbridge </p>
<p> Player Name: James </p>

<p> Fighter: 0 </p>
<p> Rogue: 11 </p>

<p>Rog 11,Wiz 2</p>

FreeMarker Documentation

FreeMarker has quite thorough documentation. The main references output sheet authors will use are:

  1. Template Author's Guide
  2. Reference Guide
  3. Main FreeMarker page

In addition, see testsuite/base-xml.ftl for a fully worked example of xml output.

Creating a Sheet

FreeMarker sheets are detected by PCGen by their file name suffix. A template ending in .ftl will be processed by FreeMarker rather than by PCGen's legacy output engine. The extension of the file to be produced should be immediately before that and preceded by a . or -. So a template to produce html output might be named csheet-first.html.ftl or chseet-compact-html.ftl and PCGen would pick up that it was a template to be processed by FreeMarker template that would produce output of a html file.

PCGen's Custom Functions

To provide output of PCGen characters we need to be able to access the character data and the output tags. The following tags are provided for that purpose.

pcstring

This tag evaluates a PCGen export token for the current character and returns the value as a string. It may be called as a directive (with a single parameter named 'tag') or as a function (with a single unnamed parameter).

Examples

<@pcstring tag="PLAYERNAME"/>
<@pcstring tag="CLASSABB.${class}"/>
${pcstring('CLASSABB.${class}')}
<#if (pcstring('SPELLLISTCLASS.${class}')?length > 0)>present</#if>

pcvar

This tag allows character variable values to be exported to a FreeMarker template. It evaluates a variable for the current character and returns the value as a number.

Examples

<#assign numClasses=pcvar('countdistinct("CLASSES")')/>
${pcvar('count("ABILITIES","CATEGORY=FEAT","TYPE=Metamagic","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")')}
${pcvar("CL=Fighter")}

pcboolean

pcboolean allows character boolean values to be exported to a FreeMarker template. It evaluates an export token for the current character and returns the value as a boolean.

Examples

<#if pcboolean('WEAPON.${weap}.ISTYPE.Double')>
<#if (pcboolean('HASVAR:Manifester.OR.HASVAR:PsychicWarriorManifester')) >

loop

The loop tag provides a way to repeat content a certain number of times. Unlike the inbuilt list directive it can loop zero times.

Parameters

  • from (optional) - The starting value, defaults to 0.
  • to - The ending value (inclusive). If this is less than from then the contents will not be output.
  • step (optional) - The amount to increment b each loop, defaults to 1.

In addition up to two loopvars may be specified. The first will be populated with the current index value of the loop and the second will be a boolean indicating if there are more iterations of the loop to go. We recommend that the second variable, if present, is named after the first variable with _has_next added. e.g. class, class_has_next . This is in order to match the list directive, which provides a similarly named variable automatically.

Example

<@loop from=0 to=pcvar('countdistinct("CLASSES")')-1 ; class , class_has_next >
	<@pcstring tag="CLASSABB.${class}"/>
	<@pcstring tag="CLASS.${class}.LEVEL"/><#if class_has_next>,</#if>
</@loop>

might produce

Rog 11, Wiz 5

equipsetloop

The equipsetloop tag provides a way to repeat content for each of a character's equipment sets.

Parameters

None

Example

<@equipsetloop>
  <b>Equipment Set : </b> ${pcstring("EQSET.NAME")}. Equipment:
  <@loop from=0 to=pcvar('COUNT[EQUIPMENT.MERGELOC]')-1 ; equip , equip_has_next >
    ${pcstring("EQ.MERGELOC.${equip}.NAME")}<#if equip_has_next>, </#if>
  </@loop><#lt><#-- Equipment -->
  </equipmentset><br/>
</@equipsetloop>

might produce

<b>Equipment Set : </b> Travelling Gear. Equipment: Backpack, Longsword, Traveller's Outfit<br/>
<b>Equipment Set : </b> Council. Equipment: Coin purse, Noble's Outfit, Rapier<br/>


Phase 1

The aim of phase 1 is to utilise the already built output system and add the ability to

  1. Detect Freemarker templates and use the Freemarker engine to process them
  2. Add custom directives that can be called from a template (output sheet) to allow the existing output tags to be used.

This gives us a simple change-over and use of our existing well tested output tags.

We need to ensure that freemarker templates can be used in html, and PDF output styles.

See User Defined Directives and Shared Variables for information on creating custom directives/tags.

1b Assisted Looping

Freemarker's inbuilt looping mechanism is object oriented. It expects to receive a list and process through it. In contrast, PCGen currently only outputs strings and numbers. The technique described at For loop can be used, which results in a structure like the following to loop through a list:

<#assign max=pcvar('countdistinct("CLASSES")')-1>
<#if (max>=0)>
<#list 0..max as class>
	<@pcstring tag="CLASSABB.${class}"/>
	<@pcstring tag="CLASS.${class}.LEVEL"/><#if class_has_next>,</#if>
</#list>
</#if>

Note The if test is to allow the loop to be skipped if the list is empty.

Now this is a bit verbose so I have instead created a custom 'loop' directive to allow looping a number of times. This also deals with the empty loop scenario. See loop

1c Object Output

To more closely align with FreeMarker's object oriented approach, we should examine the potential for exporting objects for output. So for example, exporting a list of spell objects that can then be looped over and each object queried as needed. A base for this output interface could be the Facade work done for the user interface.

<#list pc.knownspells as spell>
    <li>${spell.name} School: ${spell.school} <#if spell.subschool??> [${spell.school}] </#if></li>
</#list>