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

com.lyncode.jtwig.parser.parboiled.JtwigContentParser Maven / Gradle / Ivy

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

package com.lyncode.jtwig.parser.parboiled;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.lyncode.jtwig.addons.Addon;
import com.lyncode.jtwig.addons.AddonModel;
import com.lyncode.jtwig.content.api.Compilable;
import com.lyncode.jtwig.content.api.Tag;
import com.lyncode.jtwig.content.model.compilable.*;
import com.lyncode.jtwig.content.model.tag.WhiteSpaceControl;
import com.lyncode.jtwig.exception.ParseBypassException;
import com.lyncode.jtwig.exception.ParseException;
import com.lyncode.jtwig.exception.ResourceException;
import com.lyncode.jtwig.expressions.model.Constant;
import com.lyncode.jtwig.parser.config.ParserConfiguration;
import com.lyncode.jtwig.parser.model.JtwigKeyword;
import com.lyncode.jtwig.parser.model.JtwigSymbol;
import com.lyncode.jtwig.resource.JtwigResource;
import org.parboiled.BaseParser;
import org.parboiled.Rule;
import org.parboiled.common.FileUtils;
import org.parboiled.errors.ParserRuntimeException;
import org.parboiled.parserunners.ReportingParseRunner;
import org.parboiled.support.ParsingResult;

import javax.annotation.Nullable;
import java.nio.charset.Charset;
import java.util.Collection;

import static com.lyncode.jtwig.parser.model.JtwigKeyword.*;
import static com.lyncode.jtwig.parser.model.JtwigSymbol.ATTR;
import static com.lyncode.jtwig.parser.model.JtwigSymbol.COMMA;
import static com.lyncode.jtwig.parser.model.JtwigTagProperty.Trim;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.parboiled.Parboiled.createParser;

public class JtwigContentParser extends JtwigBaseParser {
    public static JtwigContentParser newParser(
            JtwigResource resource,
            ParserConfiguration configuration
    ) {
        return createParser(JtwigContentParser.class, resource, configuration);
    }

    public static Compilable parse(JtwigContentParser parser, JtwigResource input) throws ParseException {
        try {
            ReportingParseRunner runner = new ReportingParseRunner<>(parser.start());
            ParsingResult result = runner.run(
                    FileUtils.readAllText(input.retrieve(), Charset.defaultCharset()));
            return result.resultValue;
        } catch (ParserRuntimeException e) {
            if (e.getCause() instanceof ParseBypassException) {
                ParseException innerException = ((ParseBypassException) e.getCause()).getInnerException();
                innerException.setExpression(e.getMessage());
                throw innerException;
            } else {
                throw new ParseException(e);
            }
        } catch (ResourceException e) {
            throw new ParseException(e);
        }
    }

    final JtwigBasicParser basicParser;
    final JtwigExpressionParser expressionParser;
    final JtwigTagPropertyParser tagPropertyParser;

    Addon[] contentAddonParsers;
    Collection> contentAddons;
    ParserConfiguration configuration;

    public JtwigContentParser(JtwigResource resource, ParserConfiguration configuration) {
        super(resource);
        basicParser = createParser(JtwigBasicParser.class, configuration);
        tagPropertyParser = createParser(JtwigTagPropertyParser.class, configuration);
        expressionParser = createParser(JtwigExpressionParser.class, resource, configuration);

        this.contentAddons = Collections2.transform(configuration.addons().list(), toBaseParser());
        this.configuration = configuration;

        contentAddonParsers = new Addon[contentAddons.size()];

        int i = 0;
        for (Class contentAddon : contentAddons) {
            contentAddonParsers[i++] = (Addon) createParser(contentAddon, resource, configuration);
        }
    }

    private Function, Class> toBaseParser() {
        return new Function, Class>() {
            @Nullable
            @Override
            public Class apply(@Nullable Class input) {
                return input;
            }
        };
    }

    public Rule start() {
        return FirstOf(
                extendTemplate(),
                normalTemplate()
        );
    }

    Rule extendTemplate() {
        return Sequence(
                basicParser.spacing(),
                Sequence(
                        basicParser.openCode(),
                        basicParser.spacing(),
                        keyword(EXTENDS),
                        mandatory(
                                Sequence(
                                        basicParser.stringLiteral(),
                                        basicParser.spacing(),
                                        basicParser.closeCode(),
                                        push(new Extends(basicParser.pop())),
                                        ZeroOrMore(
                                                basicParser.spacing(),
                                                block(),
                                                action(peek(1, Extends.class).add(pop(Block.class)))
                                        ),
                                        basicParser.spacing(),
                                        EOI
                                ),
                                new ParseException("Wrong include syntax")
                        )
                )
        );
    }

    Rule normalTemplate() {
        return Sequence(
                content(),
                EOI
        );
    }

    Rule content() {
        return Sequence(
                push(new Sequence()),
                ZeroOrMore(
                        FirstOf(
                                addToContent(output()),
                                addToContent(block()),
                                addToContent(include()),
                                addToContent(embed()),
//                                addToContent(filter()),
                                addToContent(forEach()),
                                addToContent(ifCondition()),
                                addToContent(set()),
                                addToContent(verbatim()),
                                addToContent(comment()),
                                addToContent(contentParsers()),
                                Sequence(
                                        openCode(),
                                        TestNot(
                                                FirstOf(
                                                        keyword(ENDBLOCK),
                                                        keyword(ENDFOR),
                                                        keyword(ENDIF),
                                                        keyword(IF),
                                                        keyword(BLOCK),
                                                        keyword(FOR),
                                                        keyword(SET),
                                                        keyword(ELSEIF),
                                                        keyword(ELSE),
                                                        keyword(VERBATIM),
                                                        keyword(ENDFILTER),
                                                        keywordsContent()
                                                )
                                        ),
                                        throwException(new ParseException("Unknown tag"))
                                ),
                                addToContent(text())
                        )
                )
        );
    }

    Rule keywordsContent() {
        if (contentAddonParsers.length == 0) {
            return Test(false);
        }
        Rule[] rules = new Rule[contentAddonParsers.length];
        for (int i = 0; i < contentAddonParsers.length; i++) {
            rules[i] = FirstOf(
                    basicParser.terminal(contentAddonParsers[i].beginKeyword()),
                    basicParser.terminal(contentAddonParsers[i].endKeyword())
            );
        }
        return FirstOf(rules);
    }

    Rule contentParsers() {
        if (contentAddonParsers.length == 0) {
            return Test(false);
        }
        Rule[] rules = new Rule[contentAddonParsers.length];
        for (int i = 0; i < contentAddonParsers.length; i++) {
            rules[i] = contentAddon(contentAddonParsers[i]);
        }
        return FirstOf(rules);
    }

    Rule contentAddon(Addon parser) {
        return Sequence(
                openCode(),
                basicParser.terminal(parser.beginKeyword()),
                basicParser.spacing(),
                parser.startRule(),
                mandatory(
                        Test(instanceOf(AddonModel.class).matches(peek())),
                        new ParseException(
                                "Addon parser not pushing a JtwigContentAddon object to the top of the stack")
                ),
                mandatory(
                        Sequence(
                                action(beforeBeginTrim()),
                                closeCode(),
                                action(afterBeginTrim()),
                                content(),
                                action(peek(1, AddonModel.class).withContent(pop(Sequence.class))),
                                openCode(),
                                basicParser.terminal(parser.endKeyword()),
                                basicParser.spacing(),
                                action(beforeEndTrim()),
                                closeCode(),
                                action(afterEndTrim())
                        ),
                        new ParseException("Wrong syntax for " + parser.beginKeyword())
                )
        );
    }

    Rule addToContent(Rule innerRule) {
        return Sequence(
                innerRule,
                action(peek(1, Sequence.class).add(pop()))
        );
    }

    Rule block() {
        return Sequence(
                openCode(),
                keyword(BLOCK),
                mandatory(
                        Sequence(
                                expressionParser.identifierAsString(),
                                push(new Block(expressionParser.popIdentifierAsString())),
                                action(beforeBeginTrim()),
                                closeCode(),
                                action(afterBeginTrim()),
                                content(),
                                action(peek(1, Block.class).withContent(pop(Sequence.class))),
                                openCode(),
                                action(beforeEndTrim()),
                                keyword(ENDBLOCK),
                                Optional(
                                        expressionParser.variable(),
                                        assertEqual(
                                                peek(Block.class).name(),
                                                (String) expressionParser.pop(Constant.class).getValue()
                                        )
                                ),
                                closeCode(),
                                action(afterEndTrim())
                        ),
                        new ParseException("Wrong block syntax")
                )
        );
    }

    boolean assertEqual(String value1, String value2) {
        if (!value1.equals(value2)) {
            return throwException(new ParseException("Start statement and ending block names do not match"));
        } else {
            return true;
        }
    }

    Rule include() {
        return Sequence(
                openCode(),
                keyword(INCLUDE),
                mandatory(
                        Sequence(
                                basicParser.stringLiteral(),
                                basicParser.spacing(),
                                push(new Include(currentPosition(), basicParser.pop())),
                                action(beforeBeginTrim()),
                                Optional(
                                        keyword(JtwigKeyword.WITH),
                                        expressionParser.map(),
                                        action(peek(1, Include.class).with(expressionParser.pop()))
                                ),
                                closeCode(),
                                action(afterEndTrim())
                        ),
                        new ParseException("Wrong include syntax")
                )
        );
    }

    Rule embed() {
        return Sequence(
                openCode(),
                keyword(EMBED),
                mandatory(
                        Sequence(
                                basicParser.stringLiteral(),
                                basicParser.spacing(),
                                closeCode(),
                                push(new Extends(basicParser.pop())),
                                ZeroOrMore(
                                        basicParser.spacing(),
                                        block(),
                                        action(peek(1, Extends.class).add(pop(Block.class)))
                                ),
                                basicParser.spacing(),
                                openCode(),
                                keyword(ENDEMBED),
                                closeCode()
                        ),
                        new ParseException("Wrong embed syntax")
                )
        );
    }

    Rule text() {
        return Sequence(
                push(new Text.Builder()),
                OneOrMore(
                        FirstOf(
                                Sequence(
                                        basicParser.escape(),
                                        action(peek(Text.Builder.class).append(match()))
                                ),
                                Sequence(
                                        TestNot(
                                                FirstOf(
                                                        basicParser.openCode(),
                                                        basicParser.openOutput(),
                                                        basicParser.openComment()
                                                )
                                        ),
                                        ANY,
                                        action(peek(Text.Builder.class).append(match()))
                                )
                        )
                ).suppressSubnodes(),
                push(pop(Text.Builder.class).build())
        );
    }

    Rule verbatim() {
        return Sequence(
                openCode(),
                keyword(VERBATIM),
                mandatory(
                        Sequence(
                                push(new Verbatim()),
                                action(beforeBeginTrim()),
                                closeCode(),
                                text(Sequence(
                                        basicParser.openCode(),
                                        basicParser.spacing(),
                                        keyword(ENDVERBATIM)
                                )),
                                action(peek(1, Verbatim.class).withContent(new Sequence().add(pop(Compilable.class)))),
                                openCode(),
                                keyword(JtwigKeyword.ENDVERBATIM),
                                closeCode(),
                                action(afterEndTrim())
                        ),
                        new ParseException("Wrong verbatim syntax")
                )
        );
    }

    Rule text(Rule until) {
        return Sequence(
                push(new Text.Builder()),
                OneOrMore(
                        FirstOf(
                                Sequence(
                                        basicParser.escape(),
                                        action(peek(Text.Builder.class).append(match()))
                                ),
                                Sequence(
                                        TestNot(
                                                until
                                        ),
                                        ANY,
                                        action(peek(Text.Builder.class).append(match()))
                                )
                        )
                ).suppressSubnodes(),
                push(pop(Text.Builder.class).build())
        );
    }

    Rule ifCondition() {
        return Sequence(
                openCode(),
                keyword(IF),
                mandatory(
                        Sequence(
                                push(new IfControl()),
                                expressionParser.expression(),
                                push(new IfControl.Case(expressionParser.pop())),
                                action(beforeBeginTrim()),
                                closeCode(),
                                action(afterBeginTrim()),
                                content(),
                                action(peek(1, IfControl.Case.class).withContent(pop(Sequence.class))),
                                ZeroOrMore(
                                        Sequence(
                                                action(peek(1, IfControl.class).add(peek(IfControl.Case.class))),
                                                openCode(),
                                                keyword(ELSEIF),
                                                expressionParser.expression(),
                                                push(new IfControl.Case(expressionParser.pop())),
                                                action(beforeEndTrim(1)),
                                                action(beforeBeginTrim()),
                                                closeCode(),
                                                action(afterEndTrim(1)),
                                                action(afterBeginTrim()),
                                                action(pop(1)),
                                                content(),
                                                action(peek(1, IfControl.Case.class).withContent(pop(Sequence.class)))
                                        )
                                ),
                                Optional(
                                        Sequence(
                                                action(peek(1, IfControl.class).add(peek(IfControl.Case.class))),
                                                openCode(),
                                                keyword(ELSE),
                                                push(new IfControl.Case(new Constant<>(true))),
                                                action(beforeEndTrim(1)),
                                                action(beforeBeginTrim()),
                                                closeCode(),
                                                action(afterEndTrim(1)),
                                                action(afterBeginTrim()),
                                                action(pop(1)),
                                                content(),
                                                action(peek(1, IfControl.Case.class).withContent(pop(Sequence.class)))
                                        )
                                ),
                                action(peek(1, IfControl.class).add(peek(IfControl.Case.class))),
                                openCode(),
                                action(beforeEndTrim()),
                                keyword(ENDIF),
                                closeCode(),
                                action(afterEndTrim()),
                                action(pop())
                        ),
                        new ParseException("Wrong if syntax")
                )
        );
    }

    Rule forEach() {
        return Sequence(
                openCode(),
                keyword(FOR),
                mandatory(
                        Sequence(
                                expressionParser.identifierAsString(),
                                FirstOf(
                                        Sequence(
                                                symbolWithSpacing(COMMA),
                                                expressionParser.identifierAsString(),
                                                keyword(IN),
                                                expressionParser.expression(),
                                                push(new For(expressionParser.popIdentifierAsString(2),
                                                        expressionParser.popIdentifierAsString(1),
                                                        expressionParser.pop()))
                                        ),
                                        Sequence(
                                                keyword(IN),
                                                expressionParser.expression(),
                                                push(new For(expressionParser.popIdentifierAsString(1),
                                                        expressionParser.pop()))
                                        )
                                ),
                                action(beforeBeginTrim()),
                                closeCode(),
                                action(afterBeginTrim()),
                                content(),
                                action(peek(1, Content.class).withContent(pop(Sequence.class))),
                                Optional(
                                        Sequence(
                                                openCode(),
                                                action(beforeEndTrim()),
                                                keyword(ELSE),
                                                closeCode(),
                                                action(afterBeginTrim()),
                                                content(),
                                                action(peek(1, For.class).withElse(pop(Sequence.class)))
                                        )
                                ),
                                
                                openCode(),
                                action(beforeEndTrim()),
                                keyword(ENDFOR),
                                closeCode(),
                                action(afterEndTrim())
                        ),
                        new ParseException("Wrong for each syntax")
                )
        );
    }

    Rule set() {
        return Sequence(
                openCode(),
                keyword(SET),
                mandatory(
                        Sequence(
                                expressionParser.identifierAsString(),
                                symbolWithSpacing(ATTR),
                                expressionParser.expression(),
                                push(new SetVariable(expressionParser.popIdentifierAsString(1), expressionParser.pop())),
                                action(beforeBeginTrim()),
                                closeCode(),
                                action(afterEndTrim())
                        ),
                        new ParseException("Wrong set syntax")
                )
        );
    }

    Rule output() {
        return Sequence(
                basicParser.openOutput(),
                tagPropertyParser.property(),
                basicParser.spacing(),
                mandatory(
                        Sequence(
                                expressionParser.expression(),
                                push(new Output(expressionParser.pop())),
                                action(beforeBeginTrim()),
                                tagPropertyParser.property(),
                                action(afterEndTrim()),
                                basicParser.closeOutput()
                        ),
                        new ParseException("Wrong output syntax")
                )
        );
    }

    Rule symbolWithSpacing(JtwigSymbol symbol) {
        return Sequence(
                basicParser.symbol(symbol),
                basicParser.spacing()
        );
    }

    Rule comment() {
        return Sequence(
                push(new Comment()),
                basicParser.openComment(),
                tagPropertyParser.property(),
                action(beforeBeginTrim()),
                ZeroOrMore(
                        TestNot(
                                Sequence(
                                        basicParser.symbol(JtwigSymbol.MINUS),
                                        basicParser.closeComment()
                                )
                        ),
                        TestNot(
                                basicParser.closeComment()
                        ),
                        ANY
                ),
                tagPropertyParser.property(),
                action(afterEndTrim()),
                basicParser.closeComment()
        );
    }

    Rule openCode() {
        return Sequence(
                basicParser.openCode(),
                tagPropertyParser.property(),
                basicParser.spacing()
        );
    }

    Rule closeCode() {
        return Sequence(
                tagPropertyParser.property(),
                basicParser.closeCode()
        );
    }

    Rule keyword(JtwigKeyword keyword) {
        return Sequence(
                basicParser.keyword(keyword),
                basicParser.spacing()
        );
    }


    WhiteSpaceControl afterEndTrim(int p) {
        return peek(p, Tag.class).tag().whiteSpaceControl().trimAfterEnd(tagPropertyParser.getCurrentProperty() == Trim);
    }

    WhiteSpaceControl beforeEndTrim(int p) {
        return peek(p, Tag.class).tag().whiteSpaceControl().trimBeforeEnd(tagPropertyParser.getCurrentProperty() == Trim);
    }

    WhiteSpaceControl afterBeginTrim(int p) {
        return peek(p, Tag.class).tag().whiteSpaceControl().trimAfterBegin(tagPropertyParser.getCurrentProperty() == Trim);
    }

    WhiteSpaceControl beforeBeginTrim(int p) {
        return peek(p, Tag.class).tag().whiteSpaceControl().trimBeforeBegin(tagPropertyParser.getCurrentProperty() == Trim);
    }

    WhiteSpaceControl afterEndTrim() {
        return afterEndTrim(0);
    }

    WhiteSpaceControl beforeEndTrim() {
        return beforeEndTrim(0);
    }

    WhiteSpaceControl afterBeginTrim() {
        return afterBeginTrim(0);
    }

    WhiteSpaceControl beforeBeginTrim() {
        return beforeBeginTrim(0);
    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy