From 99c7704f831a1623ee37027ab5116aa6bc4b0174 Mon Sep 17 00:00:00 2001 From: Christopher Ramey Date: Mon, 13 Feb 2012 02:07:33 +0000 Subject: [PATCH] Merge from existing repo --- .gitignore | 2 + LICENSE | 30 +++ README.md | 112 +++++++++++ build.xml | 46 +++++ res/test.html | 10 + .../binarythought/picotemplate/Template.java | 184 ++++++++++++++++++ .../picotemplate/TemplateDictionary.java | 162 +++++++++++++++ .../picotemplate/TemplateNode.java | 48 +++++ .../picotemplate/example/TemplateExample.java | 48 +++++ 9 files changed, 642 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.xml create mode 100644 res/test.html create mode 100644 src/com/binarythought/picotemplate/Template.java create mode 100644 src/com/binarythought/picotemplate/TemplateDictionary.java create mode 100644 src/com/binarythought/picotemplate/TemplateNode.java create mode 100644 src/com/binarythought/picotemplate/example/TemplateExample.java 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"); + } + +}