Package pspdash

Source Code of pspdash.HTMLPreprocessor$DirectiveMatch

// PSP Dashboard - Data Automation Tool for PSP-like processes
// Copyright (C) 2003 Software Process Dashboard Initiative
//
// 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; either version 2
// of the License, or (at your option) any later version.
//
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// The author(s) may be contacted at:
// OO-ALC/TISHD
// Attn: PSP Dashboard Group
// 6137 Wardleigh Road
// Hill AFB, UT 84056-5843
//
// E-Mail POC:  processdash-devel@lists.sourceforge.net

package pspdash;

import pspdash.data.DataRepository;
import pspdash.data.ListData;
import pspdash.data.SimpleData;
import pspdash.data.StringData;

import java.io.IOException;
import java.io.FileNotFoundException;
import java.net.URL;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.util.*;



/** Class for performing server-side includes and other preprocessing on
*  HTML files.
*/
public class HTMLPreprocessor {

    TinyWebServer web;
    DataRepository data;
    PSPProperties props;
    Map env, params;
    String prefix;
    String defaultEchoEncoding = null;


    public HTMLPreprocessor(TinyWebServer web, DataRepository data, Map env) {
        this(web, data, (PSPProperties) env.get(TinyCGI.PSP_PROPERTIES),
             (String) env.get("PATH_TRANSLATED"), env, null);
        QueryParser p = new QueryParser();
        try {
            p.parseInput((String) env.get("QUERY_STRING"));
        } catch (IOException ioe) {}
        params = p.parameters;
    }

    public HTMLPreprocessor(TinyWebServer web, DataRepository data,
                            PSPProperties props,
                            String prefix, Map env, Map params) {
        this.web = web;
        this.data = data;
        this.props = props;
        this.prefix = (prefix == null ? "" : prefix);
        this.env = env;
        this.params = params;
    }


    /** preprocess the given content, and return the result. */
    public String preprocess(String content) throws IOException {
        StringBuffer text = new StringBuffer(content);
        cachedTestExpressions.clear();

        numberBlocks(text, "foreach", "endfor", null, null);
        numberBlocks(text, "fortree", "endtree", null, null);
        numberBlocks(text, "if", "endif", "else", "elif");

        DirectiveMatch dir;
        int pos = 0;
        while ((dir = new DirectiveMatch(text, "", pos, true)).matches()) {
            if ("echo".equals(dir.directive))
                processEchoDirective(dir);
            else if ("include".equals(dir.directive))
                processIncludeDirective(dir);
            else if (blockMatch("foreach", dir.directive))
                processForeachDirective(dir);
            else if (blockMatch("if", dir.directive))
                processIfDirective(dir);
            else if ("incr".equals(dir.directive))
                processIncrDirective(dir);
            else if ("set".equals(dir.directive))
                processSetDirective(dir);
            else if ("break".equals(dir.directive))
                processBreakDirective(dir);
            else if (blockMatch("fortree", dir.directive))
                processForTreeDirective(dir);
            else if ("resources".equals(dir.directive))
                processResourcesDirective(dir);
            else
                dir.replace("");
            pos = dir.end;
        }
        return text.toString();
    }


    /** process an include directive within the buffer */
    private void processIncludeDirective(DirectiveMatch include)
        throws IOException
    {
        // what file do they want us to include?
        String url = include.getAttribute("file");
        if (isNull(url))
            include.replace(""); // no file specified - delete this directive.
        else {
            // fetch the requested url (relative to the current url) and
            // replace the include directive with its contents.
            String context = (String) env.get("REQUEST_URI");
            String incText = new String
                (web.getRequest(context, url, true), "UTF-8");
            // does the page author want us to parse the included text
            // for directives, or just insert it verbatim?
            if (include.getAttribute("parse") != null) {
                // parse the insertion.
                include.replace("");
                include.buf.insert(include.end, incText);
            } else {
                // insert it verbatim (default);
                include.replace(incText);
            }
        }
    }


