Package httl.spi.parsers

Source Code of httl.spi.parsers.TemplateParser$BlockDirectiveEntry

/*
* Copyright 2011-2013 HTTL Team.
* 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 httl.spi.parsers;

import httl.Engine;
import httl.Node;
import httl.Template;
import httl.ast.BlockDirective;
import httl.ast.BreakDirective;
import httl.ast.Comment;
import httl.ast.Directive;
import httl.ast.ElseDirective;
import httl.ast.EndDirective;
import httl.ast.Expression;
import httl.ast.ForDirective;
import httl.ast.IfDirective;
import httl.ast.MacroDirective;
import httl.ast.RootDirective;
import httl.ast.SetDirective;
import httl.ast.Statement;
import httl.ast.Text;
import httl.ast.ValueDirective;
import httl.spi.Parser;
import httl.util.ClassUtils;
import httl.util.DfaScanner;
import httl.util.LinkedStack;
import httl.util.ParameterizedTypeImpl;
import httl.util.StringUtils;
import httl.util.Token;

import java.lang.reflect.Type;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* TemplateParser. (SPI, Singleton, ThreadSafe)
*
* @see httl.spi.engines.DefaultEngine#setTemplateParser(Parser)
*
* @author Liang Fei (liangfei0201 AT gmail DOT com)
*/
public class TemplateParser implements Parser {

  // 单字母命名, 保证状态机图简洁

  // END,结束片段,包含当前字符
  private static final int E = DfaScanner.BREAK;

  // BREAK,结束片段,并退回一个字符,即不包含当前字符
  private static final int B = DfaScanner.BREAK - 1;

  // BACKSPACE,结束片段,退回当前字符,以及之前的所有空白
  private static final int S = DfaScanner.BACKSPACE - 1;

  // PUSH,压栈1,即指令小括号栈,并回到状态4,即指令参数
  private static final int P = DfaScanner.PUSH - 4;

  // POP,弹栈1,即指令小括号栈,并回到状态4,即指令参数
  private static final int O = DfaScanner.POP - 4;

  // PUSH,压栈2,即插值大括号栈,并回到状态7,即插值参数
  private static final int P2 = DfaScanner.PUSH - 7;

  // POP,弹栈2,即插值大括号栈,并回到状态7,即插值参数
  private static final int O2 = DfaScanner.POP - 7;

  // 插值语法状态机图
  // 行表示状态
  // 行列交点表示, 在该状态时, 遇到某类型的字符时, 切换到的下一状态(数组行号)
  // E/B/T表示接收前面经过的字符为一个片断, R表示错误状态(这些状态均为负数)
  static final int states[][] = {
          // 0.\s, 1.a-z, 2.#, 3.$, 4.!, 5.*, 6.(, 7.), 8.[, 9.], 10.{, 11.}, 12.", 13.', 14.`, 15.\, 16.\r\n, 17.其它
    /* 0.起始 */ { 1, 1, 2, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, }, // 初始状态或上一片断刚接收完成状态
    /* 1.文本 */ { 1, 1, B, B, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, }, // 非指令文本内容
   
    /* 2.指令 */ { 1, 3, 9, B, 6, 10, 1, 1, 12, 1, P2, 1, 1, 1, 1, 1, 1, 1, }, // 指令提示符
    /* 3.指名 */ { 26, 3, B, B, B, B, P, B, B, B, B, B, B, B, B, B, B, B, }, // 指令名
    /* 4.指参 */ { 4, 4, 4, 4, 4, 4, P, O, 4, 4, 4, 4, 14, 16, 18, 4, 4, 4, }, // 指令参数
   
    /* 5.插值 */ { 1, 1, B, B, 6, 1, 1, 1, 1, 1, P2, 1, 1, 1, 1, 1, 1, 1, }, // 插值提示符
    /* 6.非滤 */ { 1, 1, B, B, 1, 1, 1, 1, 1, 1, P2, 1, 1, 1, 1, 1, 1, 1, }, // 非过滤插值
    /* 7.插参 */ { 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, P2, O2, 20, 22, 24, 7, 7, 7, }, // 插值参数
   
    /* 8.转义 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // 井号美元号转义
    /* 9.行注 */ { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, B, 9, }, // 双井号行注释
    /* 10.块注 */ { 10, 10, 10, 10, 10, 11, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, }, // 井星号块注释
    /* 11.结块 */ { 10, 10, E, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, }, // 井星号块注释结束
    /* 12.字面 */ { 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 12, 12, 12, 12, 12, 12, 12, 12, }, // 井方号块字面不解析块
    /* 13.结字 */ { 12, 12, E, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, }, // 井方号块字面不解析块结束
   
    /* 14.字串 */ { 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 4, 14, 14, 15, 14, 14, }, // 指令参数双引号字符串
    /* 15.转字 */ { 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, }, // 指令参数双引号字符串转义
    /* 16.字串 */ { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 4, 16, 17, 16, 16, }, // 指令参数单引号字符串
    /* 17.转字 */ { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, }, // 指令参数单引号字符串转义
    /* 18.字串 */ { 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 4, 19, 18, 18, }, // 指令参数反单引号字符串
    /* 19.转字 */ { 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, }, // 指令参数反单引号字符串转义
   
    /* 20.字串 */ { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 7, 20, 20, 21, 20, 20, }, // 插值参数双引号字符串
    /* 21.转字 */ { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, }, // 插值参数双引号字符串转义
    /* 22.字串 */ { 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 7, 22, 23, 22, 22, }, // 插值参数单引号字符串
    /* 23.转字 */ { 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, }, // 插值参数单引号字符串转义
    /* 24.字串 */ { 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 7, 25, 24, 24, }, // 插值参数反单引号字符串
    /* 25.转字 */ { 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, }, // 插值参数反单引号字符串转义
   
    /* 26.指间空白 */ { 26, S, S, S, S, S, P, S, S, S, S, S, S, S, S, S, S, S, }, // 指令名和括号间的空白
  };

