co.kademi.kdom.template.KDomTemplater.java Source code

Java tutorial

Introduction

Here is the source code for co.kademi.kdom.template.KDomTemplater.java

Source

/*
 * Copyright 2015 McEvoy Software Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package co.kademi.kdom.template;

import co.kademi.kdom.KDocument;
import co.kademi.kdom.KDocument.KDomContainerNode;
import co.kademi.kdom.KDocument.KDomElement;
import co.kademi.kdom.KDocument.KDomExpressionNode;
import co.kademi.kdom.KDocument.KDomExpressionSection;
import co.kademi.kdom.KDocument.KDomNode;
import co.kademi.kdom.KDocument.KDomTextNode;
import co.kademi.kdom.KDocument.KQuery;
import co.kademi.kdom.MultiJexlContext;
import co.kademi.kdom.NestableJexlContext;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;
import org.apache.commons.jexl2.ObjectContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Templating process 1. Merge templates to get a new document 2. Evaluate
 * template expressions, foreach loops, etc to get a new document 3. Evaluate
 * DOM scripts, to manipulate the doc
 *
 * @author brad
 */
public class KDomTemplater {

    private static final Logger log = LoggerFactory.getLogger(KDomTemplater.class);

    private final TemplateLocator templateLocator;
    private final JexlEngine jexl;

    public KDomTemplater(JexlEngine jexl) {
        this.templateLocator = null;
        this.jexl = jexl;
    }

    public KDomTemplater(TemplateLocator templateLocator, JexlEngine jexl) {
        this.templateLocator = templateLocator;
        this.jexl = jexl;
    }

    public void render(KDocument doc, OutputStream out, Map vars) {
        if (doc.getRoot() == null) {
            throw new NullPointerException("Document has no root element");
        }
        KDocument newDoc = new KDocument();
        MapContext c = new MapContext(vars);
        List<TemplateCall> contextStack = new ArrayList<>();
        c.set("doc", doc);
        c.set("topDoc", doc);
        KDomElement merged = merge(doc.getRoot(), newDoc, c, contextStack);
        newDoc.setRoot(merged);
        // at this point we should look for server side scripts and run them

        newDoc.write(out);
    }

    private KDomElement merge(KDocument.KDomElement el, KDocument newDoc, JexlContext vars,
            List<TemplateCall> contextStack) {
        //        String templatePath = el.getAttributes().get("template");
        //        if (StringUtils.isNotBlank(templatePath)) {
        //            KDocument templateDoc = templateLocator.locate(templatePath);
        //            if (templateDoc == null) {
        //                throw new RuntimeException("Couldnt find " + templatePath);
        //            }
        //            Map<String, Object> newVars = new HashMap<>();
        //            newVars.put("doc", templateDoc);
        //            JexlContext docContext = new MapContext(newVars);
        //            vars = new NestableJexlContext(vars, docContext);
        //            el = templateDoc.getRoot();
        //            System.out.println("using template: " + el);
        //        }

        List<KDocument.KDomNode> nodes = new ArrayList<>();
        mergeElementContent(el, el, newDoc, vars, nodes, contextStack);
        return newDoc.createElement(el.getName(), el.getAttributes(), nodes);
    }

    private void mergeElementContent(KDomElement el, KDomContainerNode containerNode, KDocument newDoc,
            JexlContext vars, List<KDomNode> nodes, List<TemplateCall> contextStack) {
        containerNode.getNodes().forEach((KDocument.KDomNode t) -> {
            mergeNode(t, newDoc, vars, nodes, el, contextStack);
        });
    }