    /** process an echo directive within the buffer */
    private void processEchoDirective(DirectiveMatch echo) {
        String var, value, encoding;

        // Was an explicit value specified? (This is used for performing
        // encodings on strings, and is especially useful when the string
        // in question came from interpolating a foreach statement.)
        value = echo.getAttribute("value");

        if (isNull(value)) {
            // was a variable name specified? If so, look up the associated
            // string value.
            var = echo.getAttribute("var");
            if (isNull(var)) var = echo.contents.trim();
            value = (isNull(var) ? "" : getString(var));
        }

        // Apply the requested encoding(s)
        String encodings = echo.getAttribute("defaultEncoding");
        if (encodings != null) defaultEchoEncoding = encodings;
        encodings = echo.getAttribute("encoding");
        if (encodings == null) encodings = defaultEchoEncoding;
        value = applyEncodings(value, encodings);

        // replace the echo directive with the resulting value.
        echo.replace(value);
    }
    public void setDefaultEchoEncoding(String enc) {
        defaultEchoEncoding = enc;
    }
    public static String applyEncodings(String value, String encodings) {
        if (encodings == null || "none".equalsIgnoreCase(encodings))
            return value;
        StringTokenizer tok = new StringTokenizer(encodings, ",");
        String encoding;
        while (tok.hasMoreTokens()) {
            encoding = tok.nextToken();

            if ("url".equalsIgnoreCase(encoding)) {
                // url encode the value
                value = URLEncoder.encode(value);
                value = StringUtils.findAndReplace(value, "%2F", "/");
                value = StringUtils.findAndReplace(value, "%2f", "/");
            } else if ("xml".equalsIgnoreCase(encoding))
                // encode the value as an xml entity
                value = XMLUtils.escapeAttribute(value);
            else if ("data".equalsIgnoreCase(encoding))
                value = AutoData.esc(value);
            else if ("dir".equalsIgnoreCase(encoding))
                value = dirEncode(value);
            else if ("javaStr".equalsIgnoreCase(encoding))
                value = javaEncode(value);
            else
                // default: HTML entity encoding
                value = HTMLUtils.escapeEntities(value);
        }

        return value;
    }


    /** process a foreach directive within the buffer */
    private void processForeachDirective(DirectiveMatch foreach) {
        StringBuffer text = foreach.buf;
        String blockNum = blockNum("foreach", foreach.directive);
        // find the matching endfor.
        DirectiveMatch endfor = new DirectiveMatch
            (text, blockNum + "endfor", foreach.end, true);

        if (!endfor.matches()) {
            // if the endfor is missing, delete this directive and abort.
            System.err.println
                ("foreach directive without matching endfor - aborting.");
            foreach.replace("");
            return;
        }

        // get the list of values that we should iterate over. This can be
        // specified either as a list literal using the "values" attribute,
        // or via a list variable using the "list" attribute.
        String values = foreach.getAttribute("values");
        ListData list;
        if (isNull(values)) {
            String listName = foreach.getAttribute("list");
            list = getList(listName);
        } else {
            list = new ListData(values);
        }

        // iterate over the list and calculate the resulting contents.
        String loopIndex = foreach.getAttribute("name");
        String loopContents = text.substring(foreach.end, endfor.begin);
        StringBuffer replacement = new StringBuffer();
        String iterResults;
        for (int i = 0;   i < list.size();   i++) {
            iterResults = StringUtils.findAndReplace
                (loopContents, loopIndex, (String)list.get(i));
            /*
            iterResults = StringUtils.findAndReplace
                (iterResults,
                 DIRECTIVE_START + "0",
                 DIRECTIVE_START + "0" + i + "-");
            */
            replacement.append(iterResults);
        }

        // replace the directive with the iterated contents.  Note
        // that we explicitly replace the initial foreach tag with an
        // empty string, so the overall processing loop (in the
        // preprocess method) will process these iterated contents.
        text.replace(foreach.end, endfor.end, replacement.toString());
        foreach.replace("");
    }

