package org.freehep.util.template; import java.io.FilterReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; /** A very simple template engine. The template engine takes input and transforms * it by looking for tags in the document, and replacing them with values provided * by a set of ValueProviders. * <p> * Example of HTML template:<p> * <code> <html><br> * <head><br> * <title><b>{v:title}</b></title> <br> * </head> <br> * <body><br> * Welcome <b>{v:name}</b> to Simple Template Example<br> * <table><br> * <tr bgcolor="d0d0d0"><th>Author</th><th>Title</th><th>Year</th></tr><br> * <b><t:book></b><br> * <tr><br> * <td><a href="#"><b>{v:author}</b></a></td><br> * <td><a href="#"><b>{v:title}</b></a></td><br> * <td><a href="#"><b>{v:year}</b></a></td><br> * </tr><br> * <b></t:book></b><br> * </table><br> * </body> <br> * </html> </code> * <p> * The design (although not the code) was inspired by the JByte template engine * @link {http://javaby.sourceforge.net/}. * @author tonyj * @version $Id: TemplateEngine.java 8584 2006-08-10 23:06:37Z duns $ */ public class TemplateEngine { /** Apply the template to a document. * @param in The Reader from which the document will be read. * @return A Reader from which the filtered document can be read. */ public Reader filter(Reader in) { return new TemplateReader(in,providers); } /** Add a value provider to the set of value providers * @param p The ValueProvider to add */ public void addValueProvider(ValueProvider p) { providers.add(p); } /** Remove a value provider from the list of value providers. * @param p The value provider to remove. */ public void removeValueProvider(ValueProvider p) { providers.remove(p); } private List providers = new ArrayList(); private static class TemplateReader extends FilterReader { TemplateReader(Reader in, List providers) { super(in); this.providers = providers; } public int read() throws IOException { outer: for (;;) { if (subReader != null) { int c = subReader.read(); if (c >= 0) return c; subReader = null; } if (templateIterator != null) { if (templateIterator.hasNext()) { ValueProvider vp = (ValueProvider) templateIterator.next(); subReader = new TemplateReader(new StringReader(buf.toString()),Collections.singletonList(vp)); continue; } else { templateIterator = null; buf.delete(0, buf.length()); } } if (state < 0) { int rc = buf.charAt(buf.length()+state++); if (state == 0) buf.delete(0,buf.length()); return rc; } int c = in.read(); if (c<0) { state = -buf.length(); if (state == 0) return -1; continue; } if (templateMarker > 0) { buf.append((char) c); if (state == 0 && c == '<') state++; else if (state == 1 && c == '/') state++; else if (state > 1 && buf.charAt(state-1) == c) state++; else state = 0; if (state-1 == templateMarker) // Found end of template { String symb = buf.substring(3,templateMarker-1); for (int i=0; i<providers.size(); i++) { ValueProvider vp = (ValueProvider) providers.get(i); List list = vp.getValues(symb); if (list == null) continue; buf.delete(buf.length()-state,buf.length()); buf.delete(0, templateMarker); templateIterator = list.iterator(); templateMarker = 0; state = 0; continue outer; } buf.delete(0, buf.length()); state = 0; templateMarker = 0; continue; } } else if (state == 0 && (c!='{' && c!='<')) return c; else if (state == 0) { buf.append((char) c); template = c == '<'; state++; } else if (state == 1 && (template ? c=='t' : c=='v')) { buf.append((char) c); state++; } else if (state == 2 && c==':') { buf.append((char) c); state++; } else if (state > 2 && (template ? c!='>' : c!='}')) { state++; buf.append((char) c); } else if (state > 2) { if (template) { // Everything must go into the string buffer until we // find end of template buf.append((char) c); state++; templateMarker = state; state = 0; } else { String symb = buf.substring(3); for (int i=0; i<providers.size(); i++) { ValueProvider vp = (ValueProvider) providers.get(i); String result = vp.getValue(symb); if (result == null) continue; buf.replace(0, buf.length(),result); state = -buf.length(); continue outer; } state = 0; buf.delete(0, buf.length()); continue; } } else { buf.append((char)c); state = - buf.length(); continue; } } } public int read(char[] buf, int off, int len) throws IOException { for (int i=0; i<len; i++) { int b = read(); if (b < 0) return i == 0 ? -1 : i; buf[i+off] = (char) b; } return len; } private int state = 0; private boolean template; private int templateMarker = 0; private StringBuffer buf = new StringBuffer(); private Iterator templateIterator = null; private Reader subReader = null; private List providers; } }