com.zimbra.common.util.TemplateCompiler.java Source code

Java tutorial

Introduction

Here is the source code for com.zimbra.common.util.TemplateCompiler.java

Source

/*
 * ***** BEGIN LICENSE BLOCK *****
 * Zimbra Collaboration Suite Server
 * Copyright (C) 2007, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software Foundation,
 * version 2 of the License.
 *
 * This program 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 General Public License for more details.
 * You should have received a copy of the GNU General Public License along with this program.
 * If not, see <https://www.gnu.org/licenses/>.
 * ***** END LICENSE BLOCK *****
 */
package com.zimbra.common.util;

import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Map;
import java.util.Iterator;
import java.util.HashMap;
import java.io.*;

import org.apache.commons.cli.*;

/**
 */
public class TemplateCompiler {

    //
    // Constants
    //

    private static final String S_PARAM = "\\$\\{(.+?)\\}";
    private static final String S_INLINE = "<\\$=(.+?)\\$>";
    private static final String S_CODE = "<\\$(.+?)\\$>";
    private static final String S_ALL = S_PARAM + "|" + S_INLINE + "|" + S_CODE;
    private static final String S_TEMPLATE = "<template(.*?)>(.*?)</template>";
    private static final String S_ATTR = "\\s*(\\S+)\\s*=\\s*('[^']*'|\"[^\"]*\")";
    private static final String S_WS_LINESEP = "\\s*\\n+\\s*";
    private static final String S_GT_LINESEP_LT = ">" + S_WS_LINESEP + "<";

    private static final Pattern RE_REPLACE = Pattern.compile(S_ALL, Pattern.DOTALL);
    private static final Pattern RE_TEMPLATE = Pattern.compile(S_TEMPLATE, Pattern.DOTALL);
    private static final Pattern RE_ATTR = Pattern.compile(S_ATTR, Pattern.DOTALL);

    private static final String A_XML_SPACE = "xml:space";
    private static final String V_XML_SPACE_PRESERVE = "preserve";

    private static final String S_PARAM_PART = "([^\\(\\.]+)(\\(.*?\\))?\\.?";
    private static final Pattern RE_PARAM_PART = Pattern.compile(S_PARAM_PART);

    //
    // Data
    //

    private static Options _mOptions = new Options();
    private String _prefix = "";
    private String _idir = ".";
    private String _odir = ".";
    private boolean _authoritative = false;
    private boolean _define = false;
    private String[] _filenames = null;
    private String _format = "js";

    //
    //  Main
    //

    static {
        Option option = new Option("p", "prefix", true, "");
        option.setRequired(false);
        _mOptions.addOption(option);

        option = new Option("d", "define", false, "prevent ");
        option.setRequired(false);
        _mOptions.addOption(option);

        option = new Option("a", "authoritative", false, "declare template as authoritative");
        option.setRequired(false);
        _mOptions.addOption(option);

        //TODO: do we need to collect files?  For now, just pass files in, collect with sh.
        option = new Option("i", "inputdir", true, "source directory base");
        option.setRequired(false);
        _mOptions.addOption(option);

        option = new Option("o", "outputdir", true, "name of directory for resultant files");
        option.setRequired(false);
        _mOptions.addOption(option);

        option = new Option("f", "format", true, "output format, \"js\" or \"properties\" (default: \"js\"");
        option.setRequired(false);
        _mOptions.addOption(option);

    }

    public static void main(String[] args) throws Exception {
        try {
            TemplateCompiler compiler = new TemplateCompiler();
            compiler.compile(args);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    //
    //  Public API
    //

    public static void compile(File idir, File odir, String prefix, String[] filenames, boolean authoritative,
            boolean define) throws IOException {
        compile(idir, odir, prefix, filenames, "js", authoritative, define);
    }

    public static void compile(File idir, File odir, String prefix, String[] filenames, String format,
            boolean authoritative, boolean define) throws IOException {
        for (String filename : filenames) {
            String path = stripExt(filename);
            String pkg = prefix + path2package(path);
            File ifile = new File(idir, filename);
            File ofile = new File(odir, filename + "." + format);
            if (upToDate(ifile, ofile)) {
                System.out.println(ifile + " is up to date");
                continue;
            }
            System.out.println("Compiling " + ifile);
            if (odir != idir) {
                File pdir = ofile.getParentFile();
                pdir.mkdirs();
            }
            try {
                compile(ifile, ofile, format, pkg, authoritative, define);
            } catch (IOException e) {
                System.err.println("error: " + e.getMessage());
            }
        }
    }

    public static void compile(File ifile, File ofile, String pkg, boolean authoritative, boolean define)
            throws IOException {
        compile(ifile, ofile, "js", pkg, authoritative, define);
    }

    public static void compile(File ifile, File ofile, String format, String pkg, boolean authoritative,
            boolean define) throws IOException {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            boolean isProperties = format.equals("properties");
            in = new BufferedReader(new FileReader(ifile));
            out = new PrintWriter(new FileWriter(ofile));

            String lines = readLines(in);
            Matcher matcher = RE_TEMPLATE.matcher(lines);
            if (matcher.find()) {
                boolean first = true;
                do {
                    Map<String, String> attrs = parseAttrs(matcher.group(1));
                    String templateId = attrs.get("id");
                    String packageId = pkg;
                    // NOTE: Template ids can be specified absolutely (i.e.
                    //       overriding the default package) if the id starts
                    //       with a forward slash (/), or if the id contains
                    //       a hash mark (#). This allows a template file to
                    //       override both types of template files (i.e. a
                    //       single template per file or multiple templates
                    //       per file).
                    if (templateId != null && (templateId.indexOf('#') != -1 || templateId.startsWith("/"))) {
                        if (templateId.indexOf('#') == -1)
                            templateId += "#";
                        packageId = templateId.replaceAll("#.*$", "").replaceAll("^/", "").replace('/', '.');
                        templateId = templateId.replaceAll("^.*#", "");
                    }
                    String id = templateId != null && !templateId.equals("") ? packageId + "#" + templateId
                            : packageId;

                    // copy to .properties file
                    if (isProperties) {
                        printEscaped(out, id);
                        String body = lines.substring(matcher.start(), matcher.end());
                        if (body.indexOf('\n') == -1) {
                            out.print(" = ");
                            printEscaped(out, body);
                        } else {
                            out.print(" =");
                            String[] bodylines = body.split("\n");
                            for (String bodyline : bodylines) {
                                out.print("\\\n\t");
                                printEscaped(out, bodyline);
                            }
                        }
                        out.println();
                        continue;
                    }

                    // compile to JavaScript
                    String body = matcher.group(2);
                    String stripWsAttr = attrs.get(A_XML_SPACE);
                    if (stripWsAttr == null || !stripWsAttr.equals(V_XML_SPACE_PRESERVE)) {
                        body = body.replaceAll(S_GT_LINESEP_LT, "><").trim();
                    }
                    convertLines(out, id, body, attrs, authoritative);
                    if (first && define) {
                        out.print("AjxPackage.define(\"");
                        out.print(packageId);
                        out.println("\");");
                    }
                    if (first) {
                        first = false;
                        out.print("AjxTemplate.register(\"");
                        out.print(packageId);
                        out.print("\", ");
                        out.print("AjxTemplate.getTemplate(\"");
                        out.print(id);
                        out.print("\"), ");
                        out.print("AjxTemplate.getParams(\"");
                        out.print(id);

                        out.println("\"));");
                    }
                    out.println();
                } while (matcher.find());
            } else {
                convertLines(out, pkg, lines, null, authoritative);
            }
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    // ignore
                }
            }
            if (out != null) {
                out.close();
            }
        }
    }

    //
    //  Private helpers
    //

    private void compile(String[] args) throws IOException {
        parseArgs(args);

        File idir = new File(_idir);
        File odir = new File(_odir);
        compile(idir, odir, _prefix, _filenames, _format, _authoritative, _define);
    }

    private void parseArgs(String[] args) {
        CommandLineParser parser = new GnuParser();
        CommandLine cl = null;
        try {
            cl = parser.parse(_mOptions, args);
        } catch (Exception e) {
            System.out.println(e);
            System.exit(10);
        }

        if (cl == null) {
            System.out.println("Nothing to do!");
            System.exit(1);
        }

        if (cl.hasOption("p")) {
            _prefix = cl.getOptionValue("p");
        }
        if (cl.hasOption("d")) {
            _define = true;
        }
        if (cl.hasOption("a")) {
            _authoritative = true;
        }
        if (cl.hasOption("i")) {
            _odir = cl.getOptionValue("i");
        }
        if (cl.hasOption("o")) {
            _odir = cl.getOptionValue("o");
        }
        if (cl.hasOption("f")) {
            _format = cl.getOptionValue("f");
        }

        _filenames = cl.getArgs();
        if (_filenames.length == 0) {
            System.out.println("No files to convert!");
            System.exit(1);
        }
    }

    private static void convertLines(PrintWriter out, String pkg, String lines, Map<String, String> attrs,
            boolean authoritative) {
        out.print("AjxTemplate.register(\"");
        out.print(pkg);
        out.println("\", ");
        out.println("function(name, params, data, buffer) {");
        out.println("\tvar _hasBuffer = Boolean(buffer);");
        out.println("\tdata = (typeof data == \"string\" ? { id: data } : data) || {};");
        out.println("\tbuffer = buffer || [];");
        out.println("\tvar _i = buffer.length;");
        out.println();

        Matcher matcher = RE_REPLACE.matcher(lines);
        if (matcher.find()) {
            int offset = 0;
            do {
                int index = matcher.start();
                if (offset < index) {
                    printStringLines(out, lines.substring(offset, index));
                }
                String param = matcher.group(1);
                String inline = matcher.group(2);
                if (param != null) {
                    printDataLine(out, param);
                } else if (inline != null) {
                    printBufferLine(out, inline);
                } else {
                    printLine(out, "\t", matcher.group(3).replaceAll("\n", "\n\t"), "\n");
                }
                offset = matcher.end();
            } while (matcher.find());
            if (offset < lines.length()) {
                printStringLines(out, lines.substring(offset));
            }
        } else {
            printStringLines(out, lines);
        }
        out.println();

        out.println("\treturn _hasBuffer ? buffer.length : buffer.join(\"\");");
        out.println("},");
        if (attrs != null && attrs.size() > 0) {
            out.println("{");
            Iterator<String> iter = attrs.keySet().iterator();
            while (iter.hasNext()) {
                String aname = iter.next();
                String avalue = attrs.get(aname);
                out.print("\t\"");
                printEscaped(out, aname);
                out.print("\": \"");
                printEscaped(out, avalue);
                out.print("\"");
                if (iter.hasNext()) {
                    out.print(",");
                }
                out.println();
            }
            out.print("}");
        } else {
            out.print("null");
        }
        out.print(", ");
        out.print(authoritative);
        out.println(");");
    }

    //
    // Private functions
    //

    private static Map<String, String> parseAttrs(String s) {
        Map<String, String> attrs = new HashMap<String, String>();
        Matcher matcher = RE_ATTR.matcher(s);
        while (matcher.find()) {
            String aname = matcher.group(1);
            String avalue = matcher.group(2).replaceAll("^['\"]|['\"]$", "");
            attrs.put(aname, avalue);
        }
        return attrs;
    }

    private static String readLines(BufferedReader in) throws IOException {
        StringBuilder str = new StringBuilder();
        String line;
        while ((line = in.readLine()) != null) {
            str.append(line);
            str.append('\n');
        }
        return str.toString();
    }

    private static void printLine(PrintWriter out, String... ss) {
        for (String s : ss) {
            out.print(s);
        }
    }

    private static void printStringLines(PrintWriter out, String... ss) {
        for (String s : ss) {
            String[] lines = s.split("\n");
            for (int i = 0; i < lines.length; i++) {
                String line = lines[i];
                printStringLine(out, line, i < lines.length - 1 ? "\n" : "");
            }
        }
    }

    private static void printStringLine(PrintWriter out, String... ss) {
        out.print("\tbuffer[_i++] = \"");
        for (String s : ss) {
            printEscaped(out, s);
        }
        out.println("\";");
    }

    private static void printDataLine(PrintWriter out, String s) {
        out.print("\tbuffer[_i++] = data");
        Matcher part = RE_PARAM_PART.matcher(s);
        while (part.find()) {
            String name = part.group(1);
            String args = part.group(2);

            out.print("[\"");
            out.print(name);
            out.print("\"]");
            if (args != null) {
                out.print(args);
            }
        }
        out.println(";");
    }

    private static void printBufferLine(PrintWriter out, String... ss) {
        out.print("\tbuffer[_i++] = ");
        for (String s : ss) {
            out.print(s);
        }
        out.println(";");
    }

    private static String stripExt(String s) {
        return s.replaceAll("\\.[^\\.]+$", "");
    }

    private static String path2package(String s) {
        return s.replace(File.separatorChar, '.');
    }

    private static void printEscaped(PrintWriter out, String s) {
        int length = s.length();
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            if (c == '"') {
                out.print('\\');
            } else if (c == '\n') {
                out.print("\\n");
                continue;
            } else if (c == '\r') {
                out.print("\\r");
                continue;
            } else if (c == '\t') {
                out.print("\\t");
                continue;
            } else if (c == '\\') {
                out.print("\\\\");
                continue;
            }
            out.print(c);
        }
    }

    private static boolean upToDate(File ifile, File ofile) {
        if (ifile.exists() && ofile.exists()) {
            return ifile.lastModified() < ofile.lastModified();
        }
        return false;
    }

}