  static int getCharType(char ch) {
    switch (ch) {
      case ' ': case '\t': case '\f': case '\b':
        return 0;
      case 'a' : case 'b' : case 'c' : case 'd' : case 'e' : case 'f' : case 'g' :
      case 'h' : case 'i' : case 'j' : case 'k' : case 'l' : case 'm' : case 'n' :
      case 'o' : case 'p' : case 'q' : case 'r' : case 's' : case 't' :
      case 'u' : case 'v' : case 'w' : case 'x' : case 'y' : case 'z' :
        return 1;
      case '#' :
        return 2;
      case '$' :
        return 3;
      case '!' :
        return 4;
      case '*' :
        return 5;
      case '(' :
        return 6;
      case ')' :
        return 7;
      case '[' :
        return 8;
      case ']' :
        return 9;
      case '{' :
        return 10;
      case '}' :
        return 11;
      case '\"' :
        return 12;
      case '\'' :
        return 13;
      case '`' :
        return 14;
      case '\\' :
        return 15;
      case '\r': case '\n':
        return 16;
      default:
        return 17;
    }
  }

  private static DfaScanner scanner = new DfaScanner() {
    @Override
    public int next(int state, char ch) {
      return states[state][getCharType(ch)];
    }
  };

  private static final Pattern ESCAPE_PATTERN = Pattern.compile("\\\\+[#$]");

  private String[] setDirective = new String[] { "var" };

  private String[] ifDirective = new String[] { "if" };

  private String[] elseDirective = new String[] { "else" };

  private String[] forDirective = new String[] { "for" };

  private String[] breakDirective = new String[] { "break" };

  private String[] macroDirective = new String[] { "macro" };

  private String[] endDirective = new String[] { "end" };

  private Engine engine;
 
  private Parser expressionParser;
 
  private String[] importMacros;
  
  private final Map<String, Template> importMacroTemplates = new ConcurrentHashMap<String, Template>();

  private String[] importPackages;

  private String[] importVariables;

  private Map<String, Class<?>> importTypes;

  private final Map<Class<?>, Object> functions = new ConcurrentHashMap<Class<?>, Object>();

  private Class<?> defaultVariableType;

  private boolean removeDirectiveBlankLine = true;

  private boolean isDirective(String message) {
    if (message.length() > 1 && message.charAt(0) == '#'
        && message.charAt(1) >= 'a' && message.charAt(1) <= 'z') {
      int i = message.indexOf('(');
      String name = (i > 0 ? message.substring(1, i) : message.substring(1));
       return isDirectiveName(name);
    }
    return false;
  }

  private boolean isDirectiveName(String name) {
    return StringUtils.inArray(name, setDirective)
        || StringUtils.inArray(name, ifDirective) || StringUtils.inArray(name, elseDirective)
        || StringUtils.inArray(name, forDirective) || StringUtils.inArray(name, breakDirective)
        || StringUtils.inArray(name, macroDirective) || StringUtils.inArray(name, endDirective);
  }
 
  private void defineVariableTypes(String value, int offset, List<Statement> directives) throws ParseException {
    int o = offset;
    for (String v : splitDefine(value)) {
      v = v.trim().replaceAll("\\s", " ");
      String var;
      String type;
      int i = v.lastIndexOf(' ');
      if (i <= 0) {
        type = defaultVariableType == null ? Object.class.getSimpleName() : defaultVariableType.getCanonicalName();
        var = v;
      } else {
        type = v.substring(0, i).trim();
        var = v.substring(i + 1).trim();
      }
      directives.add(new SetDirective(parseGenericType(type, o), var, null, false, false, offset));
      o += v.length() + 1;
    }
  }
 
  private boolean isNoLiteralText(Statement node) {
    return node instanceof Text && ! ((Text) node).isLiteral();
  }

  private List<Statement> clean(List<Statement> nodes) throws ParseException {
    List<Statement> result = null;
    for (int i = 0; i < nodes.size(); i ++) {
      if (i + 1 < nodes.size() && isNoLiteralText(nodes.get(i)) && isNoLiteralText(nodes.get(i + 1))) {
        if (result == null) {
          result = new ArrayList<Statement>();
          for (int j = 0; j < i; j ++) {
            result.add(nodes.get(j));
          }
        }
        int offset = nodes.get(i).getOffset();
        StringBuilder buf = new StringBuilder();
        buf.append(((Text) nodes.get(i)).getContent());
        while (i + 1 < nodes.size() && isNoLiteralText(nodes.get(i + 1))) {
          buf.append(((Text) nodes.get(i + 1)).getContent());
          i ++;
        }
        result.add(new Text(buf.toString(), false, offset));
      } else if (result != null) {
        result.add(nodes.get(i));
      }
    }
    if (result != null) {
      return result;
    }
    return nodes;
  }

