1
0

Merge from existing repo

This commit is contained in:
2012-02-13 02:07:33 +00:00
committed by cdramey
parent 649512ba39
commit 99c7704f83
9 changed files with 642 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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