(Nth) Alternative syntax proposal for FreeMarker

Denis <[email protected]>
Newsgroups gmane.comp.web.freemarker.user
Message-ID <[email protected]>
This has been raised several times already, especially from people  
coming from Velocity-land... The FreeMarker syntax is quite heavy.  
Now we can use square brackets which help when working with XML/HTML  
templates but there is still room for improvement.

My suggestion is quite simple, but it may require a new parser.

Comparison:

Directive without a body
--------------------------------
=FreeMarker classic=

Opening: <#directive
Closing: />

=FreeMarker 'light'=

Opening: ##directive
Closing: end of line


Directive with a body
----------------------------
=FreeMarker classic=

Opening: <#directive parameters>
Closing: <#/directive> (short form: <#/>)

=FreeMarker 'light'=

Opening: ##directive parameters
Closing: ##/directive (short form: ##/)


Macro call without a body
----------------------------------
=FreeMarker classic=

Opening: <@macroname
Closing: />

=FreeMarker 'light'=

Opening: @@macroname
Closing: end of line


Macro call with a body
------------------------------
=FreeMarker classic=

Opening: <@macroname parameters>
Closing: </@macroname> (short form: <@/>)

=FreeMarker 'light'=

Opening: ##@macroname parameters
Closing: ##/@macroname (short form: ##/)


Single-line comment
----------------------------
=FreeMarker classic=

Opening: <#-- comment
Closing: -->

=FreeMarker 'light'=

Opening: ##-- comment
Closing: end of line


Multi-line comment
--------------------------
=FreeMarker classic=

Opening: <#--
Closing: -->

=FreeMarker 'light'=

Opening: ##--
Closing: ##/-- (short form: ##/)


I chose double characters to minimize clashes with the rendered  
document. Escaping is done by trippling the double character, ie. ###  
renders as ## and @@@ renders as @@. Using single characters would  
require a different escaping rule.

To put more than one directive in a line, enclose in brackets eg. ## 
(if n > 5)##(-- display in long form)@@(showLongList items)##(else)@@ 
(showList items)##/

I tried it on a few templates and I think the result is quite pleasing.
Now a real-world example (borrowed from http://www.devdaily.com/java/ 
jwarehouse/spring-framework-2.5.3/dist/resources/spring.ftl.shtml)

Original
-----------

<#--
  * formCheckboxes
  *
  * Show checkboxes.
  *
  * @param path the name of the field to bind to
  * @param options a map (value=label) of all the available options
  * @param separator the html tag or other character list that should  
be used to
  *    separate each option. Typically ' ' or '<br>'
  * @param attributes any additional attributes for the element (such  
as class
  *    or CSS styles or size
-->
<#macro formCheckboxes path options separator attributes="">
     <@bind path/>
     <#list options?keys as value>
         <#assign id="${status.expression}${value_index}">
        <#assign isSelected = contains(status.value?default([""]),  
value)>
        <input type="checkbox" id="${id}" name="${status.expression}"  
value="${value?html}"<#if isSelected> checked="checked"<#/if> $ 
{attributes}<@closeTag/>
         <label for="${id}">${options[value]?html}${separator}
     </#list>
     <input type="hidden" name="_${status.expression}" value="on"/>
</#macro>

<#--
  * showErrors
  *
  * Show validation errors for the currently bound field, with
  * optional style attributes.
  *
  * @param separator the html tag or other character list that should  
be used to
  *    separate each option. Typically '<br>'.
  * @param classOrStyle either the name of a CSS class element (which  
is defined in
  *    the template or an external CSS file) or an inline style. If  
the value passed in here
  *    contains a colon (:) then a 'style=' attribute will be used,  
else a 'class=' attribute
  *    will be used.
-->
<#macro showErrors separator classOrStyle="">
     <#list status.errorMessages as error>
         <#if classOrStyle == "">
             <b>${error}
         <#else>
             <#if classOrStyle?index_of(":") == -1><#assign  
attr="class"><#else><#assign attr="style"></#if>
             <span ${attr}="${classOrStyle}">${error}
         </#if>
         <#if error_has_next>${separator}
     </#list>
</#macro>

<#--
  * checkSelected
  *
  * Check a value in a list to see if it is the currently selected  
value.
  * If so, add the 'selected="selected"' text to the output.
  * Handles values of numeric and string types.
  * This function is used internally but can be accessed by user code  
