com.webslingerz.jpt.PageTemplateImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.webslingerz.jpt.PageTemplateImpl.java

Source

/**
 *  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.webslingerz.jpt;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;

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.cyberneko.html.parsers.SAXParser;
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.io.SAXReader;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;

import bsh.Interpreter;

/**
 * @author <a href="mailto:rossi@webslingerZ.com">Chris Rossi</a>
 * @version $Revision: 1.15 $
 */
public class PageTemplateImpl implements PageTemplate {
    /**
     * Strict mode requires the template to be well-formed XML
     * Non-strict mode allows use of tal: and metal: prefixes without having to
     * explicitly decleare XML namespaces for them, also uses NekoHTML parser
     * to parse HTML template and fix it to be proper XHTML document
     */
    private boolean strict;

    private final String TAL_NAMESPACE_PREFIX = "tal";
    private final String METAL_NAMESPACE_PREFIX = "metal";

    private URI uri;
    private Document template;
    private Resolver userResolver = null;

    // Map of macros contained in this template
    Map<String, Macro> macros = new HashMap<String, Macro>();

    private static SAXReader htmlReader = null;
    private static SAXReader xmlReader = null;

    private static SAXReader createXMLReader() throws SAXException {
        SAXReader reader = new SAXReader();
        reader.setIgnoreComments(false);
        reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        return reader;
    }

    synchronized static final SAXReader getXMLReader() throws Exception {
        if (xmlReader == null) {
            xmlReader = createXMLReader();
        }
        return xmlReader;
    }

    synchronized static final SAXReader getHTMLReader() throws Exception {
        if (htmlReader == null) {
            htmlReader = createXMLReader();
            SAXParser parser = new SAXParser();
            parser.setProperty("http://cyberneko.org/html/properties/names/elems", "match");
            parser.setProperty("http://cyberneko.org/html/properties/names/attrs", "no-change");
            parser.setProperty("http://cyberneko.org/html/properties/default-encoding", "UTF-8");
            htmlReader.setXMLReader(parser);
        }
        return htmlReader;
    }

    public PageTemplateImpl() {
        this.uri = null;
        this.userResolver = null;

        this.strict = false;
    }

    public PageTemplateImpl(String template) throws PageTemplateException, UnsupportedEncodingException {
        this();
        setTemplate(template);
    }

    public PageTemplateImpl(String template, Resolver resolver)
            throws PageTemplateException, UnsupportedEncodingException {
        this();
        setResolver(resolver);
        setTemplate(template);
    }

    public PageTemplateImpl(InputStream input) throws PageTemplateException {
        this();
        setTemplate(input);
    }

    public PageTemplateImpl(InputStream input, Resolver resolver) throws PageTemplateException {
        this();
        setResolver(resolver);
        setTemplate(input);
    }

    public PageTemplateImpl(URL url) throws PageTemplateException {
        this();
        setTemplate(url);
    }

    public void setTemplate(String template) throws UnsupportedEncodingException, PageTemplateException {
        setTemplate(new ByteArrayInputStream(template.getBytes("UTF-8")));
    }

    public void setTemplate(InputStream input) throws PageTemplateException {
        readTemplate(input, null);
    }

    public void setTemplate(URL url) throws PageTemplateException {
        try {
            this.uri = new URI(url.toString());
        } catch (URISyntaxException e) {
            throw new PageTemplateException(e);
        }
        readTemplate(null, url);
    }

    private void readTemplate(InputStream input, URL url) throws PageTemplateException {
        try {
            SAXReader reader = getXMLReader();
            try {
                template = input != null ? reader.read(input) : reader.read(url);
            } catch (DocumentException e) {
                if (this.strict) {
                    throw e;
                }

                try {
                    reader = getHTMLReader();
                    template = input != null ? reader.read(input) : reader.read(url);
                } catch (NoClassDefFoundError ee) {
                    /* Allow user to omit nekohtml package to disable html parsing */
                    throw e;
                }
            }
        } catch (Exception e) {
            throw new PageTemplateException(e);
        }
    }

    public Resolver getResolver() {
        return this.userResolver;
    }