    private void mergeNode(KDomNode sourceNode, KDocument newDoc, JexlContext vars, List<KDomNode> nodesToAddTo,
            KDomElement parentElement, List<TemplateCall> contextStack) {
        if (sourceNode instanceof KDomElement) {
            KDomElement te = (KDomElement) sourceNode;
            te = merge(te, newDoc, vars, contextStack);
            nodesToAddTo.add(te);
        } else if (sourceNode instanceof KDocument.KDomExpressionNode) {
            KDomExpressionNode en = (KDomExpressionNode) sourceNode;
            System.out.println("Evaluate expre: " + en.getText() + " with context parent: " + vars.get("parent"));
            Expression expr = en.getExpr();
            Object exprResult = expr.evaluate(vars);
            addNodesFromExpression(exprResult, newDoc, vars, nodesToAddTo, parentElement, contextStack);

        } else if (sourceNode instanceof KDocument.KDomExpressionSection) {
            KDomExpressionSection section = (KDomExpressionSection) sourceNode;
            Expression expr = section.getExpr();

            if (section.getHelperName() != null) {

                Object exprResult;
                switch (section.getHelperName()) {
                case "template":
                    // Evaluate the expression to get the template path (probably a const)
                    // Needs to push the current context onto the template context stack
                    System.out.println("execute template: " + section.getText());
                    exprResult = expr.evaluate(vars);
                    String templatePath = exprResult + "";
                    KDocument templateDoc = templateLocator.locate(templatePath);

                    MultiJexlContext c = MultiJexlContext.build(vars, new ObjectContext(jexl, section));
                    contextStack.add(0, new TemplateCall(c, section)); // push. include section as root object for use with #merge find('..')

                    Map<String, Object> newVars = new HashMap<>();
                    newVars.put("doc", templateDoc);
                    JexlContext docContext = new MapContext(newVars);
                    vars = new NestableJexlContext(vars, docContext);
                    vars = MultiJexlContext.build(vars, new ObjectContext(jexl, section));
                    KDomElement templateRootEl = templateDoc.getRoot();
                    System.out.println("using template: " + templateRootEl);
                    mergeElementContent(parentElement, templateRootEl, newDoc, vars, nodesToAddTo, contextStack);

                    break;
                case "merge":
                    // this is a template calling back to the page that called it. Needs to pop the execution context, and evaluate
                    TemplateCall templateCall = contextStack.remove(0); // pop
                    System.out.println("merge:" + section.getText());
                    JexlContext templateCallContext = templateCall.getContext();

                    System.out.println("-----");
                    Object o = jexl.createExpression("this.find('body')").evaluate(templateCallContext);
                    System.out.println("------");

                    exprResult = expr.evaluate(templateCallContext);
                    addNodesFromExpression(exprResult, newDoc, vars, nodesToAddTo, parentElement, contextStack);

                    break;
                }
            } else {
                Object exprResult = expr.evaluate(vars);

                JexlContext context = vars;
                if (exprResult instanceof Iterable) {
                    // The expression returned a list, so we evaluate the child nodes for each item in the list
                    Iterable it = (Iterable) exprResult;
                    it.forEach((Object listItem) -> {
                        JexlContext c = new ObjectContext(jexl, listItem);
                        NestableJexlContext jc = new NestableJexlContext(context, c);
                        mergeElementContent(parentElement, section, newDoc, jc, nodesToAddTo, contextStack);
                    });
                } else {
                    // not iterable, so use as an if condition, check if value is truthy
                    boolean b = toTruthy(exprResult);
                    if (b) {
                        JexlContext c = new ObjectContext(jexl, exprResult);
                        NestableJexlContext jc = new NestableJexlContext(vars, c);
                        mergeElementContent(parentElement, section, newDoc, jc, nodesToAddTo, contextStack);
                    }
                }
            }
        } else if (sourceNode instanceof KDocument.KDomTextNode) {
            KDomTextNode tt = (KDomTextNode) sourceNode;
            KDocument.KDomTextNode n = newDoc.createTextNode(tt.getText());
            nodesToAddTo.add(n);
            System.out.println("added node: " + n);
        }
    }

    private void addNodesFromExpression(Object o, KDocument newDoc, JexlContext vars, List<KDomNode> nodes,
            KDomElement parentElement, List<TemplateCall> contextStack) {
        if (o instanceof KDocument.KDomNode) {
            KDomNode n = (KDomNode) o;
            mergeNode(n, newDoc, vars, nodes, parentElement, contextStack);
        } else if (o instanceof KDocument.KQuery) {
            KQuery q = (KQuery) o;
            for (KDomElement n : q) {
                mergeNode(n, newDoc, vars, nodes, parentElement, contextStack);
            }
        } else if (o instanceof List) {
            List l = (List) o;
            for (Object item : l) {
                addNodesFromExpression(item, newDoc, vars, nodes, parentElement, contextStack);
            }

        } else if (o != null) {
            String s = o.toString();
            KDocument.KDomTextNode n = newDoc.createTextNode(s);
            nodes.add(n);
        }

    }

    private boolean toTruthy(Object o) {
        if (o == null) {
            return false;
        } else if (o instanceof Boolean) {
            return (Boolean) o;
        } else if (o instanceof String) {
            String s = (String) o;
            return s.equalsIgnoreCase("true");
        } else if (o instanceof Integer) {
            Integer i = (Integer) o;
            return i != 0;
        }
        return true; // not null
    }

    public class TemplateCall {

        private JexlContext context;
        private KDomExpressionSection section;

        public TemplateCall(JexlContext context, KDomExpressionSection section) {
            this.context = context;
            this.section = section;
        }

        public JexlContext getContext() {
            return context;
        }

        public KDomExpressionSection getSection() {
            return section;
        }

    }
}