Browse Source

Merge from existing repo

master
Christopher Ramey 12 years ago
committed by cdramey
parent
commit
99c7704f83
  1. 2
      .gitignore
  2. 30
      LICENSE
  3. 112
      README.md
  4. 46
      build.xml
  5. 10
      res/test.html
  6. 184
      src/com/binarythought/picotemplate/Template.java
  7. 162
      src/com/binarythought/picotemplate/TemplateDictionary.java
  8. 48
      src/com/binarythought/picotemplate/TemplateNode.java
  9. 48
      src/com/binarythought/picotemplate/example/TemplateExample.java

2
.gitignore

@ -0,0 +1,2 @@
classes/*
doc/*

30
LICENSE

@ -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.

112
README.md

@ -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>
```

46
build.xml

@ -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>

10
res/test.html

@ -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>

184
src/com/binarythought/picotemplate/Template.java

@ -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;
}
}

162
src/com/binarythought/picotemplate/TemplateDictionary.java

@ -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());
}
}

48
src/com/binarythought/picotemplate/TemplateNode.java

@ -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; }
}

48
src/com/binarythought/picotemplate/example/TemplateExample.java

@ -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");
}
}