if required.
  *
  * @param value the current value in a list iteration
-->
<#macro checkSelected value>
     <#if stringStatusValue?is_number && stringStatusValue == value? 
number>selected="selected"</#if>
     <#if stringStatusValue?is_string && stringStatusValue ==  
value>selected="selected"</#if>
</#macro>

<#--
  * closeTag
  *
  * Simple macro to close an HTML tag that has no body with '>' or '/>',
  * depending on the value of a 'xhtmlCompliant' variable in the  
namespace
  * of this library.
-->
<#macro closeTag>
     <#if xhtmlCompliant?exists && xhtmlCompliant>/><#else>></#if>
</#macro>


'Light' version
------------------

##--
  * formCheckboxes
  *
  * Show checkboxes.
  *
  * @param path the name of the field to bind to
  * @param options a map (value=label) of all the available options
  * @param separator the html tag or other character list that should  
be used to
  *    separate each option. Typically ' ' or '<br>'
  * @param attributes any additional attributes for the element (such  
as class
  *    or CSS styles or size
  ##/
##macro formCheckboxes path options separator attributes=""
     @@bind path
     ##list options?keys as value
         ##assign id="${status.expression}${value_index}"
         ##assign isSelected = contains(status.value?default([""]),  
value)
         <input type="checkbox" id="${id}" name="$ 
{status.expression}" value="${value?html}"##(if isSelected)  
checked="checked"##(/if) ${attributes}@@closeTag
         <label for="${id}">${options[value]?html}${separator}
     ##/list
     <input type="hidden" name="_${status.expression}" value="on"/>
##/macro

##--
  * showErrors
  *
  * Show validation errors for the currently bound field, with
  * optional style attributes.
  *
  * @param separator the html tag or other character list that should  
be used to
  *    separate each option. Typically '<br>'.
  * @param classOrStyle either the name of a CSS class element (which  
is defined in
  *    the template or an external CSS file) or an inline style. If  
the value passed in here
  *    contains a colon (:) then a 'style=' attribute will be used,  
else a 'class=' attribute
  *    will be used.
  ##/
##macro showErrors separator classOrStyle=""
     ##list status.errorMessages as error
         ##if classOrStyle == ""
             <b>${error}
         ##else
             ##(if classOrStyle?index_of(":") == -1)##(assign  
attr="class")##(else)##(assign attr="style")##/
             <span ${attr}="${classOrStyle}">${error}
         ##/if
         ##(if error_has_next)${separator}##/
     ##/list
##/macro

##--
  * checkSelected
  *
  * Check a value in a list to see if it is the currently selected  
value.
  * If so, add the 'selected="selected"' text to the output.
  * Handles values of numeric and string types.
  * This function is used internally but can be accessed by user code  
if required.
  *
  * @param value the current value in a list iteration
  ##/
##macro checkSelected value
     ##(if stringStatusValue?is_number && stringStatusValue == value? 
number)selected="selected"##/
     ##(if stringStatusValue?is_string && stringStatusValue == value) 
selected="selected"##/
##/macro

##--
  * closeTag
  *
  * Simple macro to close an HTML tag that has no body with '>' or '/>',
  * depending on the value of a 'xhtmlCompliant' variable in the  
namespace
  * of this library.
  ##/
##macro closeTag
     ##(if xhtmlCompliant?exists && xhtmlCompliant)/>##(else)>##/
##/macro


For reference, the first macro in case FreeMarker 'light' uses single  
characters would look like:

#macro formCheckboxes path options separator attributes=""
     @bind path
     #list options?keys as value
         #assign id="${status.expression}${value_index}"
         #assign isSelected = contains(status.value?default([""]),  
value)
         <input type="checkbox" id="${id}" name="$ 
{status.expression}" value="${value?html}"#(if isSelected)  
checked="checked"#(/if) ${attributes}@closeTag
         <label for="${id}">${options[value]?html}${separator}
     #/list
     <input type="hidden" name="_${status.expression}" value="on"/>
#/macro


What do you think?
-- Denis.

-------------------------------------------------------------------------
Sponsored by: SourceForge.net Community Choice Awards: VOTE NOW!
Studies have shown that voting for your favorite open source project,
along with a healthy diet, reduces your potential for chronic lameness
and boredom. Vote Now at http://www.sourceforge.net/community/cca08
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.