    /** process a fortree directive within the buffer */
    private void processForTreeDirective(DirectiveMatch fortree) {
        StringBuffer text = fortree.buf;
        String blockNum = blockNum("fortree", fortree.directive);
        // find the matching endtree.
        DirectiveMatch endtree = new DirectiveMatch
            (text, blockNum + "endtree", fortree.end, true);

        if (!endtree.matches()) {
            // if the endtree is missing, delete this directive and abort.
            System.err.println
                ("fortree directive without matching endtree - aborting.");
            fortree.replace("");
            return;
        }

        // determine the root prefix - possibly alter it based on the
        // directive's value for the startAt attribute.
        String rootPrefix = this.prefix;
        String startAt = fortree.getAttribute("startAt");
        if (startAt != null && startAt.length() > 0)
            rootPrefix = rootPrefix + "/" + startAt;
        PropertyKey rootNode = PropertyKey.fromPath(rootPrefix);

        // get the expandName from the directive.
        String expandName = fortree.getAttribute("expandName");

        // should the root node be included?
        boolean includeRoot =
            "true".equalsIgnoreCase(fortree.getAttribute("includeRoot"));

        // how deep should the iteration go?
        int maxDepth = Integer.MAX_VALUE;
        String depthStr = fortree.getAttribute("depth");
        if (depthStr != null) try {
            maxDepth = Integer.parseInt(depthStr);
        } catch (Exception e) {}

        // should the parent be displayed before or after children?
        boolean parentLast =
            "true".equalsIgnoreCase(fortree.getAttribute("parentLast"));

        // iterate over the tree and calculate the resulting contents.
        String loopContents = text.substring(fortree.end, endtree.begin);
        StringBuffer replacement = new StringBuffer();
        addSetDirective(replacement, "ROOT", rootPrefix);
        addSetDirective(replacement, "SPACER", SPACER);
        recurseTreeNode(replacement, loopContents, rootNode, 0, "",
                        expandName, includeRoot, maxDepth, parentLast);

        // replace the directive with the iterated contents.  Note
        // that we explicitly replace the initial fortree tag with an
        // empty string, so the overall processing loop (in the
        // preprocess method) will process these iterated contents.
        text.replace(fortree.end, endtree.end, replacement.toString());
        fortree.replace("");
    }

    private void addSetDirective(StringBuffer buf, String varName,
                                 String value) {
        buf.append(DIRECTIVE_START).append("set")
            .append(" var=\"").append(varName);
        if (value != null)
            buf.append("\" value=\"").append(dirEncode(value));
        buf.append("\" ").append(DIRECTIVE_END);
    }

    private void outputTreeNode(StringBuffer buf, String loopContents,
                                PropertyKey node, int depth, String relPath,
                                String expansionName, boolean isLeaf,
                                boolean isExpanded)
    {
        addSetDirective(buf, "PATH", node.path());
        if (relPath.length() == 0) {
            addSetDirective(buf, "RELPATH", "");
            addSetDirective(buf, "RELPATHLIST", null);
        } else {
            addSetDirective(buf, "RELPATH", relPath.substring(1));
            addSetDirective(buf, "RELPATHLIST", "LIST=" + relPath + "/");
        }
        addSetDirective(buf, "NAME", node.name());
        addSetDirective(buf, "DEPTH", Integer.toString(depth));
        addSetDirective(buf, "DEPTH_SPACER", makeDepthSpacer(depth));
        addSetDirective(buf, "ISLEAF", isLeaf ? "true" : null);
        if (expansionName != null) {
            addSetDirective(buf, "EXPANSIONNAME", expansionName);
            addSetDirective(buf, "ISEXPANDED", isExpanded ? "true" : null);
            addSetDirective(buf, "EXPANDLINK",
                            makeExpansionLink(expansionName, isLeaf,
                                              isExpanded));
        }
        buf.append(loopContents);
    }