  private List<Statement> scan(String source, int sourceOffset) throws ParseException {
    List<Statement> directives = new ArrayList<Statement>();
    List<Token> tokens = scanner.scan(source, sourceOffset);
    AtomicInteger seq = new AtomicInteger();
    for (int t = 0; t < tokens.size(); t ++) {
      Token token = tokens.get(t);
      String message = token.getMessage();
      int offset = token.getOffset();
      if (isDirective(message)) {
        int s = message.indexOf('(');
        String name;
        String value;
        int exprOffset;
        if (s > 0) {
          exprOffset = offset + s + 1;
          name = message.substring(1, s);
          if (! message.endsWith(")")) {
            throw new ParseException("The #" + name + " directive mismatch right parentheses.", exprOffset);
          }
          value = message.substring(s + 1, message.length() - 1);
        } else {
          exprOffset = token.getOffset() + message.length();
          name = message.substring(1);
          value = "";
          if (! StringUtils.inArray(name, elseDirective)
              && ! StringUtils.inArray(name, endDirective)
              && ! StringUtils.inArray(name, breakDirective)) {
            throw new ParseException("Not found parameter expression in the #" + name + " directive.", offset);
          }
        }
        if (StringUtils.inArray(name, setDirective)) {
          if (value.contains("=")) {
            int o = 0;
            for (String v : splitAssign(value)) {
              int i = v.indexOf('=');
              String var = v.substring(0, i).trim();
              String expr = v.substring(i + 1);
              int blank = 0;
              while (blank < expr.length()) {
                if (! Character.isWhitespace(expr.charAt(blank))) {
                  break;
                }
                blank ++;
              }
              if (blank > 0) {
                expr = expr.substring(blank);
              }
              Expression expression = (Expression) expressionParser.parse(expr, exprOffset + i + 1 + blank + o);
              boolean export = false;
              boolean hide = false;
              if (var.endsWith(":")) {
                export = true;
                var = var.substring(0, var.length() - 1).trim();
              } else if (var.endsWith(".")) {
                hide = true;
                var = var.substring(0, var.length() - 1).trim();
              }
              int j = var.lastIndexOf(' ');
              String type = null;
              if (j > 0) {
                type = var.substring(0, j).trim();
                var = var.substring(j + 1).trim();
              }
              directives.add(new SetDirective(parseGenericType(type, exprOffset), var, expression, export, hide, offset));
              o += v.length() + 1;
            }
          } else {
            defineVariableTypes(value, offset, directives);
          }
        } else if (StringUtils.inArray(name, forDirective)) {
          if (StringUtils.isNumber(value.trim())) {
            value = "__for" + seq.incrementAndGet() + " : 1 .. " + value.trim();
          }
          int i = value.indexOf(" in ");
          int n = 4;
          if (i < 0) {
            i = value.indexOf(':');
            n = 1;
          }
          if (i < 0) {
            throw new ParseException("Miss colon \":\" in invalid directive #for(" + value + ")", offset);
          }
          String var = value.substring(0, i).trim();
          String expr = value.substring(i + n);
          int blank = 0;
          while (blank < expr.length()) {
            if (! Character.isWhitespace(expr.charAt(blank))) {
              break;
            }
            blank ++;
          }
          if (blank > 0) {
            expr = expr.substring(blank);
          }
          Expression expression = (Expression) expressionParser.parse(expr, exprOffset + i + n + blank);
          int j = var.lastIndexOf(' ');
          String type = null;
          if (j > 0) {
            type = var.substring(0, j).trim();
            var = var.substring(j + 1).trim();
          }
          directives.add(new ForDirective(parseGenericType(type, exprOffset), var, expression, offset));
        } else if (StringUtils.inArray(name, ifDirective)) {
          directives.add(new IfDirective((Expression) expressionParser.parse(value, exprOffset), offset));
        } else if (StringUtils.inArray(name, elseDirective)) {
          directives.add(new ElseDirective(StringUtils.isEmpty(value)
              ? null : (Expression) expressionParser.parse(value, exprOffset), offset));
        } else if (StringUtils.inArray(name, breakDirective)) {
          directives.add(new BreakDirective(StringUtils.isBlank(value) ? null : (Expression) expressionParser.parse(value, exprOffset), offset));
        } else if (StringUtils.inArray(name, macroDirective)) {
          String macroName = value;
          String macroParams = null;
          String filter = null;
          int idx = macroName.indexOf("=>");
          if (idx > 0) {
            filter = macroName.substring(idx + 2).trim();
            macroName = macroName.substring(0, idx);
          }
          int i = value.indexOf('(');
          if (i > 0) {
            if (! message.endsWith(")")) {
              throw new ParseException("The #" + name + " directive mismatch right parentheses.", exprOffset);
            }
            macroName = value.substring(0, i);
            macroParams = value.substring(i + 1, value.length() - 1);
          }
          String set = null;
          boolean parent = false;
          boolean hide = false;
          i = macroName.indexOf('=');
          if (i > 0) {
            set = macroName.substring(0, i);
            if (set.endsWith(":")) {
              parent = true;
              set = set.substring(0, set.length() - 1);
            } else if (set.endsWith(".")) {
              hide = true;
              set = set.substring(0, set.length() - 1);
            }
            set = set.trim();
            macroName = macroName.substring(i + 1);
          }
          boolean out = false;
          if (macroName.startsWith("$")) {
            out = true;
            macroName = macroName.substring(macroName.startsWith("$!") ? 2 : 1);
          }
          String expr;
          if (StringUtils.isNotEmpty(filter)) {
            if (filter.contains("(")) {
              expr = filter;
            } else {
              expr = filter + "(" + macroName + ")";
            }
          } else {
            expr = macroName;
          }
          if (StringUtils.isNotEmpty(set)) {
            directives.add(new SetDirective(Template.class, set, (Expression) expressionParser.parse(expr, exprOffset), parent, hide, offset));
          }
          if (out) {
            directives.add(new ValueDirective((Expression) expressionParser.parse(expr, exprOffset), true, offset));
          }
          macroName = macroName.trim();
          directives.add(new MacroDirective(macroName, offset));
          if (StringUtils.isNotEmpty(macroParams)) {
            defineVariableTypes(macroParams, exprOffset, directives);
          }
        } else if (StringUtils.inArray(name, endDirective)) {
          directives.add(new EndDirective(offset));
        }
      } else if (message.endsWith("}") && (message.startsWith("${") || message.startsWith("$!{")
          || message.startsWith("#{") || message.startsWith("#!{"))) {
        int i = message.indexOf('{');
        directives.add(new ValueDirective((Expression) expressionParser.parse(message.substring(i + 1, message.length() - 1),
            offset + i + 1), message.startsWith("$!") || message.startsWith("#!"), offset));
      } else if (message.startsWith("##")) {
        directives.add(new Comment(message.substring(2), false, offset));
      } else if ((message.startsWith("#*") && message.endsWith("*#"))) {
        directives.add(new Comment(message.substring(2, message.length() - 2), true, offset));
      } else {
        boolean literal;
        if (message.startsWith("#[") && message.endsWith("]#")) {
          message = message.substring(2, message.length() - 2);
          literal = true;
        } else {
          message = filterEscape(message);
          literal = false;
        }
        directives.add(new Text(message, literal, offset));
      }
    }
    return directives;
  }

