All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.x5.template.Snippet Maven / Gradle / Ivy

There is a newer version: 3.6.2
Show newest version
package com.x5.template;

import java.io.Writer;
import java.util.ArrayList;
import java.util.Map;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Snippet
{
    private List parts = null;
    private String simpleText = null;
    private String origin = null;
    private long lastAccess = 0;

    private static boolean useCache = isCacheEnabled();
    private static Map snippetCache = useCache ? new ConcurrentHashMap(512) : null;
    private static long lastGC = 0;
    private static long gcCounter = 0;
    private static final int gcInterval = 500;
    private static final long CAN_GC_AFTER = 1000 * 60;

    private Snippet(String template)
    {
        parseParts(template);
    }

    private Snippet(String template, String origin)
    {
        this.origin = origin;
        parseParts(template);
    }

    public static Snippet getSnippet(String template)
    {
        if (Snippet.useCache) {
            return getSnippetFromCache(template, null);
        } else {
            return new Snippet(template);
        }
    }

    public static Snippet getSnippet(String template, String origin)
    {
        if (Snippet.useCache) {
            return getSnippetFromCache(template, origin);
        } else {
            return new Snippet(template, origin);
        }
    }

    /* premature optimization? */
    private static Snippet getSnippetFromCache(String template, String origin)
    {
        long timestamp = System.currentTimeMillis();

        if (++gcCounter % gcInterval == 0) {
            pruneCache(timestamp);
        }

        Snippet s = snippetCache.get(template);
        if (s != null) {
            s.lastAccess = timestamp;
            return s;
        } else {
            s = new Snippet(template, origin);
            s.lastAccess = timestamp;
            snippetCache.put(template, s);
            return s;
        }
    }

    private static boolean isCacheEnabled()
    {
        String useCacheProperty = System.getProperty("chunk.snippetcache");
        if (useCacheProperty == null) {
            return false;
        } else {
            return true;
        }
    }

    private static void pruneCache(long timestamp)
    {
        long threshhold = timestamp - CAN_GC_AFTER;
        if (lastGC > threshhold) return;

        Iterator> i = snippetCache.entrySet().iterator();
        while (i.hasNext()) {
            java.util.Map.Entry entry = i.next();
            if (entry.getValue().lastAccess < threshhold) {
                snippetCache.remove(entry.getKey());
            }
        }
        lastGC = timestamp;
    }

    public Snippet(List bodyParts)
    {
        if (bodyParts == null || bodyParts.size() == 0) {
            simpleText = "";
        } else {
            this.parts = bodyParts;
        }
    }

    public Snippet(List bodyParts, int from, int to)
    {
        if (bodyParts == null || bodyParts.size() == 0) {
            simpleText = "";
        } else {
            ArrayList subParts = new ArrayList();
            for (int i=from; i getParts()
    {
        return parts;
    }

    public static final String MAGIC_CHARS = "~$%^./!*=+_";

    /**
     * One pass over the string.  Identify all dynamic tags and slice into
     * parts - literals will pass directly into output, dynamic SnippetTag parts
     * will get interpreted.
     *
     * Second step is to nest block tags properly.
     *
     * @param template
     */
    private void parseParts(String template)
    {
        // pre-compile template -- slice and dice ALL tags as separate parts.
        if (template == null) return;

        // first pass was just to catch literals. reset.
        simpleText = null;

        char c, c2, e0;

        // how many regex delims left before tag parser has to wake up again
        int regexDelimCount = 0;

        int marker = 0; // beginning of latest static span
        int tagStart = -1; // beginning of latest tag span
        int exprStart = -1; // beginning of tag expression
        int trailingBackslashes = 0; // track escape chars so we can ignore escapes

        // the parser has to ignore certain chars when in various states
        boolean insideRegex = false;
        boolean insideTrToken = false;
        boolean insideComment = false;
        boolean insideLiteral = false;

        // magicChar determines what sort of tag are we forming
        char magicChar = 0;

        int len = template.length();

        for (int i=0; i= len) break;

                //c2 = chars[i+1];
                c2 = template.charAt(i+1);
                // collecting static until tag comes along
                if (c == '{') {
                    if (MAGIC_CHARS.indexOf(c2) > -1) {
                        // avoid being tricked by javascript that only smells like a tag.
                        if (c2 == '$' && isJavascriptHeadFake(i,template)) {
                            // not a chunk tag, keep scanning, nothing to see here.
                        } else {
                            tagStart = i;
                            trailingBackslashes = 0;
                            // {% signals that whitespace is ok around the tag expression
                            // eg: {% $tag %}
                            //     {% if ($tag) %} ... {% endif %}
                            if (c2 == '%') {
                                i += 2;
                                if (i == len) break;
                                e0 = template.charAt(i);
                                while (i < len && Character.isWhitespace(e0)) {
                                    i++;
                                    e0 = template.charAt(i);
                                }
                                if (MAGIC_CHARS.indexOf(e0) > -1) {
                                    magicChar = e0;
                                    exprStart = i+1;
                                } else {
                                    // implicit bareword/command eg {% exec %}
                                    magicChar = '.';
                                    exprStart = i;
                                }
                            } else {
                                // FOUND TAG START
                                i++;
                                magicChar = c2;
                                exprStart = i+1;
                            }
                        }
                    }
                } else if (c == '_' && c2 == '[') {
                    // localization token!
                    tagStart = i;
                    insideTrToken = true;
                    i++;
                }

            } else {

                // tagStart is positive value -- scan for tag-end

                if (insideTrToken && c == ']') {
                    if (trailingBackslashes % 2 == 0) {
                        // FOUND TOKEN END
                        parts = getPartsForAppend();
                        if (marker < tagStart) {
                            SnippetPart literal = new SnippetPart(template.substring(marker,tagStart));
                            literal.setLiteral(true);
                            parts.add(literal);
                        }
                        String wholeTag = template.substring(tagStart,i+1);
                        String tokenStr = template.substring(tagStart+2,i);
                        SnippetToken token = new SnippetToken(wholeTag,tokenStr);
                        parts.add(token);
                        // reset scan mode
                        marker = i+1;
                        tagStart = -1;
                        insideTrToken = false;
                    }
                } else if (c == '}') {
                    if (!insideRegex && trailingBackslashes % 2 == 0) {
                        if (insideLiteral) {
                            // scanning for end of literal
                            if (isLiteralClose(template,magicChar,tagStart,i)) {
                                String literalText = template.substring(marker,i+1);
                                SnippetPart literal = new SnippetPart(literalText);
                                literal.setLiteral(true);
                                parts.add(literal);
                                // reset...
                                marker = i+1;
                                insideLiteral = false;
                            }
                            tagStart = -1;
                        } else if (magicChar == '!') {
                            // seeking end of comment
                            char c0 = template.charAt(i-1);
                            char c00 = template.charAt(i-2);
                            if (c0 == '-' && c00 == '-') {
                                // FOUND COMMENT END - extract
                                i = extractComment(marker, tagStart, i, template, len);

                                tagStart = -1;
                                marker = i+1;
                                insideComment = false;
                            } else {
                                // this curly brace is not the end of the comment
                                // keep scanning
                                insideComment = true;
                            }
                        } else {
                            //////////////////////////////////////////////////////////
                            // FOUND TAG END, extract and add to sequence along with
                            // preceding static content, if any.
                            //////////////////////////////////////////////////////////
                            SnippetPart tag = extractTag(magicChar,template,marker,tagStart,exprStart,i);
                            if (tag != null) {
                                parts.add(tag);
                                // reset scan mode
                                marker = i+1;
                                tagStart = -1;
                            } else {
                                // uh-oh, literal block
                                insideLiteral = true;
                                marker = tagStart;
                                tagStart = -1;
                            }
                        }
                    }
                } else if (c == '/' && trailingBackslashes % 2 == 0) {
                    // MIGHT BE INSIDE REGEX...
                    // ignore curly braces until we get past this regex span
                    if (regexDelimCount > 0) {
                        regexDelimCount--;
                        if (regexDelimCount < 1) {
                            // found END of this regex
                            insideRegex = false;
                        }
                    } else {
                        //char c0 = chars[i-1];
                        //char c00 = chars[i-2];
                        char c0 = template.charAt(i-1);
                        char c00 = template.charAt(i-2);
                        if (c0 == 's' && c00 == '|') {
                            // found {~tag|s/.../.../}
                            // need to find two more
                            regexDelimCount = 2;
                            insideRegex = true;
                        } else if (c0 == 'm' && (c00 == ',' || c00 == '(')) {
                            // found {~tag|...(m/.../,...,m/.../,...)}
                            regexDelimCount = 1;
                            insideRegex = true;
                        } else if (c0 == ',' || (c0 == '(' && c00 == 'h')) {
                            // found {~tag|...(/regex/,...,/regex/,...)}
                            regexDelimCount = 1;
                            insideRegex = true;
                        }
                    }
                } else if (c == '\\') {
                    trailingBackslashes++;
                } else if (trailingBackslashes > 0) {
                    trailingBackslashes = 0;
                }
            }
        }

        if (parts == null) {
            // no parts? avoid overhead of ArrayList
            simpleText = template;
        } else {
            if (insideComment) {
                // add marker-to-end as comment
                SnippetPart finalComment = new SnippetComment(template.substring(marker));
                parts.add(finalComment);
            } else if (marker < template.length()) {
                // add marker-to-end as literal
                SnippetPart finalLiteral = new SnippetPart(template.substring(marker));
                finalLiteral.setLiteral(true);
                parts.add(finalLiteral);
            }
            groupBlocks(parts);
        }

    }

    /**
     * Sniff out invalid tags that match specific common profile.
     *
     * @return
     */
    private static boolean isJavascriptHeadFake(int i, String template)
    {
        int len = template.length();
        // verify that this is tag and not function(){$.someJQueryFn()}
        if (i+2 >= len) return true;

        char c3 = template.charAt(i+2);
        // {$.XXX} {$(XXX)} and {$ XXX} and {$$xxx} are never valid chunk tags
        if (c3 == '.' || c3 == '(' || c3 == ' ' || c3 == '$') {
            // eep, not a tag!  probably a bit of javascript/jQuery.
            return true;
        }
        // also, don't be fooled by function(){$j(...)}
        // this catches a lot of prototype utility calls.
        if (i+3 < len) {
            char c4 = template.charAt(i+3);
            if (c4 == '(') {
                return true;
            }
        }

        // did not detect javascript.  probably a legit tag.
        return false;
    }

    public boolean isSimple()
    {
        return simpleText != null;
    }

    private boolean isLiteralClose(String template, char magicChar, int tagStart, int i)
    {
        if (magicChar == '.' && i-tagStart > 8) {
            if (template.charAt(tagStart+1) == '%') {
                int exprEnd = i - 1;
                if (template.charAt(exprEnd) == '%') exprEnd--;
                String expr = template.substring(tagStart+2, exprEnd).trim();
                if (expr.equals("endliteral") || expr.equals("/literal")) {
                    // {% endliteral %}
                    return true;
                }
            }
        } else if (magicChar == '^') {
            if (tagStart == i-2) {
                // {^}
                return true;
            }
        } else if (magicChar == '~') {
            if (tagStart == i-3 && template.charAt(i-1) == '.') {
                // {~.}
                return true;
            } else if (tagStart == i-11 && template.substring(tagStart+3,i).equals("/literal")) {
                // {~./literal}
                return true;
            }
        } else if (tagStart == i-9 && magicChar == '/') {
            if (template.substring(tagStart+1,i).equals("/literal")) {
                // {/literal}
                return true;
            }
        }
        return false;
    }

    private SnippetPart extractTag(char magicChar, String template,
            int marker, int tagStart, int exprStart, int i)
    {
        // FOUND TAG END
        // extract and add to part sequence
        parts = getPartsForAppend();

        // any static content leading up to tag?  capture static part.
        if (marker < tagStart) {
            SnippetPart literal = new SnippetPart(template.substring(marker,tagStart));
            literal.setLiteral(true);
            parts.add(literal);
        }

        // and now focus on the tag...
        String wholeTag = template.substring(tagStart,i+1);

        // remove trailing whitespace from expr if nec.
        if (wholeTag.charAt(1) == '%') {
            if (wholeTag.charAt(wholeTag.length()-2) == '%') i--;
            while (i > tagStart && Character.isWhitespace(wholeTag.charAt(i-tagStart-1))) {
                i--;
            }
        }

        if (magicChar == '~' || magicChar == '$') {
            String gooeyCenter = template.substring(exprStart, i);
            if (gooeyCenter.startsWith(".end")) {
                gooeyCenter = "./" + gooeyCenter.substring(4);
            }
            SnippetTag tag = new SnippetTag(wholeTag, gooeyCenter);
            return tag;
        } else if (magicChar == '^' || magicChar == '.') {
            String gooeyCenter = template.substring(exprStart,i);
            // check for literal block (includes abandoned {^^} shorthand syntax)
            if (gooeyCenter.equals("literal") || gooeyCenter.equals("^")) {
                // null return signals literal-start to caller
                return null;
            }
            if (gooeyCenter.startsWith("end")) {
                gooeyCenter = "/" + gooeyCenter.substring(3);
            }
            // prepend . to signal bareword/command
            SnippetTag tag = new SnippetTag(wholeTag,"."+gooeyCenter);
            return tag;
        } else if (magicChar == '/') {
            String gooeyCenter = template.substring(exprStart,i);
            // expand {/ to {^/ to {~./
            SnippetTag tag = new SnippetTag(wholeTag,"./"+gooeyCenter);
            return tag;
        } else if (magicChar == '*') {
            // convert macro syntax to internal macro block-tag
            if (wholeTag.length() == 3) {
                // this marks the end of the macro block {*}
                SnippetTag tag = new SnippetTag(wholeTag,"."+MacroTag.MACRO_END_MARKER);
                return tag;
            } else {
                int refEnd = i;
                if (template.charAt(i-1) == '*') refEnd--;
                String macroTemplate = template.substring(exprStart,refEnd).trim();
                SnippetTag macroHead = new SnippetTag(wholeTag,"."+MacroTag.MACRO_MARKER+" "+macroTemplate+" original");
                return macroHead;
            }
        } else if (magicChar == '=') {
            if (wholeTag.length() == 3) {
                // this marks the end of a macro def {=}
                SnippetTag tag = new SnippetTag(wholeTag,"=");
                return tag;
            }
        } else if (magicChar == '_') {
            // for example: {_[token %s %s],~with,~args}
            SnippetToken token = SnippetToken.parseTokenWithArgs(wholeTag);
            return token;
        } else if (magicChar == '+') {
            // include shorthand: {+template#ref} or {+(cond)template#ref}
            if (wholeTag.startsWith("{+(") || wholeTag.indexOf("+(") == exprStart - tagStart) {
                String includeIfTag = ".includeIf(" + template.substring(exprStart+1,i);
                SnippetTag condInclude = new SnippetTag(wholeTag,includeIfTag);
                return condInclude;
            } else {
                String includeTag = ".include " + template.substring(exprStart,i);
                SnippetTag include = new SnippetTag(wholeTag,includeTag);
                return include;
            }
        } else if (magicChar == '%') {
            // invalid expr, pass through as literal
            SnippetPart literal = new SnippetPart(wholeTag);
            literal.setLiteral(true);
            return literal;
        }

        // huh? in other words, we should never reach here.
        // but just in case, let's make sure the wheels don't completely come off.
        SnippetPart wackyTag = new SnippetPart(wholeTag);
        return wackyTag;
    }

    private List getPartsForAppend()
    {
        if (parts == null) parts = new ArrayList();
        return parts;
    }

    private int extractComment(int marker, int tagStart, int i, String template, int len)
    {
        // strip comment into non-rendering part
        parts = getPartsForAppend();
        String precedingComment = null;

        int startOfThisLine = marker;
        if (marker < tagStart) {
            precedingComment = template.substring(marker,tagStart);

            // might need to strip empty line left by stripped comment.
            // locate the start of this line by backtracking
            int lineBreakPos = precedingComment.lastIndexOf('\n');
            if (lineBreakPos > -1) {
                startOfThisLine = marker + lineBreakPos + 1;
            }

        }

        // If eating comment leaves empty line, eat empty line too.
        //
        // ie, IF the span between final linebreak and tag is all whitespace
        // (or tag is not preceded by anything but whitespace)
        // *AND* the template following the comment begins with whitespace
        // and a linebreak (or the template-end), eat the preceding whitespace
        // and skip past the end of line.
        if (startOfThisLine == tagStart || template.substring(startOfThisLine,tagStart).trim().length()==0) {
            // locate end of this line
            int endOfLine = template.indexOf('\n',i+1);
            if (endOfLine < 0) {
                endOfLine = len;
            }
            if (template.substring(i+1,endOfLine).trim().length()==0) {
                // yep, need to eat empty line and linebreak
                if (startOfThisLine < tagStart) {
                    // strip leading whitespace from preceding static
                    // and shift that whitespace into the comment text
                    precedingComment = template.substring(marker,startOfThisLine);
                    tagStart = startOfThisLine;
                }
                // skip ahead to end of condemned line
                i = (endOfLine == len) ? endOfLine-1 : endOfLine;
            }
        }

        // preserve static leading up to comment
        if (precedingComment != null) {
            SnippetPart literal = new SnippetPart(precedingComment);
            literal.setLiteral(true);
            parts.add(literal);
        }

        // this grabs the comment tag as well as any surrounding
        // whitespace that's being eaten (see above)
        String wholeComment = template.substring(tagStart,i+1);

        if (origin == null && parts.size() == 0 && wholeComment.startsWith("{!--@ORIGIN:")) {
            // parse origin and don't commit this comment, it will
            // get regenerated automatically if toString() is ever called.
            int endOfOrigin = wholeComment.indexOf("@", 12);
            if (endOfOrigin < 0) endOfOrigin = wholeComment.length();
            origin = wholeComment.substring(12, endOfOrigin);
        } else {
            SnippetComment comment = new SnippetComment(wholeComment);
            parts.add(comment);
        }

        return i;
    }

    private void groupBlocks(List bodyParts)
    {
        for (int i=0; i i) {
                        // remove these parts and place them all together
                        // in a new SnippetBlockTag
                        SnippetTag endTag = (SnippetTag)bodyParts.remove(j);

                        ArrayList subBodyParts = new ArrayList();
                        for (int x=i+1; x=i; x--) bodyParts.remove(x);

                        // recurse
                        groupBlocks(subBodyParts);

                        SnippetBlockTag blockTag = new SnippetBlockTag(tag,subBodyParts,endTag,origin);
                        bodyParts.add(i,blockTag);

                        if (blockTag.doSmartTrimAroundBlock()) {
                            int trimType = smartTrimBeforeBlockStart(bodyParts,blockTag,i-1);
                            if (trimType != BLOCKSTART_MIDLINE) {
                                smartTrimAfterBlockEnd(bodyParts,blockTag,i+1);
                            }
                        }
                    } else {
                        // unmatched block tag!!  output error notice.
                        String errMsg = "[ERROR in template! "+helper.getBlockStartMarker()+" block with no matching end marker! ]";
                        SnippetError errorPart = new SnippetError(errMsg);
                        bodyParts.add(i+1,errorPart);
                        i++;
                    }
                }
            }
        }
    }

    private static final int BLOCKSTART_TRIMMED_ALONE = -1;
    private static final int BLOCKSTART_ALONE = 0;
    private static final int BLOCKSTART_WITHBODY = 1;
    private static final int BLOCKSTART_MIDLINE = 2;
    private int smartTrimBeforeBlockStart(List parts, SnippetBlockTag blockTag, int prevPartIdx)
    {
        // first, ensure that block body begins with (whitespace+)LF
        // if not, abort -- smart-trim is not warranted.
        if (blockBodyStartsOnSameLine(blockTag)) {
            // abort
            return BLOCKSTART_WITHBODY;
        }

        // there was nothing to trim before block, but
        // might still need to trim after.
        if (prevPartIdx < 0) return BLOCKSTART_ALONE;

        SnippetPart prevPart = parts.get(prevPartIdx);

        // skip non-rendering comments
        while (prevPart instanceof SnippetComment) {
            prevPartIdx--;
            if (prevPartIdx < 0) return BLOCKSTART_ALONE;
            prevPart = parts.get(prevPartIdx);
        }

        if (!prevPart.isLiteral()) {
            if (prevPart instanceof SnippetBlockTag) {
                prevPart = ((SnippetBlockTag)prevPart).getCloseTag();
            } else {
                // abort
                return BLOCKSTART_MIDLINE;
            }
        }

        String text = prevPart.getText();
        if (text.length() == 0) {
            return smartTrimBeforeBlockStart(parts, blockTag, prevPartIdx-1);
        }

        // if the previous block ends with LF + whitespace, trim whitespace
        int i = text.length() - 1;
        char c = text.charAt(i);
        boolean eatWhitespace = false;
        while (Character.isWhitespace(c)) {
            if (c == '\n' || c == '\r') {
                i++;
                eatWhitespace = true;
                break;
            }
            i--;
            if (i<0) {
                i = 0;
                if (prevPartIdx == 0) {
                    // beginning of snippet is also a "new line"
                    eatWhitespace = true;
                } else {
                    // Still just whitespace so far,
                    // no newlines -- should keep scanning
                    // backwards through parts.  recurse.
                    int trimState = smartTrimBeforeBlockStart(parts, blockTag, prevPartIdx-1);
                    if (trimState > 0) {
                        // non-whitespace found between blockstart and start of line
                        // or, block body starts on same line
                        return trimState;
                    } else {
                        eatWhitespace = true;
                    }
                }
                break;
            }
            c = text.charAt(i);
        }

        if (eatWhitespace) {
            prevPart.setText( text.substring(0,i) );
            // TODO preserve eaten space as non-rendering part?
        }

        return eatWhitespace ? BLOCKSTART_TRIMMED_ALONE : BLOCKSTART_MIDLINE;
    }

    private static final Pattern UNIVERSAL_LF = Pattern.compile("\n|\r\n|\r\r");

    private boolean blockBodyStartsOnSameLine(SnippetBlockTag blockTag)
    {
        Snippet blockBody = blockTag.getBody();
        if (blockBody.parts != null) {
            int i = 0;
            while (i < blockBody.parts.size()) {
                SnippetPart firstPart = blockBody.parts.get(i);
                // skip comments... unless comment already ate LF
                if (firstPart instanceof SnippetComment) {
                    String commentText = firstPart.toString();
                    if (commentText.charAt(commentText.length()-1) != '}') {
                        // ate linefeed already, but it was there.
                        // do not abort.
                        return false;
                    }
                    i++;
                    continue;
                }

                // must start with (whitespace)+LF or no smart-trim
                if (firstPart.isLiteral()) {
                    String text = firstPart.getText();
                    Matcher m = UNIVERSAL_LF.matcher(text);
                    if (m.find()) {
                        int firstLF = m.start();
                        if (text.substring(0,firstLF).trim().length() == 0) {
                            // trim this too?
                        } else {
                            // abort! no smart-trim.
                            return true;
                        }
                    }
                }
                return false;
            }
        }

        return false;
    }

    private void smartTrimAfterBlockEnd(List parts, SnippetBlockTag blockTag, int nextPartIdx)
    {
        if (parts.size() <= nextPartIdx) return;
        SnippetPart nextPart = parts.get(nextPartIdx);

        // make sure to skip over non-rendering comments
        while (nextPart instanceof SnippetComment) {
            String commentText = nextPart.toString();
            if (commentText.charAt(commentText.length()-1) != '}') {
                // already ate, bail!  no double-trimming.
                return;
            }
            nextPartIdx++;
            if (parts.size() <= nextPartIdx) return;
            nextPart = parts.get(nextPartIdx);
        }

        if (nextPart.isLiteral()) {
            String text = nextPart.getText();
            // if the block begins with (whitespace+) LF, trim initial line
            Matcher m = UNIVERSAL_LF.matcher(text);

            if (m.find()) {
                int firstLF = m.start();
                if (text.substring(0,firstLF).trim().length() == 0) {
                    nextPart.setText( text.substring(m.end()) );
                    // shift whitespace into blockTag's end-tag?
                    // or make a non-rendering part?
                    blockTag.getCloseTag().snippetText += text.substring(0,m.end());
                }
            }
        }
    }

    public String toString()
    {
        return _toString(true);
    }

    private String _toString(boolean withOrigin)
    {
        if (simpleText != null) return simpleText;
        if (parts == null) return null;

        // reassemble parts back into original template
        StringBuilder sb = new StringBuilder();

        if (withOrigin && origin != null) {
            sb.append("{!--@ORIGIN:");
            sb.append(origin);
            sb.append("@--}");
        }

        for (SnippetPart part : parts) {
            sb.append(part.toString());
        }
        return sb.toString();
    }

    /**
     * Do not prepend comment with template origin.
     */
    public String toSimpleString()
    {
        return _toString(false);
    }

    public void render(Writer out, Chunk rules, int depth)
    throws java.io.IOException
    {
        if (simpleText != null) {
            out.append(simpleText);
        } else if (parts != null) {
            for (SnippetPart part : parts) {
                part.render(out, rules, origin, depth+1);
            }
        }
    }

    public Snippet copy()
    {
        if (simpleText != null) {
            Snippet copy = new Snippet("");
            copy.simpleText = simpleText;
            return copy;
        } else {
            List partsCopy = new ArrayList();
            partsCopy.addAll(parts);
            Snippet copy = new Snippet(partsCopy);
            copy.origin = this.origin;
            return copy;
        }
    }

    public static Snippet makeLiteralSnippet(String literal)
    {
        SnippetPart x = new SnippetPart(literal);
        x.setLiteral(true);

        List listOfOne = new ArrayList();
        listOfOne.add(x);

        return new Snippet(listOfOne);
    }

    /**
     * Let's say your whole snippet is just {$x}
     * In certain contexts, it's nice to resolve to the value of
     * x instead of the chunk-evaluation of x into a string.
     */
    boolean isSimplePointer()
    {
        if (parts != null && parts.size() == 1) {
            SnippetPart onlyPart = parts.get(0);
            if (onlyPart instanceof SnippetTag) {
                return true;
            }
        }
        return false;
    }

    String getPointer()
    {
        if (isSimplePointer()) {
            SnippetPart onlyPart = parts.get(0);
            SnippetTag tag = (SnippetTag)onlyPart;
            return tag.getTag();
        }
        return null;
    }

    SnippetTag getPointerTag()
    {
        if (isSimplePointer()) {
            SnippetPart onlyPart = parts.get(0);
            SnippetTag tag = (SnippetTag)onlyPart;
            return tag;
        }
        return null;
    }

    static Snippet consolidateSnippets(Vector template)
    throws EndOfSnippetException
    {
        if (template == null) return null;
        if (template.size() == 1) return template.get(0);
        for (int i=1; i merged = new ArrayList();
        for (int i=0; i parts = s.ungroupBlocks();
            if (parts != null) {
                merged.addAll(parts);
            }
        }

        Snippet voltron = new Snippet(merged);
        voltron.origin = template.get(0).origin;
        voltron.groupBlocks(voltron.parts);

        return voltron;
    }

    private List ungroupBlocks()
    {
        if (parts == null) {
            if (simpleText.length() < 1) {
                return null;
            } else {
                List onePart = new ArrayList();
                SnippetPart simplePart = new SnippetPart(simpleText);
                simplePart.setLiteral(true);
                onePart.add(simplePart);
                return onePart;
            }
        } else {
            // best case: no blocks
            boolean noBlocks = true;
            for (SnippetPart part : parts) {
                if (part instanceof SnippetBlockTag || part instanceof SnippetError) {
                    noBlocks = false;
                    break;
                }
            }
            if (noBlocks) {
                return parts;
            }
            // otherwise...
            List flat = new ArrayList();
            for (SnippetPart part : parts) {
                if (part instanceof SnippetBlockTag) {
                    // flatten block tag...
                    //  how to detect un-closed block tag?
                    SnippetBlockTag block = (SnippetBlockTag)part;
                    flat.add(block.getOpenTag());
                    Snippet body = block.getBody();
                    flat.addAll(body.ungroupBlocks());
                    flat.add(block.getCloseTag());
                } else if (part instanceof SnippetError) {
                    // should qualify this somehow but for now, just
                    // lose the error.
                } else {
                    flat.add(part);
                }
            }
            return flat;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy