package com.github.czyzby.lml.parser.impl.tag.macro; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectMap.Entry; import com.github.czyzby.kiwi.util.common.Nullables; import com.github.czyzby.kiwi.util.gdx.collection.GdxArrays; import com.github.czyzby.lml.parser.LmlParser; import com.github.czyzby.lml.parser.tag.LmlTag; /** Allows to iterate over multiple arrays at once. Keeps track of its iteration index. Arrays have to follow LML array * syntax. If one of the arrays is longer than the others, null values will be eventually returned when its out of * indexes. For example: <blockquote> * * <pre> * <:forEach element=elem0;elem1 range=rang[0,2]> * {forEach:index}: {element} {range} * </:forEach> * </pre> * * </blockquote>The first argument is a standard array with values separated with ';'. The second is a range. This will * evaluate to: * <ul> * <li>0: elem0 rang0 * <li>1: elem1 rang1 * <li>2: null rang2 * </ul> * Be careful when using nested loop tags: its arguments should not overlap. If you need to access indexes of both * loops, use different tag aliases. * * <p> * When using default DTD settings, "element" is the only recognized macro attribute. As this does not allow you to * create nested loops or iterate over multiple arrays at once, you might want to modify DTD files manually. * * @author MJ */ public class ForEachLmlMacroTag extends AbstractLoopLmlMacroTag { private final Array<String> argumentNames; private final Array<String[]> arrays; private final int size; private int currentIndex; public ForEachLmlMacroTag(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) { super(parser, parentTag, rawTagData); if (GdxArrays.isEmpty(getAttributes())) { parser.throwErrorIfStrict("For each macro needs array attributes to iterate over."); argumentNames = null; arrays = null; size = 0; } else { argumentNames = GdxArrays.newArray(); arrays = GdxArrays.newArray(); size = fillArrays(); } } /** @return size of the biggest array that we iterate over. */ private int fillArrays() { int biggestSize = 0; for (final Entry<String, String> attribute : getNamedAttributes()) { argumentNames.add(attribute.key); final String[] array = getParser().parseArray(attribute.value, getActor()); arrays.add(array); if (array.length > biggestSize) { biggestSize = array.length; } } return biggestSize; } @Override protected boolean supportsNamedAttributes() { return true; } @Override protected boolean hasNext() { return currentIndex < size; } @Override protected int getIndex() { return currentIndex; } @Override protected void next(final ObjectMap<String, String> arguments) { for (int argumentId = 0, length = argumentNames.size; argumentId < length; argumentId++) { arguments.put(argumentNames.get(argumentId), getArgumentValue(argumentId)); } currentIndex++; } /** @param argumentId ID of the argument array. * @return value stored in the selected array. */ protected String getArgumentValue(final int argumentId) { final String[] array = arrays.get(argumentId); if (array.length > currentIndex) { return array[currentIndex]; } return Nullables.DEFAULT_NULL_STRING; } @Override public String[] getExpectedAttributes() { return new String[] { ELEMENT_ATTRIBUTE }; } }