9 changed files with 642 additions and 0 deletions
-
2.gitignore
-
30LICENSE
-
112README.md
-
46build.xml
-
10res/test.html
-
184src/com/binarythought/picotemplate/Template.java
-
162src/com/binarythought/picotemplate/TemplateDictionary.java
-
48src/com/binarythought/picotemplate/TemplateNode.java
-
48src/com/binarythought/picotemplate/example/TemplateExample.java
@ -0,0 +1,2 @@ |
|||
classes/* |
|||
doc/* |
@ -0,0 +1,30 @@ |
|||
Copyright (c) 2009,2011 Christopher Ramey |
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions |
|||
are met: |
|||
|
|||
Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
|
|||
Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
|
|||
Neither the name of the picotemplate nor the names of its |
|||
contributors may be used to endorse or promote products derived |
|||
from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY |
|||
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY |
|||
OF SUCH DAMAGE. |
@ -0,0 +1,112 @@ |
|||
picotemplate |
|||
============ |
|||
|
|||
`picotemplate` is a simple, tiny C-Template workalike written in java |
|||
with the intention of being as high performance as possible. |
|||
`picotemplate` is very small, only three class files and is BSD licensed. |
|||
|
|||
basic usage |
|||
----------- |
|||
|
|||
First, create your template: |
|||
```html |
|||
<html> |
|||
<body> |
|||
This is my template. My favorite food is {{FOOD}}. |
|||
</body> |
|||
</html> |
|||
``` |
|||
|
|||
|
|||
Import the required classes: |
|||
```java |
|||
import com.binarythought.picotemplate.Template; |
|||
import com.binarythought.picotemplate.TemplateDictionary; |
|||
``` |
|||
|
|||
|
|||
Create your template and template dictionary: |
|||
```java |
|||
Template template = new Template(new File("mytemplate.tpl")); |
|||
TemplateDictionary dict = new TemplateDictionary(); |
|||
``` |
|||
|
|||
|
|||
Assign a value to the "food" variable (Unassigned variables are not shown): |
|||
```java |
|||
dict.put("food", "cookies"); |
|||
``` |
|||
|
|||
|
|||
And parse your template: |
|||
```java |
|||
String result = template.parse(dict); |
|||
``` |
|||
|
|||
|
|||
And the result: |
|||
```html |
|||
<html> |
|||
<body> |
|||
This is my template. My favorite food is cookies. |
|||
</body> |
|||
</html> |
|||
``` |
|||
|
|||
advanced usage |
|||
-------------- |
|||
|
|||
picotemplate can selectively show areas of static content, called "sections". |
|||
It can also loop over these sections using child dictionaries. Consider the |
|||
following example: |
|||
```html |
|||
<html> |
|||
<body> |
|||
{{FAVORITE_SHOW}} is probably my favorite show. |
|||
{{#GOODSHOWS}} |
|||
{{SHOW}} is pretty good, too.. |
|||
{{/GOODSHOWS}} |
|||
</body> |
|||
</html> |
|||
``` |
|||
|
|||
|
|||
Create your template and template dictionary as usual: |
|||
```java |
|||
Template template = new Template(new File("mytemplate.tpl")); |
|||
TemplateDictionary dict = new TemplateDictionary(); |
|||
``` |
|||
|
|||
|
|||
Define our favorite show: |
|||
```java |
|||
dict.put("favorite_show", "Happy Days"); |
|||
``` |
|||
|
|||
Now show the section called "goodshows" (Sections are by default hidden, and |
|||
must be explicitly told to be shown): |
|||
```java |
|||
dict.show("goodshows"); |
|||
``` |
|||
|
|||
And add some shows for it to loop over: |
|||
```java |
|||
TemplateDictionary child1 = dict.createChild("goodshows"); |
|||
child1.put("show", "M.A.S.H"); |
|||
TemplateDictionary child2 = dict.createChild("goodshows"); |
|||
child2.put("show", "A-Team"); |
|||
``` |
|||
|
|||
|
|||
And the result: |
|||
```html |
|||
<html> |
|||
<body> |
|||
Happy Days is probably my favorite show. |
|||
|
|||
M.A.S.H is pretty good, too.. |
|||
|
|||
A-Team is pretty good, too.. |
|||
</body> |
|||
</html> |
|||
``` |
@ -0,0 +1,46 @@ |
|||
<?xml version="1.0"?> |
|||
<project name="picotemplate" default="compile"> |
|||
<property name="version.num" value="1.0" /> |
|||
|
|||
<target name="clean"> |
|||
<delete file="${ant.project.name}-${version.num}.jar" /> |
|||
<delete includeemptydirs="true"> |
|||
<fileset dir="doc" includes="**/*" /> |
|||
<fileset dir="classes" includes="**/*" /> |
|||
</delete> |
|||
</target> |
|||
|
|||
<target name="compile"> |
|||
<mkdir dir="classes" /> |
|||
<javac includeantruntime="false" target="1.5" |
|||
srcdir="src" destdir="classes" /> |
|||
</target> |
|||
|
|||
<target name="jar" depends="compile"> |
|||
<delete file="${ant.project.name}-${version.num}.jar" /> |
|||
<jar destfile="${ant.project.name}-${version.num}.jar"> |
|||
<fileset dir="classes"> |
|||
<exclude name="**/example/**" /> |
|||
</fileset> |
|||
</jar> |
|||
</target> |
|||
|
|||
<target name="run" depends="compile"> |
|||
<java classname="com.binarythought.picotemplate.example.TemplateExample" |
|||
fork="true" maxmemory="64M"> |
|||
<classpath> |
|||
<dirset dir="classes" /> |
|||
</classpath> |
|||
</java> |
|||
</target> |
|||
|
|||
<target name="doc"> |
|||
<mkdir dir="doc" /> |
|||
<javadoc destdir="doc"> |
|||
<fileset dir="src"> |
|||
<exclude name="**/example/**" /> |
|||
</fileset> |
|||
</javadoc> |
|||
</target> |
|||
|
|||
</project> |
@ -0,0 +1,10 @@ |
|||
<html> |
|||
<head> |
|||
<title>Pico Template</title> |
|||
</head> |
|||
<body> |
|||
This is my {{OBJECT1}}, this is my gun.<br/> |
|||
{{#SECTION}} This is for {{OBJECT2}}!<br/> |
|||
{{/SECTION}} |
|||
</body> |
|||
</html> |
@ -0,0 +1,184 @@ |
|||
package com.binarythought.picotemplate; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Stack; |
|||
import java.util.regex.Pattern; |
|||
import java.util.regex.Matcher; |
|||
import java.io.FileReader; |
|||
import java.io.File; |
|||
|
|||
/** |
|||
* Template is the base class for picotemplate. It it used to generate |
|||
* html or text from a supplied template and dictionary. |
|||
* <p>Basic usage: |
|||
* <p><code>Template template = new Template("I like {{FOOD}}.");<br> |
|||
* TemplateDictionary dictionary = new TemplateDictionary();<br> |
|||
* dictionary.put("food", "cookies");<br> |
|||
* String result = template.parse(dictionary);</code> |
|||
* <p>Value of result : <code>I like cookies.</code> |
|||
*/ |
|||
public class Template { |
|||
private static final Pattern pattern = Pattern.compile( |
|||
"\\{\\{(#|/){0,1}(\\w+)\\}\\}", Pattern.CANON_EQ |
|||
); |
|||
|
|||
private TemplateNode parsedTemplate[]; |
|||
private String originalTemplate; |
|||
|
|||
|
|||
/** |
|||
* Instantiate and compile a new template with the template provided. |
|||
* @param template String containing the template. |
|||
*/ |
|||
public Template(String template) throws Exception |
|||
{ |
|||
this.originalTemplate = template; |
|||
this.parsedTemplate = initTemplate(template); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Instantiate and compile a new template with the template provided. |
|||
* @param file File containing the template. |
|||
*/ |
|||
public Template(File file) throws Exception |
|||
{ |
|||
if(file == null || !file.canRead()){ |
|||
throw new Exception("Cannot read "+file.getName()); |
|||
} |
|||
|
|||
FileReader reader = new FileReader(file); |
|||
StringBuilder b = new StringBuilder(); |
|||
|
|||
char buf[] = new char[1024]; |
|||
for(int i=0; (i = reader.read(buf)) != -1;){ |
|||
b.append(buf, 0, i); |
|||
} |
|||
|
|||
reader.close(); |
|||
this.originalTemplate = b.toString(); |
|||
this.parsedTemplate = initTemplate(this.originalTemplate); |
|||
} |
|||
|
|||
|
|||
private static TemplateNode[] initTemplate(String template) throws Exception |
|||
{ |
|||
ArrayList<TemplateNode> parsedTemplate = new ArrayList<TemplateNode>(); |
|||
Stack<String> sections = new Stack<String>(); |
|||
Matcher match = pattern.matcher(template); |
|||
int lastpos = 0; |
|||
|
|||
while(match.find()){ |
|||
String segment = template.substring(lastpos,match.start()); |
|||
if(segment.length() > 0){ |
|||
parsedTemplate.add(new TemplateNode(TemplateNode.PLAIN, segment)); |
|||
} |
|||
|
|||
if("#".equals(match.group(1))){ |
|||
sections.push(match.group(2)); |
|||
parsedTemplate.add( |
|||
new TemplateNode(TemplateNode.SECTION_START, match.group(2)) |
|||
); |
|||
} else if("/".equals(match.group(1))){ |
|||
parsedTemplate.add( |
|||
new TemplateNode(TemplateNode.SECTION_END, match.group(2)) |
|||
); |
|||
if(sections.empty() || !sections.pop().equals(match.group(2))){ |
|||
throw new Exception("Out of turn section termation at "+match.start()); |
|||
} |
|||
} else { |
|||
parsedTemplate.add( |
|||
new TemplateNode(TemplateNode.VARIABLE, match.group(2)) |
|||
); |
|||
} |
|||
lastpos = match.end(); |
|||
} |
|||
|
|||
String segment = template.substring(lastpos, template.length()); |
|||
if(segment.length() > 0){ |
|||
parsedTemplate.add(new TemplateNode(TemplateNode.PLAIN, segment)); |
|||
} |
|||
|
|||
if(!sections.empty()){ |
|||
throw new Exception("Unterminated section in template"); |
|||
} |
|||
|
|||
return parsedTemplate.toArray(new TemplateNode[0]); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Parse this template with the provided dictionary and output it to a string. |
|||
* <p><b>This method is threadsafe.</b> |
|||
* @param dict Template dictionary to parse against. |
|||
* @return String containing the parsed result of the template. |
|||
*/ |
|||
public String parse(TemplateDictionary dict) |
|||
{ |
|||
StringBuilder b = new StringBuilder(); |
|||
|
|||
if(dict != null){ parseTemplate(dict, b, parsedTemplate, 0); } |
|||
else { parseTemplate(b, parsedTemplate); } |
|||
|
|||
return b.toString(); |
|||
} |
|||
|
|||
|
|||
private static int parseTemplate(StringBuilder output, TemplateNode template[]) |
|||
{ |
|||
for(int i = 0; i < template.length; i++){ |
|||
if(template[i].getNodeType() == TemplateNode.PLAIN){ |
|||
output.append(template[i].getContent()); |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
|
|||
private static int parseTemplate(TemplateDictionary dict, StringBuilder output, TemplateNode template[], int i) |
|||
{ |
|||
for(;i < template.length; i++){ |
|||
switch(template[i].getNodeType()){ |
|||
case TemplateNode.PLAIN: |
|||
output.append(template[i].getContent()); |
|||
break; |
|||
|
|||
case TemplateNode.VARIABLE: |
|||
output.append(dict.get(template[i].getContent())); |
|||
break; |
|||
|
|||
case TemplateNode.SECTION_START: |
|||
if(dict.isShown(template[i].getContent())){ |
|||
List<TemplateDictionary> l = ( |
|||
dict.getParent() == null ? dict.getChild(template[i].getContent()) : |
|||
dict.getParent().getChild(template[i].getContent()) |
|||
); |
|||
|
|||
if(l == null){ |
|||
i = parseTemplate(dict, output, template, i+1); |
|||
} else { |
|||
int last = 0; |
|||
for(TemplateDictionary d : l){ |
|||
last = parseTemplate(d, output, template, i+1); |
|||
} |
|||
i = last; |
|||
} |
|||
} else { |
|||
for(int c=1; c > 0;){ |
|||
i++; |
|||
switch(template[i].getNodeType()){ |
|||
case TemplateNode.SECTION_START: c++; break; |
|||
case TemplateNode.SECTION_END: c--; break; |
|||
} |
|||
} |
|||
} |
|||
break; |
|||
|
|||
case TemplateNode.SECTION_END: return i; |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
} |
@ -0,0 +1,162 @@ |
|||
package com.binarythought.picotemplate; |
|||
|
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.HashMap; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.LinkedList; |
|||
import java.util.Collections; |
|||
|
|||
|
|||
/** |
|||
* TemplateDictionary is used to assign variables and show sections |
|||
* inside templates. It is functionally similar to a HashMap, although |
|||
* not a decendent. <b>All methods that use identifying keys under this object |
|||
* are case-insensitive.</b> This means that "MY_VAR" is the same as "my_var" |
|||
* for both section names and variable names. |
|||
* <p>At the most basic level, TemplateDictionary can be used with |
|||
* Template to create simple fill-in-the-blank style templates. |
|||
* <p>More advanced functionality is available, allowing developers |
|||
* to toggle shown sections and loop over sections using child dictionaries. |
|||
* @see Template |
|||
*/ |
|||
public class TemplateDictionary |
|||
{ |
|||
private Map<String,String> dictionary; |
|||
private Map<String,List<TemplateDictionary>> children; |
|||
private Set<String> sections; |
|||
private TemplateDictionary parent; |
|||
|
|||
/** |
|||
* Instantiate an empty TemplateDictionary. |
|||
*/ |
|||
public TemplateDictionary(){ this(null); } |
|||
|
|||
|
|||
private TemplateDictionary(TemplateDictionary parent) |
|||
{ |
|||
this.parent = parent; |
|||
|
|||
dictionary = new HashMap<String,String>(); |
|||
children = new HashMap<String,List<TemplateDictionary>>(); |
|||
sections = new HashSet<String>(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Get the parent dictionary to this one, or null if one does not exist. |
|||
* @return This dictionary's parent dictionary. |
|||
*/ |
|||
public TemplateDictionary getParent(){ return parent; } |
|||
|
|||
|
|||
/** |
|||
* Show a specific section. This method is case-insensitive. |
|||
* @param name The section to show. |
|||
*/ |
|||
public void show(String name){ sections.add(name.toUpperCase()); } |
|||
|
|||
|
|||
/** |
|||
* Hide a specific section. This method is case-insensitive. |
|||
* @param name The section to hide. |
|||
*/ |
|||
public void hide(String name){ sections.remove(name.toUpperCase()); } |
|||
|
|||
|
|||
/** |
|||
* Checks if a section is hidden. This method is case-insensitive. |
|||
* @param name The section name checking against. |
|||
* @return True if a section is hidden, false if it is not. |
|||
*/ |
|||
public boolean isHidden(String name){ return !sections.contains(name.toUpperCase()); } |
|||
|
|||
|
|||
/** |
|||
* Checks if a section is shown. This method is case-insensitive. |
|||
* @param name The section name checking against. |
|||
* @return True if this section is shown, false if it is not. |
|||
*/ |
|||
public boolean isShown(String name){ return sections.contains(name.toUpperCase()); } |
|||
|
|||
|
|||
/** |
|||
* Provides a list of children identified by a case-insensitive section name. |
|||
* @param name The section name checking against. |
|||
* @return List of children dictionaries identified by specific section name or null if there are none. |
|||
*/ |
|||
public List<TemplateDictionary> getChild(String name){ |
|||
if(children.containsKey(name.toUpperCase())){ |
|||
return children.get(name.toUpperCase()); |
|||
} else { return null; } |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Remove all of the child dictionaries under this one identified by |
|||
* a specific section name. |
|||
* @param name Case-insensitive section name's children to remove. |
|||
*/ |
|||
public void removeChildren(String name){ |
|||
children.remove(name.toUpperCase()); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Create a child dictionary underneath this one for use with a specific |
|||
* section. |
|||
* @param name Case-insensitive section name for the new dictionary. |
|||
* @return TemplateDictionary for specific section. |
|||
*/ |
|||
public TemplateDictionary createChild(String name){ |
|||
TemplateDictionary child = new TemplateDictionary(this); |
|||
|
|||
if(!children.containsKey(name.toUpperCase())){ |
|||
List<TemplateDictionary> list = new LinkedList<TemplateDictionary>(); |
|||
children.put(name.toUpperCase(), list); |
|||
} |
|||
|
|||
children.get(name.toUpperCase()).add(child); |
|||
return child; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Gets the value of a variable by key. This method also ascends into parent |
|||
* dictionaries looking for the key. |
|||
* @param key Case-insensitive Key to look for. |
|||
* @return Value of key as string, or an empty string if the key is not found. |
|||
*/ |
|||
public String get(String key) |
|||
{ |
|||
if(dictionary.containsKey(key.toUpperCase())){ |
|||
return dictionary.get(key.toUpperCase()); |
|||
} else if(parent != null){ |
|||
return parent.get(key); |
|||
} else { |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Set the value of a variable. |
|||
* @param key Case-insensitive key that identifies this variable. |
|||
* @param val Value of this variable. |
|||
*/ |
|||
public void put(String key, String val) |
|||
{ |
|||
dictionary.put(key.toUpperCase(), val); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Remove a variable. |
|||
* @param key Case-insensitive key to remove. |
|||
*/ |
|||
public void remove(String key) |
|||
{ |
|||
dictionary.remove(key.toUpperCase()); |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
package com.binarythought.picotemplate; |
|||
|
|||
|
|||
/** |
|||
* Groups of TemplateNodes compose a template once it's compiled. |
|||
* This class shouldn't be used directly. |
|||
* @see Template |
|||
*/ |
|||
public class TemplateNode |
|||
{ |
|||
/** Plain node type */ |
|||
public static final int PLAIN = 0; |
|||
/** Variable node type */ |
|||
public static final int VARIABLE = 1; |
|||
/** Start of section node type */ |
|||
public static final int SECTION_START = 2; |
|||
/** End of section node type */ |
|||
public static final int SECTION_END = 3; |
|||
|
|||
private int nodeType; |
|||
private String content; |
|||
|
|||
|
|||
/** |
|||
* Instantiate new node. |
|||
* @param nodeType Type of node |
|||
* @param content Content of this node. |
|||
*/ |
|||
public TemplateNode(int nodeType, String content) |
|||
{ |
|||
this.nodeType = nodeType; |
|||
this.content = content; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Get the type of this node |
|||
* @return Type of this node. |
|||
*/ |
|||
public int getNodeType(){ return nodeType; } |
|||
|
|||
|
|||
/** |
|||
* Get the content of this node. |
|||
* @return Content of this node. |
|||
*/ |
|||
public String getContent(){ return content; } |
|||
} |
@ -0,0 +1,48 @@ |
|||
package com.binarythought.picotemplate.example; |
|||
|
|||
import com.binarythought.picotemplate.Template; |
|||
import com.binarythought.picotemplate.TemplateDictionary; |
|||
import java.io.File; |
|||
|
|||
|
|||
public class TemplateExample { |
|||
public static void main(String args[]) throws Exception |
|||
{ |
|||
Template template = new Template(new File("res/test.html")); |
|||
|
|||
TemplateDictionary dict = new TemplateDictionary(); |
|||
dict.put("object1", "rifle"); |
|||
|
|||
dict.show("section"); |
|||
TemplateDictionary d1 = dict.createChild("section"); |
|||
d1.put("object2", "fighting"); |
|||
TemplateDictionary d2 = dict.createChild("section"); |
|||
d2.put("object2", "fun"); |
|||
|
|||
System.out.println("*** Parsed Template: "); |
|||
System.out.println(template.parse(dict)); |
|||
|
|||
long t = System.currentTimeMillis(); |
|||
template.parse(dict); |
|||
System.out.println("1 Template: "+(System.currentTimeMillis()-t)+"ms"); |
|||
|
|||
t = System.currentTimeMillis(); |
|||
for(int i=0;i < 1000; i++){ |
|||
template.parse(dict); |
|||
} |
|||
System.out.println("1000 Templates: "+(System.currentTimeMillis()-t)+"ms"); |
|||
|
|||
t = System.currentTimeMillis(); |
|||
for(int i=0;i < 10000; i++){ |
|||
template.parse(dict); |
|||
} |
|||
System.out.println("10000 Templates: "+(System.currentTimeMillis()-t)+"ms"); |
|||
|
|||
t = System.currentTimeMillis(); |
|||
for(int i=0;i < 100000; i++){ |
|||
template.parse(dict); |
|||
} |
|||
System.out.println("100000 Templates: "+(System.currentTimeMillis()-t)+"ms"); |
|||
} |
|||
|
|||
} |
Reference in new issue