    private void recurseTreeNode(StringBuffer buf, String loopContents,
                                 PropertyKey node, int depth,
                                 String relPath, String expandName,
                                 boolean outputNode, int remainingDepth,
                                 boolean parentLast)
    {
        boolean isLeaf = (props.getNumChildren(node) == 0);
        String expansionName = makeExpansionName(relPath, expandName);
        boolean isExpanded = (expandName == null ||
                              outputNode == false ||
                              testDataElem("["+expansionName+"]"));


        if (outputNode && !parentLast)
            outputTreeNode(buf, loopContents, node, depth, relPath,
                           expansionName, isLeaf, isExpanded);

        if (remainingDepth > 0 && isExpanded) {
            int numKids = props.getNumChildren(node);
            for (int i = 0;   i < numKids;  i++) {
                PropertyKey child = props.getChildKey(node, i);
                recurseTreeNode(buf, loopContents, child, depth+1,
                                relPath+"/"+child.name(), expandName,
                                true, remainingDepth-1, parentLast);
            }
        }

        if (outputNode && parentLast)
            outputTreeNode(buf, loopContents, node, depth, relPath,
                           expansionName, isLeaf, isExpanded);
    }
    private String makeDepthSpacer(int depth) {
        StringBuffer result = new StringBuffer();
        while (depth-- > 0)
            result.append(SPACER);
        return result.toString();
    }
    private String makeExpansionName(String relPath, String expandName) {
        if (expandName == null) return null;
        return expandName + relPath;
    }
    private String makeExpansionLink(String expansionName, boolean isLeaf,
                                     boolean isExpanded) {
        if (isLeaf) return LEAF_LINK;
        String dataName = prefix + "/" + expansionName;
        String anchor = "exp_" + dataName.hashCode();
        dataName = URLEncoder.encode(dataName);
        String result = StringUtils.findAndReplace
            (isExpanded ? COLLAPSE_LINK : EXPAND_LINK, "%%%", dataName);
        result = StringUtils.findAndReplace(result, "###", anchor);
        return result;
    }
    private static final String SPACER =
        "<img width=9 height=9 src='/Images/blank.png'>";
    private static final String LEAF_LINK = SPACER;
    private static final String ANCHOR_TEXT = "<a name='###'></a>";
    private static final String COLLAPSE_LINK =
        "<a border=0 href='/dash/expand.class?collapse=%%%'>"+
        "<img border=0 width=9 height=9 src='/Images/minus.png'></a>"+
        ANCHOR_TEXT;
    private static final String EXPAND_LINK =
        "<a border=0 href='/dash/expand.class?expand=%%%'>"+
        "<img border=0 width=9 height=9 src='/Images/plus.png'></a>"+
        ANCHOR_TEXT;



    /** process an if directive within the buffer */
    private void processIfDirective(DirectiveMatch ifdir) {
        processIfDirective(ifdir, blockNum("if", ifdir.directive));
    }
    private void processIfDirective(DirectiveMatch ifdir, String blockNum) {
        StringBuffer text = ifdir.buf;
        // find the matching endif.
        DirectiveMatch endif = new DirectiveMatch
            (text, blockNum + "endif", ifdir.end, true);

        if (!endif.matches()) {
            // if the endif is missing, delete this directive and abort.
            System.err.println
                ("if directive without matching endif - aborting.");
            ifdir.replace("");
            return;
        }

        // See if there was an elif or an else.
        DirectiveMatch elsedir = new DirectiveMatch
            (text, blockNum + "elif", ifdir.end, true);
        if (!elsedir.matches() || elsedir.begin > endif.begin)
            elsedir = new DirectiveMatch
                (text, blockNum + "else", ifdir.end, true);
        if (elsedir.matches() && elsedir.begin > endif.begin)
            elsedir.begin = -1;

                                // if this is an else clause
        if (blockMatch("else", ifdir.directive) ||
                                // or if the test expression was true,
            ifTest(ifdir.contents))
        {
            endif.replace("")// delete the endif
            if (elsedir.matches()) // delete the entire else clause if present
                text.replace(elsedir.begin, endif.begin, "");
            ifdir.replace("")// delete the if directive.
        } else if (elsedir.matches()) {
            // if the test was false, and there was an else clause, evaluate
            // the else clause as its own if statement.
            processIfDirective(elsedir, blockNum);
            // then delete the "true" clause (the text between the if
            // and the else)
            text.replace(ifdir.end, elsedir.begin, "");
            // finally, delete the if directive itself.
            ifdir.replace("");
        } else {
            // if the test was false and there was no else clause, delete
            // everything.
            text.replace(ifdir.end, endif.end, "");
            ifdir.replace("");
        }
    }