  private BlockDirective reduce(List<Statement> directives) throws ParseException {
    LinkedStack<BlockDirectiveEntry> directiveStack = new LinkedStack<BlockDirectiveEntry>();
    RootDirective rootDirective = new RootDirective();
    directiveStack.push(new BlockDirectiveEntry(rootDirective));
    for (int i = 0, n = directives.size(); i < n; i ++) {
      Statement directive = (Statement)directives.get(i);
      if (directive == null)
        continue;
      Class<?> directiveClass = directive.getClass();
      // 弹栈
      if (directiveClass == EndDirective.class
          || directiveClass == ElseDirective.class) {
        if (directiveStack.isEmpty())
          throw new ParseException("Miss #end directive.", directive.getOffset());
        BlockDirective blockDirective = ((BlockDirectiveEntry) directiveStack.pop()).popDirective();
        if (blockDirective == rootDirective)
          throw new ParseException("Miss #end directive.", directive.getOffset());
        EndDirective endDirective;
        if (directiveClass == ElseDirective.class) {
          endDirective = new EndDirective(directive.getOffset());
        } else {
          endDirective = (EndDirective) directive;
        }
        blockDirective.setEnd(endDirective);
      }
      // 设置树
      if (directiveClass != EndDirective.class) { // 排除EndDirective
        if (directiveStack.isEmpty())
          throw new ParseException("Miss #end directive.", directive.getOffset());
        ((BlockDirectiveEntry) directiveStack.peek()).appendInnerDirective(directive);
      }
      // 压栈
      if (directive instanceof BlockDirective)
        directiveStack.push(new BlockDirectiveEntry((BlockDirective) directive));
    }
    BlockDirective root = (BlockDirective) ((BlockDirectiveEntry) directiveStack.pop()).popDirective();
    if (! directiveStack.isEmpty()) { // 后验条件
      throw new ParseException("Miss #end directive." + root.getClass().getSimpleName(), root.getOffset());
    }
    return root;
  }

  // 指令归约辅助封装类
  private static final class BlockDirectiveEntry {

    private BlockDirective blockDirective;

    private List<Statement> elements = new ArrayList<Statement>();

    BlockDirectiveEntry(BlockDirective blockDirective) {
      this.blockDirective = blockDirective;
    }

    void appendInnerDirective(Statement innerDirective) {
      this.elements.add(innerDirective);
    }

    BlockDirective popDirective() throws ParseException {
      ((BlockDirective)blockDirective).setChildren(elements);
      return blockDirective;
    }

  }
 
  /**
   * httl.properties: remove.directive.blank.line=true
   */
  public void setRemoveDirectiveBlankLine(boolean removeDirectiveBlankLine) {
    this.removeDirectiveBlankLine = removeDirectiveBlankLine;
  }

  /**
   * httl.properties: set.directive=set
   */
  public void setSetDirective(String[] setDirective) {
    this.setDirective = setDirective;
  }

  /**
   * httl.properties: if.directive=if
   */
  public void setIfDirective(String[] ifDirective) {
    this.ifDirective = ifDirective;
  }

