===================================== THE "CHUNK" TEMPLATING SYSTEM

Philosophy: presentation code (HTML) and source code (Java) are like
oil and water.  They shouldn't mix, and when they do, it's not pretty.

 cf php, jsp, asp.

Fortunately, keeping them apart is a breeze with a good templating system.


Examples

Tags are denoted by {~tag_name} and function as placeholders for
dynamic values provided at runtime.  Multiple pieces of html may be
defined in a single file.

For example, the hello.html file below defines three templates:
 hello
 hello#welcome
 hello#footer

The "hello" template is not used in this example but consists of all
text in the file, minus the {#subtemplate}...{#} definitions which are
stripped out.

Template references are built using the filename minus the extension.


------------------------- hello.html

{!-- This template is used on the welcome page --}

{#welcome}
Hello {~name}!  Welcome to the site!
{#}

{#footer}
© 2007 Tom McClure.  All Rights Reserved.
{#}



------------------------- java
// Example template code:

TemplateSet html = getTemplates();
Chunk c = html.makeChunk("hello#welcome");
c.set("name", getName() );
return c.toString(); // returns "Hello Bob!  Welcome to the site!"


===================================== STYLE GUIDE

Template filenames should be named in lowercase with no periods before
the extension and no spaces anywhere.  Use underscores in place of
spaces where needed.

Example template filenames:

  welcome.html
  message.xml
  account_settings.html

Tags should follow the same guidelines.  Mixed case is allowed but
strongly discouraged, and the only legal punctuation is dashes and
underscores with underscores strongly preferred over dashes.  Spaces
are not legal inside a tag name.  Numbers are ok.

Example tags:

  {~name}
  {~home_phone}
  {~menu_bar}
  {~left_nav}
  {~header_1}

  {~results:Nothing found!}
  {~address_line_2:}

  {~price|sprintf($%,.2f)}
  {~url|s/ /%20/g}

  {+other_template}
  {+template_ref#fully_qualified}
  {+#subtemplate}

  {^alt_repository.template_name}
  {^include.template_ref}
  {^includeIf(~secure==yes).template_ref}

===================================== DEFAULTS

A tag can provide its own default value.  Here's the syntax:

  {~tag:DEFAULT VALUE}

Standard behavior when a tag value is null or never defined is for the
tag to pass through to the final output unchanged.  Tag preservation
is by design, since the output can then be used as a template or tag
value for re-processing in a "parent" context, ie later on where the
presentation code finally has access to an appropriate value for the
tag.

Tag preservation means less code.  Chunk does all the heavy lifting!
When you call Chunk's .toString() method, a tag resolution algorithm
recursively scans tag values for additional tags and attempts to
resolve any that it finds.

If you want a tag to disappear by default, supply a blank default value.
The tag will look like this:

  {~error_msg:}

Or if you're working with HTML, you could use a non-rendering comment:

  {~error_msg:<!-- no errors -->}

**********************************
Advanced Usage: Defaults may contain nested tags.  Nesting braces,
 however, is not allowed, and will result in a parsing error.

 For example, you could opt to show the username when no data is
 available for the full name:

 Correct: {~full_name:~username}
 INCORRECT: {~full_name:{~username}} <-- nested braces, will not work

 You can even use the special ^include.template_ref syntax (like in the
 intro_paragraph tag below) to designate a whole template or
 subtemplate to appear in the tag space by default.  Read the INCLUDES
 section to learn more.
**********************************

Notice how the third tag has an empty default value and disappears
in the final output, but the fourth tag "no_default" provides no
default and simply passes through into the final output.

------------------------- hello.html
Hello {~name:there}!  Welcome to {~site_name:the site}! {~friends:} {~no_default}

<p>{~intro_paragraph:^include.#default_intro}</p>

{#default_intro}
Here at Widgets of America, we specialize in widgets.  Nobody knows
widgets better than we do, and you can bet the widget farm that you
can't stump our <a href="{~webroot}/experts">Widget Experts</a>.
{#}


------------------------- java
// ...
Chunk c = html.makeChunk("hello");
// Normally there would be a bunch of c.set("tag_name", tagValue) calls here,
// but for this example we want to illustrate null tag behavior.
return c.toString(); // returns "Hello there!  Welcome to the site! {~no_default} ..."


===================================== MACROS

Chunk supports macros for achieving more complex behavior.

Macros are great for some element that is repeated over and over in
your project.  The best thing about macros is that the values for the
macro expansion are assigned directly in the template, not in the code.

Appropriate macro use can eliminate a lot of code, or can make your
templates easier to read.

Syntax:
{__macro_name__}
{=param1=}value1{=}
{=param2=}value2{=}
{__}

The end parameter markers "{=}" are optional
but the end macro marker "{__}" is required.

Example macro:

------------------------- hello.html

{#welcome}

{__box__}

{=box_title=}Welcome to {~site_name:the site}!{=}
{=box_content=}
Help!<br/>
<br/>

I'm trapped in a box!
{=}

{__}

This idea will appear to be outside the box.

{#}


------------------------- box.html

{!------------------------------------------------
  -- I hate writing HTML table code, so I just
  -- use this template over and over as a macro.
  ------------------------------------------------}

<table border="1" width="{~box_width:300}">
 <tr><td>{~box_title}</td></tr>
 <tr><td>{~box_content}</td></tr>
</table>

------------------------- java
Chunk c = html.makeChunk("hello#welcome");
return c.toString();

------------------------- output

<table border="1" width="300">
 <tr><td>Welcome to the site!</td></tr>
 <tr><td>
Help!<br/>
<br/>

I'm trapped in a box!
</td></tr>
</table>

This idea will appear to be outside the box.


===================================== INCLUDES

You don't have to use macros to provide simpler directives from the
template.  Want to just "include" another template?  It's easy!

The syntax* is:
 {^include.template_name}

To include a subtemplate:
 {^include.template_name#subtemplate_name}

When the subtemplate is defined in the same file, you may omit the template filename:
 {^include.#subtemplate_name}

An experimental branching syntax "includeIf(cond)" is available.
On template expansion, if the cond is true, the template is included.
Otherwise the entire tag resolves to the empty string.

There is no "else" clause but repeating the tag with the opposite
condition can achieve the same effect.

Example includeIf(...) uses:

{!-- only these simple conditions are supported --}

{!-- 1. if exists, if doesn't exist; ie, is non-null, is null --}
{^includeIf(~username).hello#hello_username}
{^includeIf(!username).hello#hello_anon}

{!-- 2. if equals, if doesn't equal --}
{^includeIf(~username==Bob).hello#hello_bob}
{^includeIf(~username!=Bob).hello#hello_anon}

{!-- 3. if matches regex, if doesn't match regex --}
{^includeIf(~username =~ /(jane|john)/i).hello#hello_janejohn}
{^includeIf(~username !~ /(jane|john)/i).hello#hello_anon}

Note that these examples are roughly equivalent to the more efficient
and more powerful (but less readable) "ondefined" and "onmatch" filters:
 {~username|ondefined(+tpl_a):+tpl_b}
 {~username|onmatch(/regex/,+tpl_a)nomatch(+tpl_b)}

The "+" is shorthand for ^include.  More about filters below.


* This shorthand include/includeIf syntax is supported but may reduce readability:
 {+my_content}       for {^include.my_content}
 {+(cond)my_content} for {^includeIf(cond).my_content}

The older syntax {~.include.xyz} or {~.includeIf(...).xyz} is still supported.

Keep in mind, the template parser only wakes up to do its "magic"
stuff when it sees the following triggers:
         {~...  {+...  {!--  {#...  {^...

------------------------- hello.html
{!-----------------------------------------------------}
{#welcome}

{^include.top_nav}

Hello {~name}!  Welcome to {~site_name:the site}!

{#}


------------------------- top_nav.html

{!-- put the sitewide navigation bar here --}
...


===================================== MACROS WITH INCLUDES

You can even combine macro and include syntax for clarity.

In this example the box content is defined in a separate subtemplate.

------------------------- hello.html

{#welcome}

{__box__}

{=box_title=}Welcome to {~site_name:the site}!{=}
{=box_content=}
 {^include.hello#box_text}
{=}

{__}

This idea will appear to be outside the box.

{#}


{#box_text}
Help!<br/>
<br/>

I'm trapped in a box!
{#}


===================================== FILTERS: TAG TRANSFORMATIONS

A limited number of text filters are available via the pipe (|) character.

Filters provide a way to alter/transform the tag value presentation
on-the-fly, directly in the template.

{~any_tag|qs} escapes a quoted string (don't "flub" -> don\'t \"flub\")
{~any_tag|uc} will transform the text to all uppercase
{~any_tag|lc} all lowercase
{~any_tag|md5} md5 hash (hex, or try md5base64)
{~any_tag|sha} sha-1 hash (hex, or try shabase64)
{~any_tag|base64} base64-encode
{~any_tag|base64decode} decode base64-encoded string
{~any_tag|url} url-encode (safe+for+query+strings)
{~any_tag|urldecode} url decode (eg: my%20string -> my string)
{~any_tag|html} escapes html (uses &amp; &lt; &gt; &quot; and &apos;)
{~any_tag|trim} removes leading and trailing whitespace
{~any_tag|s/[0-9]/#/g} perl-style search+replace with regular expressions
{~any_tag|sprintf(%05.3f)} applies sprintf formatting


New filters onmatch and ondefined open the door for xslt-style transforms.

A. ondefined(output)
  - if tag value is null or zero-length: display nothing
  - otherwise, display the specified output.

{~any_tag|ondefined(some text)}
{~any_tag|ondefined(~some_tag)}
{~any_tag|ondefined(+some_template)}
{~any_tag|ondefined(^some_external.content)}


B. onmatch(/RE/,output[,/RE2/,output2[,...]])
   onmatch(/RE/,output[,/RE2/,output2[,...]])nomatch(def_output)

  - if tag value is null or does not match any RE: display nothing (or def_output)
  - otherwise, display the specified output after the first matching regexp.

{~any_tag|onmatch(/a/,ABC)}
  null -> ""
  fox  -> ""
  a    -> "ABC"
  cat  -> "ABC"  (for exact matches, use /^a$/ syntax)

{~any_tag|onmatch(/ca/,Cat,/a/,ABC)nomatch(DEF)}
  null -> "DEF"
  fox  -> "DEF"
  a    -> "ABC"
  car  -> "Cat"
  bar  -> "ABC"

Note the optional nomatch clause.  The onmatch and ondefined output
arguments allow tag references and template references to be used in
place of output text.  For example:

{~any_tag|onmatch(/1/,+template_one,/2/,+template_two)nomatch(~errmsg)}
  1 -> include template "template_one"
  2 -> include template "template_two"
  3/null/etc. -> display the value of the {~errmsg} tag


Future ideas:
{~any_tag|usd} formats numbers as U.S. currency eg 30000 -> $30,000.00
 for now, just use {~amount|sprintf($%,.2f)}
{~any_tag|num(00.00)} applies DecimalFormat formatting
{~any_tag|date(yyyy-MM-dd)} applies SimpleDateFormat formatting

Pipe modifiers may be chained.  Multiple filters will be applied in
the order specified: {~any_tag|trim|lc|md5|uc}

MIXING FILTERS AND DEFAULTS

The pipe modifier may be mixed with the colon modifier.  The colon
value may be placed before or after the filters (but not inside a
filter chain).  Use the colon first to have the filter transform
applied to it, and last to skip the filter transform.

ie: {~any_tag:don't|qs} and {~any_tag|qs:don't} are not equivalent.
 When ~any_tag is null:
    {~any_tag:don't|qs} => don\'t
    {~any_tag|qs:don't} => don't

The filter is applied in both cases if any_tag is non-null, but the
default value is not filtered in the second case.

I am considering supporting a more legible sgml-inspired syntax, eg:
  {~any_tag ifnull="don't" transform="qs" transform_when_null="yes"} => don\'t
  {~any_tag ifnull="don't" transform="qs" transform_when_null="no"} => don't
but don't hold your breath.


===================================== EXTENDING THE SYSTEM

The include protocol is available standard, but you can easily whip up
your own class that provides template content.  It just has to
implement com.x5.template.ContentSource, which requires two methods,
fetch and getProtocol.

For instance, I have a class that fetches html from a wiki. The client
was familiar with creating and editing wiki pages and so instead of
building a content management system for his site content, I just
installed a wiki.  The wiki tags look like this: {^wiki.External_Page}

Here's an example that fetches templates from a database:

------------------------- DBTemplates.java

import com.x5.template.ContentSource;

public class DBTemplates implements ContentSource
{
    // ...

    public String fetch(String templateName)
    {
	return getFromDB(templateName);
    }

    public String getProtocol()
    {
	return "db";
    }

    // ...
}

------------------------- MyApp.java

// ...
// To provide your chunk with this new source of templates:

TemplateSet html = getTemplates();
Chunk c = html.makeChunk("hello#welcome");
c.set("name", getName() );

DBTemplates dbt = new DBTemplates();
c.addProtocol(dbt);

return c.toString();
// ...

------------------------- hello.html

{!-- the ^include.top_nav tag will pull from the standard template set --}
{!-- the ^db.scores tag will look in the database --}

{#welcome}
{^include.top_nav}

Hello {~name}!  Welcome to {~site_name:the site}!<br/>
<br/>

Here are today's sports scores:<br/>
{^db.scores}

{#}