    Map cachedTestExpressions = new HashMap();
    private boolean ifTest(String expression) {
        expression = expression.replace('\n',' ').replace('\t',' ').trim();
        Boolean result = (Boolean) cachedTestExpressions.get(expression);
        if (result == null) {
            boolean test = false;
            boolean reverse = false;
            boolean containsVolatileVar = false;

            // if the expression contains multiple OR clauses, evaluate
            // them individually and return true if one is true.
            int orPos = expression.indexOf(" || ");
            if (orPos != -1) {
                expression = expression + " || ";
                String subExpr;
                while (orPos != -1) {
                    subExpr = expression.substring(0, orPos);
                    if (ifTest(subExpr)) return true;
                    expression = expression.substring(orPos+4);
                    orPos = expression.indexOf(" || ");
                }
                return false;
            }

            RelationalExpression re = parseRelationalExpression(expression);
            if (re != null) {
                test = re.test();
                containsVolatileVar = re.containsVolatileVar;
            } else {
                String symbolName = cleanup(expression);
                if (symbolName.startsWith("not") &&
                    whitespacePos(symbolName) == 3) {
                    reverse = true;
                    symbolName = cleanup(symbolName.substring(4));
                } else if (symbolName.startsWith("!")) {
                    reverse = true;
                    symbolName = cleanup(symbolName.substring(1));
                }
                boolean checkDefined = false;
                if (symbolName.startsWith("defined") &&
                    whitespacePos(symbolName) == 7) {
                    checkDefined = true;
                    symbolName = cleanup(symbolName.substring(8));
                }

                if (volatileVariables.contains(symbolName))
                    containsVolatileVar = true;

                if (!isNull(symbolName))
                    test = (symbolName.startsWith("[") ?
                            testDataElem(symbolName, checkDefined) :
                            !isNull(getString(symbolName)));
                if (reverse)
                    test = !test;
            }
            result = test ? Boolean.TRUE : Boolean.FALSE;
            if (!containsVolatileVar)
                cachedTestExpressions.put(expression, result);
        }
        return result.booleanValue();
    }

    private class RelationalExpression {
        public String lhs, operator, rhs;
        public boolean containsVolatileVar = false;
        public boolean test() {
            if (lhs.length() == 0 || rhs.length() == 0) {
                System.err.println
                    ("malformed relational expression - aborting.");
                return false;
            }
            String lhval = getVal(lhs);
            String rhval = getVal(rhs);

            if ("eq".equals(operator)) return eq(lhval, rhval);
            if ("ne".equals(operator)) return !eq(lhval, rhval);
            if ("=~".equals(operator)) return matches(lhval, rhval);
            if ("!~".equals(operator)) return !matches(lhval, rhval);
            if ("gt".equals(operator)) return gt(lhval, rhval);
            if ("lt".equals(operator)) return lt(lhval, rhval);
            if ("ge".equals(operator))
                return gt(lhval, rhval) || eq(lhval, rhval);
            if ("le".equals(operator))
                return lt(lhval, rhval) || eq(lhval, rhval);
            return false;
        }
        private String getVal(String t) {
            if (t.startsWith("'")) return cleanup(t);
            t = cleanup(t);
            if (volatileVariables.contains(t)) containsVolatileVar = true;
            return getString(cleanup(t));
        }
        private boolean eq(String l, String r) {
            if (l == null && r == null) return true;
            if (l == null || r == null) return false;
            try {
                double ll = Double.parseDouble(l);
                double rr = Double.parseDouble(r);
                return (ll == rr);
            } catch (NumberFormatException nfe) {}

            return l.equals(r);
        }
        private boolean lt(String l, String r) {
            if (l == null || r == null) return false;
            try {
                double ll = Double.parseDouble(l);
                double rr = Double.parseDouble(r);
                return (ll < rr);
            } catch (NumberFormatException nfe) {}

            return (l.compareTo(r) < 0);
        }
        private boolean gt(String l, String r) {
            if (l == null || r == null) return false;
            try {
                double ll = Double.parseDouble(l);
                double rr = Double.parseDouble(r);
                return (ll > rr);
            } catch (NumberFormatException nfe) {}

            return (l.compareTo(r) > 0);
        }
        private boolean matches(String l, String r) {
            if (l == null || r == null) return false;
            boolean result = false;
            Perl5Util perl = null;
            try {
                String re = "m\n" + r + "\n";
                perl = PerlPool.get();
                result = perl.match(re, l);
            } catch (Exception e) {
            } finally {
                PerlPool.release(perl);
            }
            return result;
        }
    }
    private RelationalExpression parseRelationalExpression(String expr) {
        if (expr == null) return null;
        expr = expr.replace('\n', ' ').replace('\t', ' ');
        int pos = expr.indexOf(" eq ");
        if (pos == -1) pos = expr.indexOf(" ne ");
        if (pos == -1) pos = expr.indexOf(" =~ ");
        if (pos == -1) pos = expr.indexOf(" !~ ");
        if (pos == -1) pos = expr.indexOf(" lt ");
        if (pos == -1) pos = expr.indexOf(" le ");
        if (pos == -1) pos = expr.indexOf(" gt ");
        if (pos == -1) pos = expr.indexOf(" ge ");
        if (pos == -1) return null;

        RelationalExpression result = new RelationalExpression();
        result.lhs = expr.substring(0, pos).trim();
        result.operator = expr.substring(pos+1, pos+3);
        result.rhs = expr.substring(pos+4).trim();
        return result;
    }