  /**
   * httl.properties: else.directive=else
   */
  public void setElseDirective(String[] elseDirective) {
    this.elseDirective = elseDirective;
  }

  /**
   * httl.properties: for.directive=for
   */
  public void setForDirective(String[] forDirective) {
    this.forDirective = forDirective;
  }

  /**
   * httl.properties: break.directive=break
   */
  public void setBreakDirective(String[] breakDirective) {
    this.breakDirective = breakDirective;
  }

  /**
   * httl.properties: macro.directive=macro
   */
  public void setMacroDirective(String[] macroDirective) {
    this.macroDirective = macroDirective;
  }

  /**
   * httl.properties: end.directive=end
   */
  public void setEndDirective(String[] endDirective) {
    this.endDirective = endDirective;
  }

  /**
   * httl.properties: default.variable.type=java.lang.String
   */
  public void setDefaultVariableType(String defaultVariableType) {
    this.defaultVariableType = ClassUtils.forName(defaultVariableType);
  }

  /**
   * httl.properties: import.macros=common.httl
   */
  public void setImportMacros(String[] importMacros) {
    this.importMacros = importMacros;
  }

  /**
   * httl.properties: engine=httl.spi.engines.DefaultEngine
   */
  public void setEngine(Engine engine) {
    this.engine = engine;
  }

  /**
   * httl.properties: expression.parser=httl.spi.parsers.ExpressionParser
   */
  public void setExpressionParser(Parser expressionParser) {
    this.expressionParser = expressionParser;
  }

  /**
   * httl.properties: import.packages=java.util
   */
  public void setImportPackages(String[] importPackages) {
    this.importPackages = importPackages;
  }

  /**
   * httl.properties: import.setVariables=javax.servlet.http.HttpServletRequest request
   */
  public void setImportVariables(String[] importVariables) {
    this.importVariables = importVariables;
  }

  /**
   * httl.properties: import.methods=java.lang.Math
   */
  public void setImportMethods(Object[] importMethods) {
    for (Object function : importMethods) {
      if (function instanceof Class) {
        this.functions.put((Class<?>) function, function);
      } else {
        this.functions.put(function.getClass(), function);
      }
    }
  }
 
  /**
   * init.
   */
  public void init() {
    if (importVariables != null && importVariables.length > 0) {
      this.importTypes = new HashMap<String, Class<?>>();
      for (String var : importVariables) {
        int i = var.lastIndexOf(' ');
        if (i < 0) {
          throw new IllegalArgumentException("Illegal config import.setVariables");
        }
        this.importTypes.put(var.substring(i + 1), ClassUtils.forName(importPackages, var.substring(0, i)));
      }
    }
  }

  /**
   * inited.
   */
  public void inited() {
    if (importMacros != null && importMacros.length > 0) {
      for (String importMacro : importMacros) {
        try {
          Template importMacroTemplate = engine.getTemplate(importMacro);
          importMacroTemplates.putAll(importMacroTemplate.getMacros());
        } catch (Exception e) {
          throw new IllegalStateException(e.getMessage(), e);
        }
      }
    }
  }

  public Node parse(String source, int offset) throws ParseException {
    return reduce(trim(clean(scan(source, offset))));
  }

  /**
   * 如果指令所在的行没有其他内容,那么将两边的空白内容删除。
   *
   * @author subchen@gmail.com
   */
  private List<Statement> trim(List<Statement> nodes) throws ParseException {
    if (! removeDirectiveBlankLine) {
      return nodes;
    }
   
    // 待移除的空的 Text Node (主要是 Text Node 不允许为空的内容,只能在循环外把它delete了)
    List<Integer> empty_text_index_list = new ArrayList<Integer>();

    for (int i = 0; i < nodes.size(); i++) {
      Statement node = nodes.get(i);

      if (isTrimableDirective(node)) {
        if (i > 0) {
          int prev_index = i - 1;
          Statement prev = nodes.get(prev_index);

          if (isNoLiteralText(prev)
              && !empty_text_index_list.contains(prev_index)) {
            // 删除上一个文本节点最后一个\n之后的所有空白符
            String text = ((Text) prev).getContent();
            int pos = text.lastIndexOf('\n');
            if (pos >= 0) {
              String tail = text.substring(pos + 1);
              if (tail.length() > 0 && tail.trim().length() == 0) {
                text = text.substring(0, pos + 1);
                if (text.length() == 0) {
                  empty_text_index_list.add(prev_index); // 将会是空节点,加入到待删除队列
                } else {
                  nodes.set(prev_index, new Text(text, false,
                      prev.getOffset())); // 修改
                }
              }
            }
          }
        } // prev text node

        if (i + 1 < nodes.size()) {
          int next_index = i + 1;
          Statement next = nodes.get(next_index);

          if (isNoLiteralText(next)) {
            // 删除下一个文本节点地一个\n之前的所有空白符
            String text = ((Text) next).getContent();
            int pos = text.indexOf('\n');
            if (pos >= 0) {
              String head = text.substring(0, pos);
              if (head.trim().length() == 0) {
                text = text.substring(pos + 1);
                boolean isEmptyNode = false;
                if (text.length() == 0) {
                  empty_text_index_list.add(next_index); // 将会是空节点,加入到待删除队列
                  isEmptyNode = true;
                } else if (text.indexOf('\n') == -1
                    && text.trim().length() == 0) {
                  // 看看下面是不是还是个指令,是不是可以全部丢掉
                  if (next_index + 1 < nodes.size()) {
                    Statement next_next = nodes
                        .get(next_index + 1);
                    if (isTrimableDirective(next_next)) {
                      empty_text_index_list
                          .add(next_index); // 将会是空节点,加入到待删除队列
                      isEmptyNode = true;
                    }
                  }
                }
                if (!isEmptyNode) {
                  nodes.set(next_index, new Text(text, false,
                      next.getOffset())); // 修改
                }
              }
            }
            i++; // skip next
          }
        } // next text node
      } // not Directive
    }

    // 删除需要删掉的空节点。
    if (empty_text_index_list.size() > 0) {
      // 必须先删除后面的node
      for (int i = empty_text_index_list.size() - 1; i >= 0; i--) {
        int index = empty_text_index_list.get(i); // 必须转成小 int,
                              // 否则就是删除对象,不是删除指定的index
        nodes.remove(index);
      }
    }
    return nodes;
  }

  private boolean isTrimableDirective(Statement node) {
      if (node instanceof Directive) {
          return !(node instanceof ValueDirective);
      }
      return false;
  }

  private String filterEscape(String source) {
    StringBuffer buf = new StringBuffer();
    Matcher matcher = ESCAPE_PATTERN.matcher(source + "#"); // 预加#号简化正则表达式
    while(matcher.find()) {
      String escape = matcher.group();
      String slash = escape.substring(0, escape.length() - 1);
      String symbol = escape.substring(escape.length() - 1);
      int length = slash.length();
      int half = (length - length % 2) / 2;
      matcher.appendReplacement(buf, Matcher.quoteReplacement(slash.substring(0, half) + symbol));
    }
    matcher.appendTail(buf);
    return buf.toString().substring(0, buf.length() - 1); // 减掉预加的#号
  }

  private Type parseGenericType(String type, int offset) throws ParseException {
    if (StringUtils.isBlank(type)) {
      return null;
    }
    int i = type.indexOf('<');
    if (i < 0) {
      try {
        return ClassUtils.forName(importPackages, type);
      } catch (Exception e) {
        throw new ParseException("No such class " + type + ", cause: " + ClassUtils.dumpException(e), offset);
      }
    }
    if (! type.endsWith(">")) {
      throw new ParseException("Illegal type: " + type, offset);
    }
    Class<?> raw;
    try {
      raw = ClassUtils.forName(importPackages, type.substring(0, i));
    } catch (Exception e) {
      throw new ParseException("No such class " + type.substring(0, i) + ", cause: " + ClassUtils.dumpException(e), offset);
    }
    String parameterType = type.substring(i + 1, type.length() - 1).trim();
    offset = offset + 1;
    List<String> genericTypes = new ArrayList<String>();
    List<Integer> genericOffsets = new ArrayList<Integer>();
    parseGenericTypeString(parameterType, offset, genericTypes, genericOffsets);
    if (genericTypes != null && genericTypes.size() > 0) {
      Type[] types = new Type[genericTypes.size()];
      for (int k = 0; k < genericTypes.size(); k ++) {
        types[k] = parseGenericType(genericTypes.get(k), genericOffsets.get(k));
      }
      return new ParameterizedTypeImpl(raw, types);
    }
    return raw;
  }

  private void parseGenericTypeString(String type, int offset, List<String> types, List<Integer> offsets) throws ParseException {
    StringBuilder buf = new StringBuilder();
    int begin = 0;
    for (int j = 0; j < type.length(); j ++) {
      char ch = type.charAt(j);
      if (ch == '<') {
        begin ++;
      } else if (ch == '>') {
        begin --;
        if (begin < 0) {
           throw new ParseException("Illegal type: " + type, offset + j);
        }
      }
      if (ch == ',' && begin == 0) {
        String token = buf.toString();
        types.add(token.trim());
        offsets.add(offset + j - token.length());
        buf.setLength(0);
      } else {
        buf.append(ch);
      }
    }
    if (buf.length() > 0) {
      String token = buf.toString();
      types.add(token.trim());
      offsets.add(offset + type.length() - token.length());
      buf.setLength(0);
    }
  }
 
  private static boolean isEndString(String value) {
    int sc = 0;
    int dc = 0;
    for (int i = 0; i < value.length(); i ++) {
      char ch = value.charAt(i);
      if (ch == '\'' && dc % 2 == 0
          || ch == '\"' && sc % 2 == 0) {
        int c = 0;
        for (int j = i - 1; j >= 0; j--) {
          if (value.charAt(j) == '\\') {
            c++;
          } else {
            break;
          }
        }
        if (c % 2 == 0) {
          if (ch == '\'') {
            sc ++;
          } else {
            dc ++;
          }
        }
      }
    }
    return sc % 2 == 0 && dc % 2 == 0;
  }

  static List<String> splitAssign(String value) {
    List<String> list = new ArrayList<String>();
    int i = value.indexOf('=');
    while ((i = value.indexOf('=', i + 1)) > 0) {
      if (i + 1 < value.length() && value.charAt(i + 1) == '=') {
        i ++;
      } else if (value.charAt(i - 1) != '>'
          && value.charAt(i - 1) != '<'
          && value.charAt(i - 1) != '!'
          && isEndString(value.substring(0, i - 1))) {
        String sub = value.substring(0, i);
        int j = sub.lastIndexOf(',');
        int k = sub.lastIndexOf('>');
        if (j > 0 && j < k) {
          int g = 0;
          for (int n = k; n >= 0; n --) {
            char c = sub.charAt(n);
            if (c == '>') {
              g ++;
            } else if (c == '<') {
              g --;
            }
            if (g == 0) {
              sub = sub.substring(0, n);
              j = sub.lastIndexOf(',');
              break;
            }
          }
        }
        if (j > 0) {
          list.add(value.substring(0, j));
          value = value.substring(j + 1);
          i = i - j - 1;
        } else {
          break;
        }
      }
    }
    list.add(value);
    return list;
  }

  private static final Pattern DEFINE_PATTERN = Pattern.compile("([\\w>\\]]\\s+\\w+)\\s*[,]?");

  static List<String> splitDefine(String value) {
    List<String> vs = new ArrayList<String>();
    Matcher matcher = DEFINE_PATTERN.matcher(value);
    while (matcher.find()) {
      StringBuffer rep = new StringBuffer();
      matcher.appendReplacement(rep, "$1");
      String v = rep.toString();
      if (v.contains(",")) {
        if (! v.contains("<")) {
          vs.addAll(Arrays.asList(v.split(",")));
        } else if (v.indexOf(',') < v.indexOf('<')) {
          int j = v.indexOf('<');
          int i = v.substring(0, j).lastIndexOf(',');
          vs.addAll(Arrays.asList(v.substring(0, i).split(",")));
          vs.add(v.substring(i + 1));
        } else {
          vs.add(v);
        }
      } else {
        vs.add(v);
      }
    }
    if (vs.size() == 0) {
      vs = Arrays.asList(value.split(","));
    } else {
      StringBuffer tail = new StringBuffer();
      matcher.appendTail(tail);
      if (tail.toString().trim().length() > 0) {
        vs.add(tail.toString());
      }
    }
    return vs;
  }
 
}
TOP

Related Classes of httl.spi.parsers.TemplateParser$BlockDirectiveEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.