| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.taobao.weex.utils; |
| |
| import android.support.annotation.NonNull; |
| |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Parser for function like "rotate(30 ) transform(50 , 20)". |
| * This class will translate the raw string presentation of a group of function(s) to give type |
| * according to the {@link com.taobao.weex.utils.FunctionParser.Mapper} |
| */ |
| public class FunctionParser<K, V> { |
| |
| public static final char SPACE = ' '; |
| |
| private Mapper<K, V> mapper; |
| private Lexer lexer; |
| |
| /** |
| * Construct a function parser |
| * @param source the raw string representation of a group of function(s) |
| * @param mapper the mapping rule between string and corresponding type of object. |
| */ |
| public FunctionParser(@NonNull String source, @NonNull Mapper<K, V> mapper) { |
| this.lexer = new Lexer(source); |
| this.mapper = mapper; |
| } |
| |
| /** |
| * Start to parse the raw string. The result will be stored in a sorted map where the order |
| * is defined by the function order in the raw string. |
| * @return |
| */ |
| public LinkedHashMap<K, V> parse() { |
| lexer.moveOn(); |
| return definition(); |
| } |
| |
| private LinkedHashMap<K, V> definition() { |
| LinkedHashMap<K, V> result = new LinkedHashMap<>(); |
| do { |
| result.putAll(function()); |
| } while (lexer.getCurrentToken() == Token.FUNC_NAME); |
| return result; |
| } |
| |
| private Map<K, V> function() { |
| List<String> list = new LinkedList<>(); |
| String functionName = match(Token.FUNC_NAME); |
| match(Token.LEFT_PARENT); |
| list.add(match(Token.PARAM_VALUE)); |
| while (lexer.getCurrentToken() == Token.COMMA) { |
| match(Token.COMMA); |
| list.add(match(Token.PARAM_VALUE)); |
| } |
| match(Token.RIGHT_PARENT); |
| return mapper.map(functionName, list); |
| } |
| |
| private String match(Token token) { |
| try { |
| if (token == lexer.getCurrentToken()) { |
| String value = lexer.getCurrentTokenValue(); |
| lexer.moveOn(); |
| return value; |
| } |
| } catch (Exception e) { |
| WXLogUtils.e(token + "Token doesn't match" + lexer.source); |
| } |
| return ""; |
| } |
| |
| private enum Token { |
| FUNC_NAME, PARAM_VALUE, LEFT_PARENT, RIGHT_PARENT, COMMA |
| } |
| |
| public interface Mapper<K, V> { |
| |
| /** |
| * Map one function to a specified type of object |
| * @param functionName the name of the raw function. |
| * @param raw the list of parameter of the raw function |
| * @return the expected mapping relationship, where the key in the map is the same as the |
| * key in the return value of {{@link #parse()}}, |
| * and the value in the map is the type of object that expected by user. |
| */ |
| Map<K, V> map(String functionName, List<String> raw); |
| } |
| |
| private static class WXInterpretationException extends RuntimeException { |
| |
| private WXInterpretationException(String msg) { |
| super(msg); |
| } |
| } |
| |
| /** |
| * Lexer for the parser. For now, digit, alphabet, '(', ')', '.', ',', '+', '-' and '%' is |
| * valid character, and '(', ')', ',', parameter value and function name is valid token. |
| * Parameter value is defined as "(?i)[\+-]?[0-9]+(\.[0-9]+)?(%||deg||px)?" while function |
| * name is defined as "[a-zA-Z]+". |
| * |
| * The Lexer can also be expressed using the following EBNF format. |
| * <ul> |
| * <li>definition = {function};</li> |
| * <li>function = name, "(", value, { ",", value } , ")";</li> |
| * <li>name = character, {character};</li> |
| * <li>value = identifier, {identifier};</li> |
| * <li>identifier = character | "." | "%" | "+" | "-";</li> |
| * <li>character = digit | letter;</li> |
| * <li>digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;</li> |
| * <li>letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | |
| * "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | |
| * "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | |
| * "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" ;</li> |
| * </ul> |
| */ |
| private static class Lexer { |
| |
| private static final String LEFT_PARENT = "("; |
| private static final String RIGHT_PARENT = ")"; |
| private static final String COMMA = ","; |
| private static final char A_LOWER = 'a'; |
| private static final char Z_LOWER = 'z'; |
| private static final char A_UPPER = 'A'; |
| private static final char Z_UPPER = 'Z'; |
| private static final char ZERO = '0'; |
| private static final char NINE = '9'; |
| private static final char DOT = '.'; |
| private static final char MINUS = '-'; |
| private static final char PLUS = '+'; |
| private String source; |
| private Token current; |
| private String value; |
| private int pointer = 0; |
| |
| private Lexer(String source) { |
| this.source = source; |
| } |
| |
| private Token getCurrentToken() { |
| return current; |
| } |
| |
| private String getCurrentTokenValue() { |
| return value; |
| } |
| |
| private boolean moveOn() { |
| int start = pointer; |
| char curChar; |
| while (pointer < source.length()) { |
| curChar = source.charAt(pointer); |
| if (curChar == SPACE) { |
| if (start == pointer++) { |
| start++; |
| } else { |
| break; |
| } |
| } else if (isCharacterOrDigit(curChar) || curChar == DOT |
| || curChar == WXUtils.PERCENT || curChar == MINUS || curChar == PLUS) { |
| pointer++; |
| } else { |
| if (start == pointer) { |
| pointer++; |
| } |
| break; |
| } |
| } |
| if (start != pointer) { |
| String symbol = source.substring(start, pointer); |
| moveOn(symbol); |
| return true; |
| } else { |
| current = null; |
| value = null; |
| return false; |
| } |
| } |
| |
| private void moveOn(String token) { |
| if (LEFT_PARENT.equals(token)) { |
| current = Token.LEFT_PARENT; |
| value = LEFT_PARENT; |
| } else if (RIGHT_PARENT.equals(token)) { |
| current = Token.RIGHT_PARENT; |
| value = RIGHT_PARENT; |
| } else if (COMMA.equals(token)) { |
| current = Token.COMMA; |
| value = COMMA; |
| } else if (isFuncName(token)) { |
| current = Token.FUNC_NAME; |
| value = token; |
| } else { |
| current = Token.PARAM_VALUE; |
| value = token; |
| } |
| } |
| |
| private boolean isFuncName(CharSequence funcName) { |
| char letter; |
| for (int i = 0; i < funcName.length(); i++) { |
| letter = funcName.charAt(i); |
| if (!((A_LOWER <= letter && letter <= Z_LOWER) || |
| (A_UPPER <= letter && letter <= Z_UPPER) || |
| letter == MINUS)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private boolean isCharacterOrDigit(char letter) { |
| return (ZERO <= letter && letter <= NINE) || (A_LOWER <= letter && letter <= Z_LOWER) || |
| (A_UPPER <= letter && letter <= Z_UPPER); |
| } |
| } |
| |
| } |