    private HashSet volatileVariables = new HashSet();
    private void processIncrDirective(DirectiveMatch incrDir) {
        String varName = cleanup(incrDir.contents);
        int numberValue = 0;

        Object param = params.get(varName);
        if (param instanceof String) try {
            numberValue = Integer.parseInt((String) param) + 1;
        } catch (NumberFormatException nfe) {}
        params.put(varName, Integer.toString(numberValue));
        volatileVariables.add(varName);

        incrDir.replace("");
    }

    /** process a set directive within the buffer */
    private void processSetDirective(DirectiveMatch setDir) {
        String varName = setDir.getAttribute("var");
        String valueName = setDir.getAttribute("value");

        params.put(varName, valueName);
        volatileVariables.add(varName);

        setDir.replace("");
    }

    /** process a break directive within the buffer */
    private void processBreakDirective(DirectiveMatch breakDir) {
        String label = cleanup(breakDir.contents);
        DirectiveMatch breakEnd = new DirectiveMatch
            (breakDir.buf, "endbreak "+label, breakDir.end, true);

        if (!breakEnd.matches()) {
            // if the endbreak is missing, delete this directive and abort.
            System.err.println
                ("break directive without matching endbreak - aborting.");
            breakDir.replace("");
            return;
        }
        breakDir.buf.replace(breakDir.end, breakEnd.end, "");
        breakDir.replace("");
    }

    /** process a resources directive within the buffer */
    private void processResourcesDirective(DirectiveMatch resDir)
        throws IOException
    {
        // what file do they want us to include?
        String url = resDir.getAttribute("file");
        if (isNull(url))
            url = resDir.contents;
        if (!isNull(url)) {
            // fetch the requested url (relative to the current url) and
            // replace the include directive with its contents.
            String context = (String) env.get("REQUEST_URI");
            URL tempURL = new URL("http://ignored" + context);
            tempURL = new URL(tempURL, url);
            url = tempURL.getFile();
            String resName =
                url.substring(1).replace('.', '$').replace('/', '.');
            Resources r;
            try {
                r = Resources.getDashBundle(resName);
            } catch (MissingResourceException mre) {
                throw new FileNotFoundException(url + ".properties");
            }
            Enumeration keys = r.getKeys();
            while (keys.hasMoreElements()) {
                String key = (String) keys.nextElement();
                String value = r.getString(key);
                params.put(key, value);
            }
        }
        resDir.replace("");
    }

    /** search for blocks created by matching start and end directives, and
     * give them unique numerical prefixes so it will be easy to figure out
     * which start directive goes with which end directive.  This handles
     * nested blocks correctly.
     */
    private void numberBlocks(StringBuffer text,
                              String blockStart, String blockFinish,
                              String blockMid1, String blockMid2) {
        DirectiveMatch start, finish, mid;
        int blockNum = 0;
        String prefix;

        while ((finish = new DirectiveMatch(text, blockFinish)).matches()) {
            start = new DirectiveMatch(text, blockStart, finish.begin, false);
            if (!start.matches()) break;
            prefix = "0" + blockNum++;

            finish.rename(prefix + blockFinish);

            int end = finish.begin;
            if (!isNull(blockMid1))
                end = renameDirectives(text, start.end, end,
                                       blockMid1, prefix + blockMid1);
            if (!isNull(blockMid2))
                end = renameDirectives(text, start.end, end,
                                       blockMid2, prefix + blockMid2);

            start.rename(prefix + blockStart);
        }
    }

    /** Find all directives with the given name in text, starting at
     * position <code>from</code> and going to position <code>to</code>,
     * and rename them to newname.
     */
    private int renameDirectives(StringBuffer text, int from, int to,
                                 String name, String newName) {
        DirectiveMatch dir;
        int delta = newName.length() - name.length();

        while ((dir = new DirectiveMatch(text, name, from, true)).matches() &&
               dir.begin < to) {
            dir.rename(newName);
            from = dir.end;
            to += delta;
        }
        return to;
    }


    /** trim whitespace and unimportant delimiters from t */
    private static String cleanup(String t) {
        t = t.trim();
        if (t.length() == 0) return t;
        if (t.charAt(0) == '"' || t.charAt(0) == '\'') {
            int endPos = t.indexOf(t.charAt(0), 1);
            if (endPos != -1) t = t.substring(1, endPos);
        }
        return dirUnencode(t);
    }

