做excel单元格自定义校验规则时搞的一个小轮子,适合给不懂编码的人使用,如果会一些编码知识,直接使用groovy解析会更灵活。

可稍作拆解为计算器。

11.4更新: 优化对负数的处理

package com.weilink.commontools.utils.calculator;import java.math.BigDecimal;/*** ${DESCRIPTION}* 大于小于等于等逻辑判断计算器,* 判断等于时可设置精度,支持与、或运算,支持MAX,MIN函数** @author 林威* @create 2021-10-09 10:34**/
public class Mycalculator {public static void main(String[] args) throws Exception {System.out.println(explainExpress("1--2=3"));System.out.println(explainExpress("-1--2=1"));System.out.println(explainExpress("-1--2=3"));System.out.println(explainExpress("-1--2*-5=-11"));}// 长的放在数组前面,因为在匹配表达式时优先匹配长的private static String[] validSymbol = new String[]{">=", "<=", "!=", "≥", "≤", "≠", ">", "<", "="};public static boolean explainExpress(String str) throws Exception {return explainExpress(str, null).equals("true");}private static String explainExpress(String str, Function function) throws Exception {str = str.replaceAll(" ", "");if (null != function) {return function.cal();}// 函数优先计算str = functionCal(str);// 递归计算// 括号优先if (str.contains(")")) {int lIndex = str.lastIndexOf("(");int rIndex = str.indexOf(")", lIndex);return explainExpress(str.substring(0, lIndex) + explainExpress(str.substring(lIndex + 1, rIndex), null) + str.substring(rIndex + 1), null);}if (str.contains("|")) {int index = str.lastIndexOf("|");String valLeft = explainExpress(str.substring(0, index), null);String valRight = explainExpress(str.substring(index + 1), null);if (valLeft.equals("true") || valRight.equals("true")) {return "true";} else {return "false";}}if (str.contains("&")) {int index = str.lastIndexOf("&");String valLeft = explainExpress(str.substring(0, index), null);String valRight = explainExpress(str.substring(index + 1), null);if (valLeft.equals("true") && valRight.equals("true")) {return "true";} else {return "false";}}return expressValid(str);}private static String expressValid(String exp) throws Exception {if (exp.equals("true")) {return "true";} else if (exp.equals("false")) {return "false";}String symbol = null;String[] formulas = null;for (String sy : validSymbol) {if (exp.split(sy).length == 2) {symbol = sy;formulas = exp.split(sy);break;}}if (formulas == null) {return getResult(exp).toPlainString();}BigDecimal valueLeft = getResult(formulas[0]);BigDecimal valueRight = getResult(formulas[1]);switch (symbol) {case ">":return valueLeft.compareTo(valueRight) > 0 ? "true" : "false";case "<":return valueLeft.compareTo(valueRight) < 0 ? "true" : "false";case "=":// 默认等于的精度为四位return valueLeft.subtract(valueRight).abs().compareTo(new BigDecimal("0.0001")) < 0 ? "true" : "false";case "≥":return valueLeft.compareTo(valueRight) >= 0 ? "true" : "false";case ">=":return valueLeft.compareTo(valueRight) >= 0 ? "true" : "false";case "≤":return valueLeft.compareTo(valueRight) <= 0 ? "true" : "false";case "<=":return valueLeft.compareTo(valueRight) <= 0 ? "true" : "false";case "≠":return valueLeft.compareTo(valueRight) != 0 ? "true" : "false";case "!=":return valueLeft.compareTo(valueRight) != 0 ? "true" : "false";}throw new Exception("错误的表达式");}private static String functionCal(String str) throws Exception {// 这里设计的有问题,应该把所有函数设置为static数组变量放到Function中,判断最右侧变量的逻辑也// 该相应改造/*** <br/> 从右到左解析函数,即可满足 所有嵌套函数是从内到外解析的* @date 2021/10/9 10:59*/while (str.contains("MAX") || str.contains("MIN")) {Function function;int start = 0;int startMin = str.indexOf("(", str.lastIndexOf("MIN"));int startMax = str.indexOf("(", str.lastIndexOf("MAX"));if (str.lastIndexOf("MIN") < 0) {start = startMax;function = new Max();} else if (str.lastIndexOf("MAX") < 0) {start = startMin;function = new Min();} else {if (startMax > startMin) {start = startMax;function = new Max();} else {start = startMin;function = new Min();}}int end = getEnd(str, start);String innerExpr = str.substring(start + 1, end);function.setExpr(innerExpr);String startStr = str.substring(0, start - 3);String endStr = str.substring(end + 1);str = startStr + explainExpress(str, function) + endStr;}return str;}private static int getEnd(String str, int start) throws Exception {int count = 1;for (int i = start + 1; i < str.length(); i++) {if (str.charAt(i) == '(') {count++;}if (str.charAt(i) == ')') {count--;}if (count == 0) {return i;}}throw new Exception("错误的表达式");}public static BigDecimal getResult(String str) throws Exception {if (str.isEmpty() || isNumber(str)) {if (str.isEmpty()) {return BigDecimal.ZERO;}if (str.endsWith("%")) {str = str.replace("%", "");return new BigDecimal(str).divide(new BigDecimal("100"));}return new BigDecimal(str);}if (str.contains(")")) {int lIndex = str.lastIndexOf("(");int rIndex = str.indexOf(")", lIndex);return getResult(str.substring(0, lIndex) + getResult(str.substring(lIndex + 1, rIndex)) + str.substring(rIndex + 1));}//先乘除后加减//乘除运算必须从左边算起if (str.contains("*") || str.contains("/")) {int locate;int locateM = str.indexOf("*");int loacateD = str.indexOf("/");int type = 1;if (loacateD < 0) {locate = locateM;} else if (locateM < 0) {locate = loacateD;type = 2;} else {if (locateM < loacateD) {locate = locateM;} else {locate = loacateD;type = 2;}}int leftNum = getLeftNum(str, locate);int rightNum = getRightNum(str, locate);BigDecimal leftValue = new BigDecimal(str.substring(leftNum, locate));BigDecimal rightValue;String leftStr = str.substring(0, leftNum);String rightStr;if (rightNum >= str.length()) {rightValue = new BigDecimal(str.substring(locate + 1));rightStr = "";} else {rightStr = str.substring(rightNum + 1);rightValue = new BigDecimal(str.substring(locate + 1, rightNum + 1));}String value;if (type == 1) {value = leftValue.multiply(rightValue).toPlainString();} else {value = leftValue.divide(rightValue, 10, BigDecimal.ROUND_DOWN).toPlainString();}return getResult(leftStr + value + rightStr);}// 先加法后减法可以解决负数问题,但是仍然需要处理连续减号if (str.contains("+")) {int index = str.lastIndexOf("+");return getResult(str.substring(0, index)).add(getResult(str.substring(index + 1)));}if (str.contains("-")) {// 如果连续两个减号,则第一个减号为运算符,第二个为负数标识int index = str.lastIndexOf("-");if (index != 0 && str.charAt(index - 1) == '-') {index--;}return getResult(str.substring(0, index)).subtract(getResult(str.substring(index + 1)));}throw new Exception("错误的表达式");}private static int getLeftNum(String str, int locate) {for (int i = locate - 1; i >= 0; i--) {if (!Character.isDigit(str.charAt(i)) && str.charAt(i) != '.' && str.charAt(i) != '%') {// 负数判断if (str.charAt(i) == '-') {if (i == 0) {return i;} else {char c = str.charAt(i - 1);if (!(Character.isDigit(c)) || c == '%') {return i;}}}return i + 1;}}return 0;}private static int getRightNum(String str, int locate) {for (int i = locate + 1; i < str.length(); i++) {if (i == locate + 1 && str.charAt(i) == '-') {continue;}if (!Character.isDigit(str.charAt(i)) && str.charAt(i) != '.' && str.charAt(i) != '%') {return i - 1;}}return str.length();}static boolean isNumber(String str) {//增加负数判定String temp = str;if (str.startsWith("-")) {temp = temp.replaceFirst("-", "");}for (int i = 0; i < temp.length(); i++) {if (!Character.isDigit(temp.charAt(i)) && temp.charAt(i) != '.' && temp.charAt(i) != '%') {return false;}}return true;}}

几个扩展类

package com.weilink.commontools.utils.calculator;/*** ${DESCRIPTION}** @author linwei* @create 2021-10-09 10:41**/
public abstract class Function {protected String expr;public Function(String expr) {this.expr = expr;}public Function() {}public abstract String cal()throws Exception;public String getExpr() {return expr;}public void setExpr(String expr) {this.expr = expr;}
}

package com.weilink.commontools.utils.calculator;import java.math.BigDecimal;/*** ${DESCRIPTION}** @author linwei* @create 2021-10-09 10:42**/
public class Max extends Function {@Overridepublic String cal() throws Exception{BigDecimal val = null;for (String str : expr.split(",")) {BigDecimal current = Mycalculator.getResult(str);if (val == null || val.compareTo(current) < 0) {val = current;}}return val.toPlainString();}
}
package com.weilink.commontools.utils.calculator;import java.math.BigDecimal;/*** ${DESCRIPTION}** @author linwei* @create 2021-10-09 10:50**/
public class Min extends Function {@Overridepublic String cal() throws Exception{BigDecimal val = null;for (String str : expr.split(",")) {BigDecimal current = Mycalculator.getResult(str);if (val == null || val.compareTo(current) > 0) {val = current;}}return val.toPlainString();}
}