diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c102fe7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+classes/*
+doc/*
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..70291f2
--- /dev/null
+++ b/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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..70c4d39
--- /dev/null
+++ b/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
+
+
+ This is my template. My favorite food is {{FOOD}}.
+
+
+```
+
+
+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
+
+
+ This is my template. My favorite food is cookies.
+
+
+```
+
+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
+
+
+ {{FAVORITE_SHOW}} is probably my favorite show.
+ {{#GOODSHOWS}}
+ {{SHOW}} is pretty good, too..
+ {{/GOODSHOWS}}
+
+
+```
+
+
+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
+
+
+ Happy Days is probably my favorite show.
+
+ M.A.S.H is pretty good, too..
+
+ A-Team is pretty good, too..
+
+
+```
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..c1049df
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/test.html b/res/test.html
new file mode 100644
index 0000000..acfdb0a
--- /dev/null
+++ b/res/test.html
@@ -0,0 +1,10 @@
+
+
+ Pico Template
+
+
+ This is my {{OBJECT1}}, this is my gun.
+{{#SECTION}} This is for {{OBJECT2}}!
+{{/SECTION}}
+
+
diff --git a/src/com/binarythought/picotemplate/Template.java b/src/com/binarythought/picotemplate/Template.java
new file mode 100644
index 0000000..958f734
--- /dev/null
+++ b/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.
+ * Basic usage:
+ *
Template template = new Template("I like {{FOOD}}.");
+ * TemplateDictionary dictionary = new TemplateDictionary();
+ * dictionary.put("food", "cookies");
+ * String result = template.parse(dictionary);
+ *
Value of result : I like cookies.
+ */
+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 parsedTemplate = new ArrayList();
+ Stack sections = new Stack();
+ 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.
+ * This method is threadsafe.
+ * @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 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;
+ }
+}
diff --git a/src/com/binarythought/picotemplate/TemplateDictionary.java b/src/com/binarythought/picotemplate/TemplateDictionary.java
new file mode 100644
index 0000000..57c908d
--- /dev/null
+++ b/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. All methods that use identifying keys under this object
+ * are case-insensitive. This means that "MY_VAR" is the same as "my_var"
+ * for both section names and variable names.
+ * At the most basic level, TemplateDictionary can be used with
+ * Template to create simple fill-in-the-blank style templates.
+ *
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 dictionary;
+ private Map> children;
+ private Set sections;
+ private TemplateDictionary parent;
+
+ /**
+ * Instantiate an empty TemplateDictionary.
+ */
+ public TemplateDictionary(){ this(null); }
+
+
+ private TemplateDictionary(TemplateDictionary parent)
+ {
+ this.parent = parent;
+
+ dictionary = new HashMap();
+ children = new HashMap>();
+ sections = new HashSet();
+ }
+
+
+ /**
+ * 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 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 list = new LinkedList();
+ 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());
+ }
+}
diff --git a/src/com/binarythought/picotemplate/TemplateNode.java b/src/com/binarythought/picotemplate/TemplateNode.java
new file mode 100644
index 0000000..401517a
--- /dev/null
+++ b/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; }
+}
diff --git a/src/com/binarythought/picotemplate/example/TemplateExample.java b/src/com/binarythought/picotemplate/example/TemplateExample.java
new file mode 100644
index 0000000..ee85e09
--- /dev/null
+++ b/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");
+ }
+
+}