    public void setResolver(Resolver resolver) {
        this.userResolver = resolver;
    }

    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<String, Object> 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, null);
        } catch (TransformerConfigurationException e) {
            throw new PageTemplateException(e);
        }
    }

    public void process(ContentHandler contentHandler, LexicalHandler lexicalHandler, Object context,
            Map<String, Object> dictionary, Interpreter beanShell)
            throws SAXException, PageTemplateException, IOException {
        try {
            if (beanShell == null) {
                // Initialize the bean shell
                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 DefaultResolver(context, dictionary, beanShell));
                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<Map<String, Slot>>());
            contentHandler.endDocument();
        } catch (bsh.EvalError e) {
            throw new PageTemplateException(e);
        }
    }

    private Map<String, String> namespaces = new TreeMap<String, String>();

    private String getNamespaceURIFromPrefix(String prefix) {
        String uri = namespaces.get(prefix);
        return uri == null ? "" : uri;
    }

    private void processElement(Element element, ContentHandler contentHandler, LexicalHandler lexicalHandler,
            Interpreter beanShell, Stack<Map<String, Slot>> slotStack)
            throws SAXException, PageTemplateException, IOException {
        // Get attributes
        Expressions expressions = new Expressions();
        AttributesImpl attributes = getAttributes(element, expressions);

        // Skip macro definitions
        if (expressions.macro) {
            return;
        }

        // Process instructions
        // 1. define
        if (expressions.define != null) {
            processDefine(expressions.define, beanShell);
        }

        // 2. condition
        if (expressions.condition != null && !Expression.evaluateBoolean(expressions.condition, beanShell)) {
            // Skip this element (and children)
            return;
        }

        // 3. repeat
        Loop loop = new Loop(expressions.repeat, beanShell);
        while (loop.repeat(beanShell)) {
            // expand macro
            if (expressions.useMacro != null) {
                processMacro(expressions.useMacro, element, contentHandler, lexicalHandler, beanShell, slotStack);
                continue;
            }

            // 4. content or replace
            Object jptContent = null;
            if (expressions.content != null) {
                jptContent = processContent(expressions.content, beanShell);
            } else
            // fill slots
            if (expressions.defineSlot != null) {
                // System.err.println( "fill slot: " + expressions.defineSlot );
                if (!slotStack.isEmpty()) {
                    Map<String, Slot> slots = slotStack.pop();
                    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");
                }
            }

            // 5. attributes
            if (expressions.attributes != null) {
                processAttributes(attributes, expressions.attributes, beanShell);
            }

            // 6. omit-tag
            boolean jptOmitTag = false;
            if (expressions.omitTag != null) {
                if (expressions.omitTag.equals("")) {
                    jptOmitTag = true;
                } else {
                    jptOmitTag = Expression.evaluateBoolean(expressions.omitTag, beanShell);
                }
            }

            // Output

            // Declare element
            Namespace namespace = element.getNamespace();
            if (!jptOmitTag) {
                for (int i = 0; i < attributes.getLength(); i++) {
                    String processedValue = Expression.evaluateText(attributes.getValue(i), beanShell);
                    attributes.setValue(i, processedValue);
                }
                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<Map<String, Slot>> 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 = Expression.evaluateText(node.getText().toString(), beanShell).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();
            Namespace elementNamespace = element.getNamespace();
            if (!namespace.hasContent() && elementNamespace.hasContent())
                namespace = elementNamespace;
            // 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())
                    || (!strict && TAL_NAMESPACE_PREFIX.equals(namespace.getPrefix()))) {

                // 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())
                    || (!strict && METAL_NAMESPACE_PREFIX.equals(namespace.getPrefix()))) {

                // 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
                else if (name.equals("define-macro")) {
                    //System.out.println("Defining macro: " + attribute.getValue());
                    Element el = element.createCopy();
                    el.remove(attribute);
                    macros.put(attribute.getValue(), new MacroImpl(el));
                    expressions.macro = true;
                }

                // metal:fill-slot
                else if (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 nsURI = namespace.getURI();
                // String qualifiedName = namespace.getPrefix() + ":" + name;
                attributes.addAttribute(nsURI, name, attribute.getQualifiedName(), "CDATA", attribute.getValue());
                if (nsURI != "" && namespace != elementNamespace) {
                    String prefix = namespace.getPrefix();
                    String qName = "xmlns:" + prefix;
                    if (attributes.getIndex(qName) == -1) {
                        // add xmlns for this attribute
                        attributes.addAttribute("", prefix, qName, "CDATA", nsURI);
                    }
                }
                // 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<Map<String, Slot>> slotStack)
            throws PageTemplateException {

        Object object;
        object = Expression.evaluate(expression, beanShell);

        if (object == null) {
            object = macros.get(expression);
        }

        if (object == null) {
            throw new NoSuchPathException("could not find macro: " + expression);
        }

        if (object instanceof Macro) {
            // Find slots to fill inside this macro call
            Map<String, Slot> slots = new HashMap<String, Slot>();
            findSlots(element, slots);

            // Slots filled in later templates (processed earlier)
            // Take precedence over slots filled in intermediate
            // templates.
            if (!slotStack.isEmpty()) {
                Map<String, Slot> laterSlots = slotStack.peek();
                slots.putAll(laterSlots);
            }
            slotStack.push(slots);
            // System.err.println( "found slots: " + slots.keySet() );

            // Call macro
            Macro macro = (Macro) object;
            try {
                macro.process(contentHandler, lexicalHandler, beanShell, slotStack);
            } catch (Exception e) {
                throw new PageTemplateException(e);
            }
        } else {
            throw new PageTemplateException(
                    "expression '" + expression + "' does not evaluate to macro: " + object.getClass().getName());
        }
    }

    public Map<String, Macro> getMacros() {
        return this.macros;
    }

    /**
     * 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<String, Slot> 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);
        }
    }

    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 boolean isStrict() {
        return strict;
    }

    public void setStrict(boolean optionStrict) {
        this.strict = optionStrict;
    }

    class DefaultResolver extends Resolver {
        URIResolver uriResolver;
        private Object context;
        private Map<String, Object> dictionary;
        private Interpreter beanShell;

        DefaultResolver(Object context, Map<String, Object> dictionary, Interpreter beanShell) {
            this.context = context;
            this.dictionary = dictionary;
            this.beanShell = beanShell;
            if (uri != null) {
                uriResolver = new URIResolver(uri);
            }
        }

        public URL getResource(String path) throws java.net.MalformedURLException {
            URL resource = null;

            // If user has supplied resolver, use it
            if (userResolver != null) {
                resource = userResolver.getResource(path);
            }

            // If resource not found by user resolver
            // fall back to resolving by uri
            if (resource == null && uriResolver != null) {
                resource = uriResolver.getResource(path);
            }

            return resource;
        }

        public PageTemplate getPageTemplate(String path)
                throws PageTemplateException, java.net.MalformedURLException {
            PageTemplate template = null;

            // If user has supplied resolver, use it
            if (userResolver != null) {
                template = userResolver.getPageTemplate(path);

                // template inherits user resolver
                template.setResolver(userResolver);
            }

            // If template not found by user resolver
            // fall back to resolving by uri
            if (template == null && uriResolver != null) {
                template = uriResolver.getPageTemplate(path);
            }

            if (template != null) {
                try {
                    TransformerHandler handler = factory.newTransformerHandler();
                    // use null result handler
                    handler.setResult(new StreamResult(new OutputStream() {
                        @Override
                        public void write(int b) throws IOException {
                        }
                    }));
                    template.process(handler, handler, context, dictionary, beanShell);
                } catch (Exception e) {
                    throw new PageTemplateException(e);
                }
            }

            return template;
        }
    }

    class MacroImpl implements Macro {
        Element element;

        MacroImpl(Element element) {
            this.element = element;
        }

        public void process(ContentHandler contentHandler, LexicalHandler lexicalHandler, Interpreter beanShell,
                Stack<Map<String, Slot>> 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<Map<String, Slot>> 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;
    boolean macro = false;
}

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<String, SimpleDateFormat> dateFormats = new TreeMap<String, SimpleDateFormat>();

    public static final String format(String format, Locale locale, Date date) {
        SimpleDateFormat dateFormat = dateFormats.get(format);
        if (dateFormat == null) {
            dateFormat = new SimpleDateFormat(format, locale);
            dateFormats.put(format, dateFormat);
        }
        return dateFormat.format(date);
    }
}