    private static String javaEncode(String s) {
        StringBuffer result = new StringBuffer();
        for (int i = 0;   i < s.length();   i++)
            switch (s.charAt(i)) {
                case '\b': result.append("\\b"); break;
                case '\t': result.append("\\t"); break;
                case '\f': result.append("\\f"); break;
                case '\r': result.append("\\r"); break;
                case '\n': result.append("\\n"); break;
                case '\'': result.append("\\'"); break;
                case '\"': result.append("\\\""); break;
                case '\\': result.append("\\\\"); break;
                default:   result.append(s.charAt(i)); break;
            }

        return result.toString();
    }

    private static String dirEncode(String s) {
        if (s == null) return null;
        for (int i = s.length();   i-- > 0; )
            // look for unsafe characters in the string.
            if (-1 == SAFE_CHARS.indexOf(s.charAt(i)))
                // if we find an unsafe character, encode the entire value.
                return DIR_ENC_BEG + URLEncoder.encode(s) + DIR_ENC_END;
        return s;
    }
    private static final String SAFE_CHARS =
        "abcdefghijklmnopqrstuvwxyz" +
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
        "0123456789" +
        "/_,. "; // Note: space char IS included
    private static final String DIR_ENC_BEG = "%(";
    private static final String DIR_ENC_END = "%)";
    private static String dirUnencode(String s) {
        if (s == null) return null;
        int beg = s.indexOf(DIR_ENC_BEG);
        while (beg != -1) {
            int end = s.indexOf(DIR_ENC_END, beg);
            if (end == -1) break;
            String decoded =
                URLDecoder.decode(s.substring(beg+DIR_ENC_BEG.length(), end));
            s = s.substring(0, beg) +
                decoded +
                s.substring(end+DIR_ENC_END.length());

            beg = s.indexOf(DIR_ENC_BEG, beg + decoded.length());
        }
        return s;
    }


    /** Lookup the named list and return it.
     *
     * if the name is enclosed in braces [] it refers to a data
     * element - otherwise it refers to a cgi environment variable or
     * a query/post parameter.
     *
     * If no list is found by that name, will return an empty list.
     */
    private ListData getList(String listName) {
        if (listName.startsWith("[")) {
            // listName names a data element
            listName = trimDelim(listName);
            SimpleData d = getSimpleValue(listName);
            if (d instanceof ListData)   return (ListData) d;
            if (d instanceof StringData) return ((StringData) d).asList();
            if (d instanceof SimpleData) {
                ListData result = new ListData();
                result.add(d.format());
            }
            return EMPTY_LIST;
        } else {
            // listName names an environment variable or parameter
            ListData result = new ListData();

                                // try for an environment variable first.
            Object envVal = env.get(listName);
            if (envVal instanceof String) {
                result.add((String) envVal);
                return result;
            }
                                // look for a parameter value.
            String[] param = (String[]) params.get(listName + "_ALL");
            if (param != null)
                for (int i = 0;   i < param.length;   i++)
                    result.add(param[i]);
            else if (params.get(listName) instanceof String) {
                String paramVal = (String) params.get(listName);
                if (paramVal.startsWith("LIST="))
                    return new ListData(paramVal.substring(5));
                else
                    result.add(paramVal);
            }

            return result;
        }
    }
    private static ListData EMPTY_LIST = new ListData();


    /** Lookup the named string and return it.
     *
     * if the name is enclosed in braces [] it refers to a data
     * element - otherwise it refers to a cgi environment variable or
     * a query/post parameter.
     *
     * If no string is found by that name, will return an empty string.
     */
    private String getString(String name) {
        if (name.startsWith("[")) {
            // listName names a data element
            name = trimDelim(name);
            SimpleData d = getSimpleValue(name);
            return (d == null ? "" : d.format());
        } else if ("_UNIQUE_".equals(name)) {
            return Long.toString(uniqueNumber++);
        } else {
                                // try for an environment variable first.
            Object result = env.get(name);
            if (result instanceof String) return (String) result;

                                // look for a parameter value.
            result = params.get(name);
            if (result instanceof String) return (String) result;
            if (result != null) return result.toString();

                                // look for a user setting
            result = Settings.getVal(name);
            if (result != null) return result.toString();

            return "";
        }
    }
    private static long uniqueNumber = System.currentTimeMillis();

