package com.github.czyzby.lml.parser.impl.tag.macro; import com.badlogic.gdx.scenes.scene2d.Actor; import com.github.czyzby.kiwi.util.common.Nullables; import com.github.czyzby.kiwi.util.common.Strings; import com.github.czyzby.kiwi.util.tuple.immutable.Pair; import com.github.czyzby.lml.parser.LmlParser; import com.github.czyzby.lml.parser.LmlSyntax; import com.github.czyzby.lml.parser.action.ActorConsumer; import com.github.czyzby.lml.parser.impl.tag.AbstractMacroLmlTag; import com.github.czyzby.lml.parser.tag.LmlTag; /** Abstract base for conditional macros. Conditional macros evaluate a condition stored in their attributes and append * one part of the data stored between their tags. For example: <blockquote> * * <pre> * <:notNull {someArgument}> * Added on true. * <:notNull:else/> * Added on false. * </:notNull> * </pre> * * </blockquote>This particular macro - as you might guess - checks if the {someArgument} value is not null. If it is * present, "Added on true." will be processed by the parser, ignoring the rest of the macro's content. If argument * happens to be null or boolean false, "Added on false." will be processed. * * <p> * Else tag is optional: if else tag is not given, the whole macro content is appended only on "true" condition. Else * tag follows this syntax: tagOpening (<) + macroMarker (:) + macroTagName (for example, "notNull") + ":else" * (ignoring case) + closedTagMarker (/) + tagClosing (>). Typos or whitespaces in else tags might result in invalid * parsing. * * <p> * All conditional tags can be used with named attributes:<blockquote> * * <pre> * <:if test="5!=3"> * Added on true. * </:if> * </pre> * * </blockquote> * * @author MJ */ public abstract class AbstractConditionalLmlMacroTag extends AbstractMacroLmlTag { /** This value is appended to original macro tag name to create if-else functionality. {@literal <name:else/>} tag * should separate value appended on true "true" from "false". */ public static final String ELSE_SUFFIX = ":else"; /** When using named parameters, this value is used to construct the condition instead of other attributes. */ public static final String TEST_ATTRIBUTE = "test"; public AbstractConditionalLmlMacroTag(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) { super(parser, parentTag, rawTagData); } @Override public void handleDataBetweenTags(final CharSequence rawMacroContent) { final Pair<CharSequence, CharSequence> content = splitInTwo(rawMacroContent, getSeparator(rawMacroContent)); if (checkCondition()) { appendTextToParse(content.getFirst()); } else { appendTextToParse(content.getSecond()); } } /** @param content will be split by the separator. * @return separator, used to split raw texts into two parts - value added on "true" and on "false". */ protected String getSeparator(CharSequence content) { // Since splitting method does not use a regex, we accept 2 separators as a workaround. final LmlSyntax syntax = getParser().getSyntax(); String separator = buildSeparator(syntax, true); return Strings.containsIgnoreCase(content, separator) ? separator : buildSeparator(syntax, false); } private String buildSeparator(LmlSyntax syntax, boolean includeWhitespace) { final StringBuilder builder = new StringBuilder(); builder.append(syntax.getTagOpening()); builder.append(syntax.getMacroMarker()); builder.append(getTagName()); builder.append(ELSE_SUFFIX); if (includeWhitespace) { builder.append(' '); } builder.append(syntax.getClosedTagMarker()); builder.append(syntax.getTagClosing()); return builder.toString(); } /** @return evaluated condition result that will decide if the "true" or "false" content is appended. */ protected abstract boolean checkCondition(); /** @param attribute will be checked. * @return true if the attribute is a method invocation. */ protected boolean isAction(final String attribute) { return Strings.startsWith(attribute, getParser().getSyntax().getMethodInvocationMarker()); } /** @param attribute is an action. * @return result of the action invocation. */ protected Object invokeAction(final String attribute) { final ActorConsumer<?, Actor> action = getParser().parseAction(attribute, getActor()); if (action != null) { return action.consume(getActor()); } getParser().throwError( "Unable to evaluate conditional macro. Unknown action ID: " + attribute + " for actor: " + getActor()); return null; } /** @param value can be null. * @return true if value is mapped to null or boolean false. */ protected boolean isNullOrFalse(final Object value) { return isNullOrFalse(Nullables.toNullableString(value)); } /** @param value LML value. * @return true if value is mapped to null or boolean false. */ protected boolean isNullOrFalse(final String value) { return value == null || Strings.isWhitespace(value) || Nullables.DEFAULT_NULL_STRING.equalsIgnoreCase(value) || Boolean.FALSE.toString().equalsIgnoreCase(value); } @Override public String[] getExpectedAttributes() { return new String[] { TEST_ATTRIBUTE }; } }