/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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.google.dart.engine.internal.html.angular; import com.google.common.collect.Lists; import com.google.dart.engine.ast.Block; import com.google.dart.engine.ast.DeclaredIdentifier; import com.google.dart.engine.ast.Expression; import com.google.dart.engine.ast.ForEachStatement; import com.google.dart.engine.ast.ListLiteral; import com.google.dart.engine.ast.MapLiteral; import com.google.dart.engine.ast.MapLiteralEntry; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.SimpleStringLiteral; import com.google.dart.engine.ast.Statement; import com.google.dart.engine.element.Element; import com.google.dart.engine.error.AngularCode; import com.google.dart.engine.html.ast.XmlAttributeNode; import com.google.dart.engine.html.ast.XmlExpression; import com.google.dart.engine.html.ast.XmlTagNode; import com.google.dart.engine.internal.builder.ElementBuilder; import com.google.dart.engine.internal.builder.ElementHolder; import com.google.dart.engine.internal.element.LocalVariableElementImpl; import com.google.dart.engine.type.InterfaceType; import com.google.dart.engine.type.Type; import com.google.dart.engine.utilities.general.StringUtilities; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * {@link NgRepeatProcessor} describes built-in <code>NgRepeatDirective</code> directive. */ class NgRepeatProcessor extends NgDirectiveProcessor { private static final String NG_REPEAT = "ng-repeat"; private static final Pattern SYNTAX_PATTERN = Pattern.compile("^\\s*(.+)\\s+in\\s+(.+?)\\s*(\\s+track\\s+by\\s+(.+)\\s*)?(\\s+lazily\\s*)?$"); private static final Pattern LHS_PATTERN = Pattern.compile("^(?:([\\$\\w]+)|\\(([\\$\\w]+)\\s*,\\s*([\\$\\w]+)\\))$"); public static final NgRepeatProcessor INSTANCE = new NgRepeatProcessor(); private NgRepeatProcessor() { } @Override public void apply(AngularHtmlUnitResolver resolver, XmlTagNode node) { XmlAttributeNode attribute = node.getAttribute(NG_REPEAT); int offset = attribute.getValueToken().getOffset() + 1; String spec = attribute.getText(); // check syntax Matcher syntaxMatcher = SYNTAX_PATTERN.matcher(spec); if (!syntaxMatcher.matches()) { resolver.reportErrorForOffset(AngularCode.INVALID_REPEAT_SYNTAX, offset, spec.length() - 2); return; } String lhsSpec = syntaxMatcher.group(1); String iterableSpec = syntaxMatcher.group(2); String idSpec = syntaxMatcher.group(4); int lhsOffset = offset + syntaxMatcher.start(1); int iterableOffset = offset + syntaxMatcher.start(2); int idOffset = offset + syntaxMatcher.start(4); List<XmlExpression> expressions = Lists.newArrayList(); // check LHS syntax Matcher lhsMatcher = LHS_PATTERN.matcher(lhsSpec); if (!lhsMatcher.matches()) { resolver.reportErrorForOffset( AngularCode.INVALID_REPEAT_ITEM_SYNTAX, lhsOffset, lhsSpec.length()); return; } // parse item name Expression varExpression = resolver.parseDartExpression(lhsSpec, 0, lhsSpec.length(), lhsOffset); SimpleIdentifier varName = (SimpleIdentifier) varExpression; // parse iterable AngularExpression iterableExpr = resolver.parseAngularExpression( iterableSpec, 0, iterableSpec.length(), iterableOffset); // resolve as: for (name in iterable) {} DeclaredIdentifier loopVariable = new DeclaredIdentifier(null, null, null, null, varName); Block loopBody = new Block(null, new ArrayList<Statement>(), null); ForEachStatement loopStatement = new ForEachStatement( null, null, null, loopVariable, null, iterableExpr.getExpression(), null, loopBody); new ElementBuilder(new ElementHolder()).visitDeclaredIdentifier(loopVariable); resolver.resolveNode(loopStatement); // define item variable Type itemType = varName.getBestType(); { LocalVariableElementImpl variable = (LocalVariableElementImpl) varName.getStaticElement(); variable.setType(itemType); resolver.defineVariable(variable); } // resolve formatters resolveFormatters(resolver, iterableExpr, itemType); // remember expressions expressions.add(newRawXmlExpression(varExpression)); expressions.add(newAngularRawXmlExpression(iterableExpr)); if (idSpec != null) { AngularExpression idExpression = resolver.parseAngularExpression( idSpec, 0, idSpec.length(), idOffset); expressions.add(newAngularRawXmlExpression(idExpression)); } setExpressions(attribute, expressions); // define additional variables defineLocalVariable_int(resolver, "$index"); defineLocalVariable_bool(resolver, "$first"); defineLocalVariable_bool(resolver, "$middle"); defineLocalVariable_bool(resolver, "$last"); defineLocalVariable_bool(resolver, "$even"); defineLocalVariable_bool(resolver, "$odd"); } @Override public boolean canApply(XmlTagNode node) { return node.getAttribute(NG_REPEAT) != null; } private void defineLocalVariable_bool(AngularHtmlUnitResolver resolver, String name) { InterfaceType type = resolver.getTypeProvider().getBoolType(); LocalVariableElementImpl variable = resolver.createLocalVariableWithName(type, name); resolver.defineVariable(variable); } private void defineLocalVariable_int(AngularHtmlUnitResolver resolver, String name) { InterfaceType type = resolver.getTypeProvider().getIntType(); LocalVariableElementImpl variable = resolver.createLocalVariableWithName(type, name); resolver.defineVariable(variable); } /** * Resolves an argument for "filter" formatter. */ private void resolveFormatterArgument_filter(AngularHtmlUnitResolver resolver, Type itemType, AngularFormatterArgument argument, int argIndex) { Expression arg = argument.getExpression(); // only first argument is special for "filter" if (argIndex != 0) { resolver.resolveNode(arg); return; } // Map if (arg instanceof MapLiteral) { List<Expression> expressions = Lists.newArrayList(); List<MapLiteralEntry> entries = ((MapLiteral) arg).getEntries(); for (MapLiteralEntry mapEntry : entries) { Expression keyExpr = mapEntry.getKey(); if (keyExpr instanceof SimpleIdentifier) { SimpleIdentifier propertyNode = (SimpleIdentifier) keyExpr; resolvePropertyNode(resolver, expressions, itemType, propertyNode); } } // set resolved sub-expressions argument.setSubExpressions(expressions.toArray(new Expression[expressions.size()])); } } /** * Resolves an argument for "orderBy" formatter. */ private void resolveFormatterArgument_orderBy(AngularHtmlUnitResolver resolver, List<Expression> expressions, Type itemType, Expression arg, int argIndex) { // List of properties if (arg instanceof ListLiteral) { List<Expression> subArgs = ((ListLiteral) arg).getElements(); for (Expression subArg : subArgs) { resolveFormatterArgument_orderBy(resolver, expressions, itemType, subArg, 0); } return; } // property name in quotes if (arg instanceof SimpleStringLiteral) { SimpleStringLiteral argLiteral = (SimpleStringLiteral) arg; String exprSource = argLiteral.getStringValue(); int argOffset = argLiteral.getValueOffset(); // remove leading +/- if (StringUtilities.startsWithChar(exprSource, '+')) { exprSource = exprSource.substring(1); argOffset++; } else if (StringUtilities.startsWithChar(exprSource, '-')) { exprSource = exprSource.substring(1); argOffset++; } // empty string - use item itself, nothing to resolve if (exprSource.isEmpty()) { return; } // resolve property name arg = resolver.parseDartExpression(exprSource, 0, exprSource.length(), argOffset); if (arg instanceof SimpleIdentifier) { SimpleIdentifier propertyNode = (SimpleIdentifier) arg; resolvePropertyNode(resolver, expressions, itemType, propertyNode); } } } /** * Resolves an argument for "orderBy" formatter. */ private void resolveFormatterArgument_orderByWithFilter(AngularHtmlUnitResolver resolver, Type itemType, AngularFormatterArgument argument, int argIndex) { Expression arg = argument.getExpression(); // only first argument is special for "orderBy" if (argIndex != 0) { resolver.resolveNode(arg); return; } // List<Expression> expressions = Lists.newArrayList(); resolveFormatterArgument_orderBy(resolver, expressions, itemType, arg, 0); // set resolved sub-expressions argument.setSubExpressions(expressions.toArray(new Expression[expressions.size()])); } private void resolveFormatterArguments(AngularHtmlUnitResolver resolver, Type itemType, String formatterName, List<AngularFormatterArgument> arguments) { int index = 0; for (AngularFormatterArgument argument : arguments) { if ("filter".equals(formatterName)) { resolveFormatterArgument_filter(resolver, itemType, argument, index); } if ("orderBy".equals(formatterName)) { resolveFormatterArgument_orderByWithFilter(resolver, itemType, argument, index); } index++; } } /** * Resolves sequence of formatters. */ private void resolveFormatters(AngularHtmlUnitResolver resolver, AngularExpression angularExpression, Type itemType) { for (AngularFormatterNode formatter : angularExpression.getFormatters()) { SimpleIdentifier formatterNameNode = formatter.getName(); String formatterName = formatterNameNode.getName(); // resolve formatter name formatterNameNode.setStaticElement(resolver.findAngularElement(formatterName)); // resolve formatter arguments resolveFormatterArguments(resolver, itemType, formatterName, formatter.getArguments()); } } private void resolvePropertyNode(AngularHtmlUnitResolver resolver, List<Expression> expressions, Type itemType, SimpleIdentifier propertyNode) { // if known type - resolve, otherwise keep it 'dynamic' if (itemType instanceof InterfaceType) { String propertyName = propertyNode.getName(); Element propertyElement = ((InterfaceType) itemType).getGetter(propertyName); if (propertyElement != null) { propertyNode.setStaticElement(propertyElement); } } else { Type dynamicType = resolver.getTypeProvider().getDynamicType(); propertyNode.setStaticElement(dynamicType.getElement()); propertyNode.setStaticType(dynamicType); } // add argument expressions.add(propertyNode); } }