Java tutorial
/** * Java Page Templates * Copyright (C) 2004 webslingerZ, inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.shopximity.jpt; import bsh.Interpreter; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.net.URI; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; import java.util.TreeMap; import javax.xml.parsers.SAXParser; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Namespace; import org.dom4j.Node; import org.dom4j.QName; import org.dom4j.io.DOMReader; import org.dom4j.io.SAXReader; import org.dom4j.io.SAXWriter; import org.dom4j.io.OutputFormat; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.AttributesImpl; /** * @author <a href="mailto:rossi@webslingerZ.com">Chris Rossi</a> * @version $Revision: 1.15 $ */ public class PageTemplateImpl implements PageTemplate { private URL url; private Document template; //private String talNamespacePrefix = "tal"; //private String metalNamespacePrefix = "metal"; // Map of macros contained in this template Map macros = null; public PageTemplateImpl(URL url, SAXParser saxParser) throws PageTemplateException { try { this.url = url; SAXReader reader = new SAXReader(saxParser); template = reader.read(url); } catch (Exception e) { throw new PageTemplateException(e); } } public void process(OutputStream output, Object context) throws SAXException, PageTemplateException, IOException { process(output, context, null); } static final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance(); public void process(OutputStream output, Object context, Map dictionary) throws SAXException, PageTemplateException, IOException { try { TransformerHandler handler = factory.newTransformerHandler(); Transformer transformer = handler.getTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "html"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); handler.setResult(new StreamResult(output)); process(handler, handler, context, dictionary); } catch (TransformerConfigurationException e) { throw new PageTemplateException(e); } } public void process(ContentHandler contentHandler, LexicalHandler lexicalHandler, Object context, Map dictionary) throws Exception { try { // Initialize the bean shell Interpreter beanShell = new Interpreter(); if (dictionary != null) { for (Iterator i = dictionary.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); beanShell.set((String) entry.getKey(), entry.getValue()); } } beanShell.set("here", context); beanShell.set("template", this); beanShell.set("resolver", new URIResolver(new URI(url.toString()))); beanShell.set("bool", new BoolHelper()); beanShell.set("math", new MathHelper()); beanShell.set("date", new DateHelper()); Element root = template.getRootElement(); contentHandler.startDocument(); processElement(root, contentHandler, lexicalHandler, beanShell, new Stack()); contentHandler.endDocument(); } catch (bsh.EvalError e) { throw new PageTemplateException(e); } } private Map namespaces = new TreeMap(); private String getNamespaceURIFromPrefix(String prefix) { String uri = (String) namespaces.get(prefix); return uri == null ? "" : uri; } private void processElement(Element element, ContentHandler contentHandler, LexicalHandler lexicalHandler, Interpreter beanShell, Stack slotStack) throws SAXException, PageTemplateException, IOException { // Get attributes Expressions expressions = new Expressions(); AttributesImpl attributes = getAttributes(element, expressions); // Process instructions // use macro if (expressions.useMacro != null) { processMacro(expressions.useMacro, element, contentHandler, lexicalHandler, beanShell, slotStack); return; } // fill slot if (expressions.defineSlot != null) { //System.err.println( "fill slot: " + expressions.defineSlot ); if (!slotStack.isEmpty()) { Map slots = (Map) slotStack.pop(); Slot slot = (Slot) slots.get(expressions.defineSlot); //System.err.println( "slot: " + slot ); if (slot != null) { slot.process(contentHandler, lexicalHandler, beanShell, slotStack); slotStack.push(slots); return; } // else { use content in macro } slotStack.push(slots); } else { throw new PageTemplateException("slot definition not allowed outside of macro"); } } // define if (expressions.define != null) { processDefine(expressions.define, beanShell); } // condition if (expressions.condition != null && !Expression.evaluateBoolean(expressions.condition, beanShell)) { // Skip this element (and children) return; } // repeat Loop loop = new Loop(expressions.repeat, beanShell); while (loop.repeat(beanShell)) { // content or replace Object jptContent = null; if (expressions.content != null) { jptContent = processContent(expressions.content, beanShell); } // attributes if (expressions.attributes != null) { processAttributes(attributes, expressions.attributes, beanShell); } // omit-tag boolean jptOmitTag = false; if (expressions.omitTag != null) { if (expressions.omitTag.equals("")) { jptOmitTag = true; } else { jptOmitTag = Expression.evaluateBoolean(expressions.omitTag, beanShell); } } // Declare element Namespace namespace = element.getNamespace(); if (!jptOmitTag) { contentHandler.startElement(namespace.getURI(), element.getName(), element.getQualifiedName(), attributes); } // Process content if (jptContent != null) { // Content for this element has been generated dynamically if (jptContent instanceof HTMLFragment) { HTMLFragment html = (HTMLFragment) jptContent; html.toXhtml(contentHandler, lexicalHandler); } // plain text else { char[] text = ((String) jptContent).toCharArray(); contentHandler.characters(text, 0, text.length); } } else { defaultContent(element, contentHandler, lexicalHandler, beanShell, slotStack); } // End element if (!jptOmitTag) { contentHandler.endElement(namespace.getURI(), element.getName(), element.getQualifiedName()); } } } private void defaultContent(Element element, ContentHandler contentHandler, LexicalHandler lexicalHandler, Interpreter beanShell, Stack slotStack) throws SAXException, PageTemplateException, IOException { // Use default template content for (Iterator i = element.nodeIterator(); i.hasNext();) { Node node = (Node) i.next(); switch (node.getNodeType()) { case Node.ELEMENT_NODE: processElement((Element) node, contentHandler, lexicalHandler, beanShell, slotStack); break; case Node.TEXT_NODE: char[] text = node.getText().toCharArray(); contentHandler.characters(text, 0, text.length); break; case Node.COMMENT_NODE: char[] comment = node.getText().toCharArray(); lexicalHandler.comment(comment, 0, comment.length); break; case Node.CDATA_SECTION_NODE: lexicalHandler.startCDATA(); char[] cdata = node.getText().toCharArray(); contentHandler.characters(cdata, 0, cdata.length); lexicalHandler.endCDATA(); break; case Node.NAMESPACE_NODE: Namespace declared = (Namespace) node; //System.err.println( "Declared namespace: " + declared.getPrefix() + ":" + declared.getURI() ); namespaces.put(declared.getPrefix(), declared.getURI()); //if ( declared.getURI().equals( TAL_NAMESPACE_URI ) ) { // this.talNamespacePrefix = declared.getPrefix(); //} //else if (declared.getURI().equals( METAL_NAMESPACE_URI ) ) { // this.metalNamespacePrefix = declared.getPrefix(); //} break; case Node.ATTRIBUTE_NODE: // Already handled break; case Node.DOCUMENT_TYPE_NODE: case Node.ENTITY_REFERENCE_NODE: case Node.PROCESSING_INSTRUCTION_NODE: default: //System.err.println( "WARNING: Node type not supported: " + node.getNodeTypeName() ); } } } AttributesImpl getAttributes(Element element, Expressions expressions) throws PageTemplateException { AttributesImpl attributes = new AttributesImpl(); for (Iterator i = element.attributeIterator(); i.hasNext();) { Attribute attribute = (Attribute) i.next(); Namespace namespace = attribute.getNamespace(); //String prefix = namespace.getPrefix(); //System.err.println( "attribute: name=" + attribute.getName() + "\t" + // "qualified name=" + attribute.getQualifiedName() + "\t" + // "ns prefix=" + namespace.getPrefix() + "\t" + // "ns uri=" + namespace.getURI() ); //String qualifiedName = attribute.getName(); //String name = qualifiedName; //if ( qualifiedName.startsWith( prefix + ":" ) ) { // name = qualifiedName.substring( prefix.length() + 1 ); //} String name = attribute.getName(); // Handle JPT attributes //if ( prefix.equals( talNamespacePrefix ) ) { if (TAL_NAMESPACE_URI.equals(namespace.getURI())) { // tal:define if (name.equals("define")) { expressions.define = attribute.getValue(); } // tal:condition else if (name.equals("condition")) { expressions.condition = attribute.getValue(); } // tal:repeat else if (name.equals("repeat")) { expressions.repeat = attribute.getValue(); } // tal:content else if (name.equals("content")) { expressions.content = attribute.getValue(); } // tal:replace else if (name.equals("replace")) { if (expressions.omitTag == null) { expressions.omitTag = ""; } expressions.content = attribute.getValue(); } // tal:attributes else if (name.equals("attributes")) { expressions.attributes = attribute.getValue(); } // tal:omit-tag else if (name.equals("omit-tag")) { expressions.omitTag = attribute.getValue(); } // error else { throw new PageTemplateException("unknown tal attribute: " + name); } } //else if ( prefix.equals( metalNamespacePrefix ) ) else if (METAL_NAMESPACE_URI.equals(namespace.getURI())) { // metal:use-macro if (name.equals("use-macro")) { expressions.useMacro = attribute.getValue(); } // metal:define-slot else if (name.equals("define-slot")) { expressions.defineSlot = attribute.getValue(); } // metal:define-macro // metal:fill-slot else if (name.equals("define-macro") || name.equals("fill-slot")) { // these are ignored here, as they don't affect processing of current // template, but are called from other templates } // error else { throw new PageTemplateException("unknown metal attribute: " + name); } } // Pass on all other attributes else { //String qualifiedName = namespace.getPrefix() + ":" + name; attributes.addAttribute(namespace.getURI(), name, attribute.getQualifiedName(), "CDATA", attribute.getValue()); //attributes.addAttribute( getNamespaceURIFromPrefix(prefix), name, qualifiedName, "CDATA", attribute.getValue() ); } } return attributes; } private Object processContent(String expression, Interpreter beanShell) throws PageTemplateException { // Structured text, preserve xml structure if (expression.startsWith("structure ")) { expression = expression.substring("structure ".length()); Object content = Expression.evaluate(expression, beanShell); if (!(content instanceof HTMLFragment)) { content = new HTMLFragment(String.valueOf(content)); } return content; } else if (expression.startsWith("text ")) { expression = expression.substring("text ".length()); } return String.valueOf(Expression.evaluate(expression, beanShell)); } private void processDefine(String expression, Interpreter beanShell) throws PageTemplateException { try { ExpressionTokenizer tokens = new ExpressionTokenizer(expression, ';', true); while (tokens.hasMoreTokens()) { String variable = tokens.nextToken().trim(); int space = variable.indexOf(' '); if (space == -1) { throw new ExpressionSyntaxException("bad variable definition: " + variable); } String name = variable.substring(0, space); String valueExpression = variable.substring(space + 1).trim(); Object value = Expression.evaluate(valueExpression, beanShell); beanShell.set(name, value); } } catch (bsh.EvalError e) { throw new PageTemplateException(e); } } private void processAttributes(AttributesImpl attributes, String expression, Interpreter beanShell) throws PageTemplateException { ExpressionTokenizer tokens = new ExpressionTokenizer(expression, ';', true); while (tokens.hasMoreTokens()) { String attribute = tokens.nextToken().trim(); int space = attribute.indexOf(' '); if (space == -1) { throw new ExpressionSyntaxException("bad attributes expression: " + attribute); } String qualifiedName = attribute.substring(0, space); String valueExpression = attribute.substring(space + 1).trim(); Object value = Expression.evaluate(valueExpression, beanShell); //System.err.println( "attribute:\n" + qualifiedName + "\n" + valueExpression + "\n" + value ); removeAttribute(attributes, qualifiedName); if (value != null) { String name = ""; String uri = ""; int colon = qualifiedName.indexOf(":"); if (colon != -1) { String prefix = qualifiedName.substring(0, colon); name = qualifiedName.substring(colon + 1); uri = getNamespaceURIFromPrefix(prefix); } attributes.addAttribute(uri, name, qualifiedName, "CDATA", String.valueOf(value)); } } } private void removeAttribute(AttributesImpl attributes, String qualifiedName) { int index = attributes.getIndex(qualifiedName); if (index != -1) { attributes.removeAttribute(index); } } private void processMacro(String expression, Element element, ContentHandler contentHandler, LexicalHandler lexicalHandler, Interpreter beanShell, Stack slotStack) throws SAXException, PageTemplateException, IOException { Object object = Expression.evaluate(expression, beanShell); if (object == null) { throw new NoSuchPathException("could not find macro: " + expression); } if (object instanceof Macro) { // Find slots to fill inside this macro call Map slots = new HashMap(); findSlots(element, slots); // Slots filled in later templates (processed earlier) // Take precedence over slots filled in intermediate // templates. if (!slotStack.isEmpty()) { Map laterSlots = (Map) slotStack.peek(); slots.putAll(laterSlots); } slotStack.push(slots); //System.err.println( "found slots: " + slots.keySet() ); // Call macro Macro macro = (Macro) object; macro.process(contentHandler, lexicalHandler, beanShell, slotStack); } else { throw new PageTemplateException( "expression '" + expression + "' does not evaluate to macro: " + object.getClass().getName()); } } /** * With all of our namespace woes, getting an XPath expression * to work has proven futile, so we'll recurse through the tree * ourselves to find what we need. */ private void findSlots(Element element, Map slots) { //System.err.println( "element: " + element.getName() ); for (Iterator i = element.attributes().iterator(); i.hasNext();) { Attribute attribute = (Attribute) i.next(); //System.err.println( "\t" + attribute.getName() + "\t" + attribute.getQualifiedName() ); } // Look for our attribute //String qualifiedAttributeName = this.metalNamespacePrefix + ":fill-slot"; //String name = element.attributeValue( qualifiedAttributeName ); String name = element.attributeValue("fill-slot"); if (name != null) { slots.put(name, new SlotImpl(element)); } // Recurse into child elements for (Iterator i = element.elementIterator(); i.hasNext();) { findSlots((Element) i.next(), slots); } } /** * With all of our namespace woes, getting an XPath expression * to work has proven futile, so we'll recurse through the tree * ourselves to find what we need. */ private void findMacros(Element element, Map macros) { // Process any declared namespaces for (Iterator i = element.declaredNamespaces().iterator(); i.hasNext();) { Namespace namespace = (Namespace) i.next(); namespaces.put(namespace.getPrefix(), namespace.getURI()); //if ( namespace.getURI().equals( TAL_NAMESPACE_URI ) ) { // this.talNamespacePrefix = namespace.getPrefix(); //} //else if ( namespace.getURI().equals( METAL_NAMESPACE_URI ) ) { // this.metalNamespacePrefix = namespace.getPrefix(); //} } // Look for our attribute //String qualifiedAttributeName = this.metalNamespacePrefix + ":define-macro"; //String name = element.attributeValue( qualifiedAttributeName ); String name = element.attributeValue("define-macro"); //if ( name == null ) { // name = element.attributeValue // ( new QName( "define-macro", new Namespace( metalNamespacePrefix, METAL_NAMESPACE_URI ) ) ); //} if (name != null) { macros.put(name, new MacroImpl(element)); } // Recurse into child elements for (Iterator i = element.elementIterator(); i.hasNext();) { findMacros((Element) i.next(), macros); } } public String toLetter(int n) { return Loop.formatLetter(n); } public String toCapitalLetter(int n) { return Loop.formatCapitalLetter(n); } public String toRoman(int n) { return Loop.formatRoman(n); } public String toCapitalRoman(int n) { return Loop.formatCapitalRoman(n); } /* public List getDependencies() { Set templatePaths = new HashSet(); List nodes = template.selectNodes( "//@*[contains[.,'resolver/getPageTemplate']" ); for ( Iterator i = nodes.iterator(); i.hasNext(); ) { String att = ((Attribute)i.next()).getValue(); int index = att.indexOf( "resolver/getPageTemplate(" ); while( index != -1 ) { */ public Map getMacros() { if (this.macros == null) { this.macros = new HashMap(); findMacros(template.getRootElement(), this.macros); } return this.macros; } class MacroImpl implements Macro { Element element; MacroImpl(Element element) { this.element = element; } public void process(ContentHandler contentHandler, LexicalHandler lexicalHandler, Interpreter beanShell, Stack slotStack) throws SAXException, PageTemplateException, IOException { processElement(element, contentHandler, lexicalHandler, beanShell, slotStack); } } class SlotImpl implements Slot { Element element; SlotImpl(Element element) { this.element = element; } public void process(ContentHandler contentHandler, LexicalHandler lexicalHandler, Interpreter beanShell, Stack slotStack) throws SAXException, PageTemplateException, IOException { processElement(element, contentHandler, lexicalHandler, beanShell, slotStack); } } } class Expressions { String define = null; String condition = null; String repeat = null; String content = null; String attributes = null; String omitTag = null; String useMacro = null; String defineSlot = null; } class BitBucket extends OutputStream { static java.io.PrintWriter getBitBucketPrintWriter() { return new java.io.PrintWriter(new java.io.OutputStreamWriter(new BitBucket())); } public void write(int b) { // off you go to bit heaven } } class BoolHelper { public static boolean and(boolean a, boolean b) { return a && b; } public static boolean or(boolean a, boolean b) { return a || b; } public static Object cond(boolean b, Object x, Object y) { return b ? x : y; } } class MathHelper { public final static int add(int x, int y) { return x + y; } public final static int sub(int x, int y) { return x - y; } public final static int mult(int x, int y) { return x * y; } public final static int div(int x, int y) { return x / y; } public final static int mod(int x, int y) { return x % y; } } class DateHelper { static final Map dateFormats = new TreeMap(); public static final String format(String format, Date date) { SimpleDateFormat dateFormat = (SimpleDateFormat) dateFormats.get(format); if (dateFormat == null) { dateFormat = new SimpleDateFormat(format); dateFormats.put(format, dateFormat); } return dateFormat.format(date); } }