    /** lookup a named value in the data repository. */
    private SimpleData getSimpleValue(String name) {
        if (data == null) return null;
        return data.getSimpleValue(data.createDataName(prefix, name));
    }

    /** trim the first and last character from a string */
    private String trimDelim(String str) {
        return str.substring(1, str.length() - 1);
    }

    /** look up a named data element and perform a test() on it. */
    private boolean testDataElem(String name) {
        return testDataElem(name, false);
    }
    private boolean testDataElem(String name, boolean checkDefined) {
        if (name.startsWith("[")) {
            // listName names a data element
            name = trimDelim(name);
            if (checkDefined) {
                name = data.createDataName(prefix, name);
                return (data.getValue(name) != null);
            } else {
                SimpleData d = getSimpleValue(name);
                return (d == null ? false : d.test());
            }
        }
        return false;
    }

    /** Class which can locate and parse directives of the form <!--#foo -->
     */
    private class DirectiveMatch {
        StringBuffer buf;
        public int begin, end;
        public String directive, contents;
        private Map attributes = null;

        /** Find the first occurrence of the named directive in buf. */
        public DirectiveMatch(StringBuffer buf, String directive) {
            this(buf, directive, 0, true); }

        /** Find a directive in buf */
        public DirectiveMatch(StringBuffer buf, String directive,
                              int pos, boolean after) {
            this.buf = buf;
            this.directive = directive;
            String dirStart = DIRECTIVE_START + directive;
            if (after)
                begin = StringUtils.indexOf(buf, dirStart, pos);
            else
                begin = StringUtils.lastIndexOf(buf, dirStart, pos);

            if (begin == -1) return;

            end = StringUtils.indexOf(buf, DIRECTIVE_END, begin);
            if (end == -1) {
                begin = -1;
                return;
            }
            contents = buf.substring(begin + dirStart.length(), end);
            end += DIRECTIVE_END.length();

            if (contents.endsWith("#")) {
                contents = contents.substring(0, contents.length()-1);
                while (end < buf.length() &&
                       Character.isWhitespace(buf.charAt(end)))
                    end++;
            }

            if (directive.length() == 0) {
                StringTokenizer tok = new StringTokenizer(contents);
                if (tok.hasMoreTokens())
                    this.directive = tok.nextToken();
                if (tok.hasMoreTokens())
                    contents = tok.nextToken("\u0000");
                else
                    contents = "";
            }
        }

        /** @return true if a directive was found */
        public boolean matches() { return begin != -1; }

        /** replace the directive found with the given text. */
        public void replace(String text) {
            buf.replace(begin, end, text);
            end = begin + text.length();
        }

        /** parse the inner contents of the directive as a set of
         * attrName=attrValue pairs, and return the value associated
         * with the named attribute. returns null if the attribute was
         * not present. */
        public String getAttribute(String attrName) {
            if (attributes == null)
                attributes = parseAttributes();
            return dirUnencode((String) attributes.get(attrName));
        }

        /** parse the inner contents of the directive as a set of
         * attrName=attrValue pairs */
        private Map parseAttributes() {
            return HTMLUtils.parseAttributes(contents);
        }

        public void rename(String newName) {
            int from = begin + DIRECTIVE_START.length();
            int to   = from + directive.length();
            buf.replace(from, to, newName);
            end = end + newName.length() - directive.length();
            directive = newName;
        }
    }


    private static class QueryParser extends TinyCGIBase {
        protected boolean supportQueryFiles() { return false; }
    }

    /** @return true if t is null or the empty string */
    private boolean isNull(String t) {
        return (t == null || t.length() == 0);
    }
    private int whitespacePos(String t) {
        int result = t.indexOf(' ');
        if (result == -1) result = t.indexOf('\t');
        if (result == -1) result = t.indexOf('\r');
        if (result == -1) result = t.indexOf('\n');
        return result;
    }

    private boolean blockMatch(String name, String directive) {
        return (directive != null && directive.endsWith(name));
    }
    private String blockNum(String name, String directive) {
        return directive.substring(0, directive.length() - name.length());
    }

    private static final String DIRECTIVE_START = "<!--#";
    private static final String DIRECTIVE_END   = "-->";
}
TOP

Related Classes of pspdash.HTMLPreprocessor$DirectiveMatch

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
,'ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');