package org.xmind.core.internal.xpath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Evaluator {
private static final List<Object> EMPTY_SEQUENCE = Collections.emptyList();
private static final String AXIS_ATTRIBUTE = "attribute"; //$NON-NLS-1$
private static final String AXIS_CHILD = "child"; //$NON-NLS-1$
private static final String AXIS_SELF = "self"; //$NON-NLS-1$
private static final String AXIS_PARENT = "parent"; //$NON-NLS-1$
private static final String KIND_TEXT = "text"; //$NON-NLS-1$
private static final String KIND_NODE = "node"; //$NON-NLS-1$
private static final Set<String> KINDS = new HashSet<String>(
Arrays.asList(KIND_TEXT, KIND_NODE));
private static final String TOKEN_SINGLE_SLASH = "/"; //$NON-NLS-1$
private static final String TOKEN_PREDICATE_START = "["; //$NON-NLS-1$
private static final String TOKEN_PREDICATE_END = "]"; //$NON-NLS-1$
private static final String TOKEN_SELF = "."; //$NON-NLS-1$
private static final String TOKEN_PARENT = ".."; //$NON-NLS-1$
private static final String TOKEN_PAREN_START = "("; //$NON-NLS-1$
private static final String TOKEN_PAREN_END = ")"; //$NON-NLS-1$
private static final String TOKEN_ARGUMENT_SEPARATOR = ","; //$NON-NLS-1$
private static final String TOKEN_AXIS_SEPARATOR = "::"; //$NON-NLS-1$
private static final String TOKEN_AXIS_ATTRIBUTE = "@"; //$NON-NLS-1$
private static final Pattern RE_LEXER = Pattern.compile(
"\\$?(?:(?![0-9-])(?:[\\w-]+|\\*):)?(?![0-9-])(?:[\\w-]+|\\*)|\\(:|:\\)|\\/\\/|\\.\\.|::|\\d+(?:\\.\\d*)?(?:[eE][+-]?\\d+)?|\\.\\d+(?:[eE][+-]?\\d+)?|\"[^\"]*(?:\"\"[^\"]*)*\"|'[^']*(?:''[^']*)*'|<<|>>|[!<>]=|(?![0-9-])[\\w-]+:\\*|\\s+|."); //$NON-NLS-1$
private static final Pattern RE_SPACE = Pattern.compile("^\\s+$"); //$NON-NLS-1$
private static final Pattern RE_NAME = Pattern.compile(
"^(?:(?![0-9-])([\\w-]+|\\*)\\:)?(?![0-9-])([\\w-]+|\\*)$"); //$NON-NLS-1$
private static final Pattern RE_INTEGER = Pattern.compile("^\\d+$"); //$NON-NLS-1$
private static final Pattern RE_DOUBLE = Pattern.compile("^\\d*\\.\\d+$"); //$NON-NLS-1$
private static final Pattern RE_STRING = Pattern
.compile("^'([^']*(?:''[^']*)*)'|\"([^\"]*(?:\"\"[^\"]*)*)\"$"); //$NON-NLS-1$
private static class EvaluationContext {
public Evaluator staticContext;
public Object item;
public int position;
public int size;
public EvaluationContext(Evaluator staticContext, Object item) {
this.staticContext = staticContext;
this.item = item;
this.position = 0;
this.size = 0;
}
}
private static abstract class Expression {
private List<Expression> arguments = new ArrayList<Expression>();
protected Expression() {
}
protected void addArgument(Expression argument) {
this.arguments.add(argument);
}
protected Expression getArgument(int index, Expression defaultArg) {
return index < arguments.size() ? arguments.get(index) : defaultArg;
}
protected List<Expression> getArguments() {
return this.arguments;
}
public abstract List<Object> evaluate(EvaluationContext context);
}
private static final Expression NULL = new Expression() {
@Override
public List<Object> evaluate(EvaluationContext context) {
return EMPTY_SEQUENCE;
}
@Override
public String toString() {
return "null"; //$NON-NLS-1$
}
};
private static class Literal extends Expression {
private Object value;
public Literal(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
@Override
public List<Object> evaluate(EvaluationContext context) {
return Arrays.asList(value);
}
@Override
public String toString() {
return value == null ? "null" : value.toString(); //$NON-NLS-1$
}
}
private static class PathExpression extends Expression {
@Override
public List<Object> evaluate(EvaluationContext context) {
Object oldItem = context.item;
List<Object> sequence = Arrays.asList(oldItem);
List<Object> inputSequence, results;
for (Expression arg : getArguments()) {
inputSequence = sequence;
sequence = new ArrayList<Object>();
for (Object item : inputSequence) {
context.item = item;
results = arg.evaluate(context);
for (Object obj : results) {
if (!sequence.contains(obj)) {
sequence.add(obj);
}
}
}
}
context.item = oldItem;
return sequence;
}
}
private static class AxisExpression extends Expression {
public String axis;
public String nameToTest;
public String kindToTest;
public AxisExpression(String axis, String nameToTest,
String kindToTest) {
this.axis = axis;
this.nameToTest = nameToTest;
this.kindToTest = kindToTest;
}
@Override
public List<Object> evaluate(EvaluationContext context) {
List<Object> sequence = new ArrayList<Object>();
IAxisProvider axisProvider = context.staticContext
.getAxisProvider();
if (axisProvider == null)
return sequence;
Object item = context.item;
if (AXIS_ATTRIBUTE.equals(axis)) {
if (nameToTest != null)
sequence.add(axisProvider.getAttribute(item, nameToTest));
} else if (AXIS_CHILD.equals(axis)) {
if (nameToTest != null)
sequence.addAll(
axisProvider.getChildNodes(item, nameToTest));
} else if (AXIS_PARENT.equals(axis)) {
if (KIND_NODE.equals(kindToTest)) {
sequence.add(axisProvider.getParentNode(item));
}
} else if (AXIS_SELF.equals(axis)) {
if (KIND_TEXT.equals(kindToTest)) {
sequence.add(axisProvider.getTextContent(item));
} else if (KIND_NODE.equals(kindToTest)) {
sequence.add(item);
}
}
return sequence;
}
@Override
public String toString() {
return String.format("%s::%s", axis, //$NON-NLS-1$
nameToTest != null ? nameToTest : kindToTest + "()"); //$NON-NLS-1$
}
}
private static class FilterExpression extends Expression {
private Expression primary;
public FilterExpression(Expression primary) {
this.primary = primary;
}
@Override
public List<Object> evaluate(EvaluationContext context) {
List<Object> sequence = primary.evaluate(context);
sequence = applyPredicates(context, sequence);
return sequence;
}
private List<Object> applyPredicates(EvaluationContext context,
List<Object> sequence) {
if (sequence.isEmpty() || getArguments().isEmpty())
return sequence;
Object oldItem = context.item;
int oldPosition = context.position;
int oldSize = context.size;
List<Object> inputSequence, results;
int sequenceSize;
for (Expression predicate : getArguments()) {
inputSequence = sequence;
sequenceSize = inputSequence.size();
sequence = new ArrayList<Object>();
if (predicate instanceof Literal && ((Literal) predicate)
.getValue() instanceof Integer) {
int targetIndex = ((Integer) ((Literal) predicate)
.getValue()).intValue() - 1;
if (targetIndex >= 0 && targetIndex < sequenceSize) {
sequence.add(inputSequence.get(targetIndex));
}
} else {
for (int index = 0; index < sequenceSize; index++) {
Object item = inputSequence.get(index);
context.item = item;
context.position = index + 1;
context.size = sequenceSize;
results = predicate.evaluate(context);
if (test(results, context, index + 1, predicate)) {
sequence.add(item);
}
}
}
}
context.item = oldItem;
context.position = oldPosition;
context.size = oldSize;
return sequence;
}
private boolean test(List<Object> conditions, EvaluationContext context,
int position, Expression predicate) {
if (conditions.size() != 1)
return false;
Object condition = conditions.get(0);
if (condition instanceof Boolean)
return ((Boolean) condition).booleanValue();
if (condition instanceof String)
return !"".equals(condition); //$NON-NLS-1$
if (condition instanceof Integer)
return ((Integer) condition).intValue() != 0;
if (condition instanceof Double)
return ((Double) condition).doubleValue() != 0;
return condition != null;
}
}
private static class FunctionArgument {
private List<Object> sequence;
public FunctionArgument(List<Object> sequence) {
this.sequence = sequence;
}
public Object anyItem() {
return anyItem(null);
}
public Object anyItem(Object defaultItem) {
return sequence.isEmpty() ? defaultItem : sequence.get(0);
}
public List<Object> sequence() {
return sequence;
}
}
private static final FunctionArgument NULL_ARGUMENT = new FunctionArgument(
EMPTY_SEQUENCE);
private static class FunctionArgumentList {
private List<FunctionArgument> arguments;
public FunctionArgumentList() {
this.arguments = new ArrayList<FunctionArgument>();
}
public void add(FunctionArgument arg) {
arguments.add(arg);
}
public FunctionArgument argumentAt(int index) {
return index >= 0 && index < arguments.size() ? arguments.get(index)
: NULL_ARGUMENT;
}
public List<Object> sequenceAt(int index) {
return argumentAt(index).sequence();
}
public Object itemAt(int index) {
return argumentAt(index).anyItem();
}
}
private static interface Function {
Object call(EvaluationContext context, FunctionArgumentList args);
}
private static Map<String, Function> FUNCTIONS = new HashMap<String, Function>();
static {
FUNCTIONS.put("true", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
return Boolean.TRUE;
}
});
FUNCTIONS.put("false", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
return Boolean.FALSE;
}
});
FUNCTIONS.put("position", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
return Integer.valueOf(context.position);
}
});
FUNCTIONS.put("count", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
return Integer.valueOf(args.sequenceAt(0).size());
}
});
FUNCTIONS.put("matches", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
Object text = args.itemAt(0);
Object regex = args.itemAt(1);
if (text == null || !(text instanceof String) || regex == null
|| !(regex instanceof String))
return Boolean.FALSE;
return Boolean.valueOf(
Pattern.matches((String) regex, (String) text));
}
});
FUNCTIONS.put("eq", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
Object op1 = args.itemAt(0);
Object op2 = args.itemAt(1);
return op1 == op2 || (op1 != null && op1.equals(op2));
}
});
FUNCTIONS.put("ne", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
Object op1 = args.itemAt(0);
Object op2 = args.itemAt(1);
return op1 != op2 && (op1 == null || !op1.equals(op2));
}
});
FUNCTIONS.put("lt", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
Object op1 = args.itemAt(0);
Object op2 = args.itemAt(1);
if ((!(op1 instanceof Integer) && !(op1 instanceof Double))
|| (!(op2 instanceof Integer)
&& !(op2 instanceof Double)))
return false;
Number n1 = (Number) op1;
Number n2 = (Number) op2;
if (op1 instanceof Double || op2 instanceof Double) {
return n1.doubleValue() < n2.doubleValue();
}
return n1.intValue() < n2.intValue();
}
});
FUNCTIONS.put("gt", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
Object op1 = args.itemAt(0);
Object op2 = args.itemAt(1);
if ((!(op1 instanceof Integer) && !(op1 instanceof Double))
|| (!(op2 instanceof Integer)
&& !(op2 instanceof Double)))
return false;
Number n1 = (Number) op1;
Number n2 = (Number) op2;
if (op1 instanceof Double || op2 instanceof Double) {
return n1.doubleValue() > n2.doubleValue();
}
return n1.intValue() > n2.intValue();
}
});
FUNCTIONS.put("lte", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
Object op1 = args.itemAt(0);
Object op2 = args.itemAt(1);
if ((!(op1 instanceof Integer) && !(op1 instanceof Double))
|| (!(op2 instanceof Integer)
&& !(op2 instanceof Double)))
return false;
Number n1 = (Number) op1;
Number n2 = (Number) op2;
if (op1 instanceof Double || op2 instanceof Double) {
return n1.doubleValue() <= n2.doubleValue();
}
return n1.intValue() <= n2.intValue();
}
});
FUNCTIONS.put("gte", new Function() { //$NON-NLS-1$
public Object call(EvaluationContext context,
FunctionArgumentList args) {
Object op1 = args.itemAt(0);
Object op2 = args.itemAt(1);
if ((!(op1 instanceof Integer) && !(op1 instanceof Double))
|| (!(op2 instanceof Integer)
&& !(op2 instanceof Double)))
return false;
Number n1 = (Number) op1;
Number n2 = (Number) op2;
if (op1 instanceof Double || op2 instanceof Double) {
return n1.doubleValue() >= n2.doubleValue();
}
return n1.intValue() >= n2.intValue();
}
});
}
private static final Map<String, String> OPERATORS = new HashMap<String, String>();
static {
OPERATORS.put("=", "eq"); //$NON-NLS-1$ //$NON-NLS-2$
OPERATORS.put("!=", "ne"); //$NON-NLS-1$ //$NON-NLS-2$
OPERATORS.put("<", "lt"); //$NON-NLS-1$ //$NON-NLS-2$
OPERATORS.put(">", "gt"); //$NON-NLS-1$ //$NON-NLS-2$
OPERATORS.put("<=", "lte"); //$NON-NLS-1$ //$NON-NLS-2$
OPERATORS.put(">=", "gte"); //$NON-NLS-1$ //$NON-NLS-2$
}
private static class FunctionCall extends Expression {
private String name;
public FunctionCall(String name) {
this.name = name;
}
@SuppressWarnings("unchecked")
@Override
public List<Object> evaluate(EvaluationContext context) {
Function f = FUNCTIONS.get(name);
if (f == null)
return EMPTY_SEQUENCE;
FunctionArgumentList args = new FunctionArgumentList();
for (Expression arg : getArguments()) {
List<Object> sequence = arg.evaluate(context);
args.add(new FunctionArgument(sequence));
}
Object result = f.call(context, args);
if (result instanceof List)
return (List) result;
return Arrays.asList(result);
}
}
private String expressionText;
private Expression expression;
private IAxisProvider axisProvider;
public Evaluator(String expression) {
this(expression, null);
}
public Evaluator(String expression, IAxisProvider axisProvider) {
this.expressionText = expression;
this.axisProvider = axisProvider;
this.expression = null;
}
public IAxisProvider getAxisProvider() {
return axisProvider;
}
public void setAxisProvider(IAxisProvider axisProvider) {
this.axisProvider = axisProvider;
}
@Override
public String toString() {
return expression.toString();
}
public List<Object> evaluate(Object context) {
if (expression == null)
expression = new ExpressionParser(expressionText).parse();
EvaluationContext ctx = new EvaluationContext(this, context);
return expression.evaluate(ctx);
}
private static class ExpressionParser {
private String[] tokens;
private int tokenIndex;
public ExpressionParser(String expression) {
List<String> tokens = new ArrayList<String>();
Matcher matcher = RE_LEXER.matcher(expression);
while (matcher.find()) {
String token = matcher.group();
if (!RE_SPACE.matcher(token).matches()) {
tokens.add(token);
}
}
this.tokens = tokens.toArray(new String[tokens.size()]);
this.tokenIndex = 0;
}
public Expression parse() {
return parseExpression();
}
private String token() {
return token(0);
}
private String token(int offset) {
int index = tokenIndex + offset;
return index < tokens.length ? tokens[index] : null;
}
private boolean nextToken() {
return nextToken(1);
}
private boolean nextToken(int offset) {
tokenIndex += Math.max(offset, 1);
return tokenIndex < tokens.length;
}
private boolean hasToken() {
return tokenIndex < tokens.length;
}
private Expression parseExpression() {
return parseSingleExpression();
}
private Expression parseSingleExpression() {
return parseOrExpression();
}
private Expression parseOrExpression() {
return parseAndExpression();
}
private Expression parseAndExpression() {
return parseComparisonExpression();
}
private Expression parseComparisonExpression() {
if (!hasToken())
return NULL;
Expression arg1 = null;
if (hasToken()) {
arg1 = parseRangeExpression();
}
if (!hasToken())
return arg1;
String funcName = OPERATORS.get(token());
if (funcName == null)
return arg1;
nextToken();
Expression arg2 = parseRangeExpression();
Expression func = new FunctionCall(funcName);
func.addArgument(arg1);
func.addArgument(arg2);
return func;
}
private Expression parseRangeExpression() {
return parseAdditiveExpression();
}
private Expression parseAdditiveExpression() {
return parseMultiplicativeExpression();
}
private Expression parseMultiplicativeExpression() {
return parseUnionExpression();
}
private Expression parseUnionExpression() {
return parseIntersectExceptExpression();
}
private Expression parseIntersectExceptExpression() {
return parseInstanceofExpression();
}
private Expression parseInstanceofExpression() {
return parseTreatExpression();
}
private Expression parseTreatExpression() {
return parseCastableExpression();
}
private Expression parseCastableExpression() {
return parseCastExpression();
}
private Expression parseCastExpression() {
return parseUnaryExpression();
}
private Expression parseUnaryExpression() {
return parseValueExpression();
}
private Expression parseValueExpression() {
return parsePathExpression();
}
private Expression parsePathExpression() {
if (!hasToken())
return NULL;
PathExpression path = new PathExpression();
if (TOKEN_SINGLE_SLASH.equals(token()))
nextToken();
while (true) {
Expression step = parseStepExpression();
if (step == NULL)
break;
path.addArgument(step);
if (TOKEN_SINGLE_SLASH.equals(token()))
nextToken();
else
break;
}
if (path.getArguments().size() == 1)
return path.getArgument(0, NULL);
return path;
}
private Expression parseStepExpression() {
return parseFilterExpression();
}
private Expression parseFilterExpression() {
if (!hasToken())
return NULL;
Expression exp = parsePrimaryExpression();
if (exp == NULL)
return NULL;
Expression filter = new FilterExpression(exp);
while (TOKEN_PREDICATE_START.equals(token())) {
nextToken();
Expression predicate = parseExpression();
if (predicate != NULL) {
filter.addArgument(predicate);
}
if (TOKEN_PREDICATE_END.equals(token())) {
nextToken();
}
}
if (filter.getArguments().isEmpty())
return exp;
return filter;
}
private Expression parsePrimaryExpression() {
if (!hasToken())
return NULL;
String token = token();
if (TOKEN_PARENT.equals(token)) {
nextToken();
return new AxisExpression(AXIS_PARENT, null, KIND_NODE);
}
if (TOKEN_SELF.equals(token)) {
nextToken();
return new AxisExpression(AXIS_SELF, null, KIND_NODE);
}
Expression exp;
exp = parseParenthesizedExpression();
if (exp != NULL)
return exp;
exp = parseFunctionCall();
if (exp != NULL)
return exp;
exp = parseVarRef();
if (exp != NULL)
return exp;
exp = parseLiteral();
if (exp != NULL)
return exp;
exp = parseAxisExpression();
if (exp != NULL)
return exp;
return NULL;
}
private Expression parseParenthesizedExpression() {
Expression exp = NULL;
if (TOKEN_PAREN_START.equals(token())) {
nextToken();
if (!TOKEN_PAREN_END.equals(token())) {
exp = parseExpression();
}
if (TOKEN_PAREN_END.equals(token()))
nextToken();
}
return exp;
}
private Expression parseFunctionCall() {
if (!hasToken())
return NULL;
Matcher nameMatch = RE_NAME.matcher(token());
if (nameMatch.matches() && TOKEN_PAREN_START.equals(token(1))) {
String namespace = nameMatch.group(1);
String funcName = nameMatch.group(2);
if (namespace == null && KINDS.contains(funcName))
return parseAxisExpression();
FunctionCall functionCall = new FunctionCall(funcName);
nextToken(2);
if (!TOKEN_PAREN_END.equals(token())) {
do {
Expression exp = parseSingleExpression();
functionCall.addArgument(exp);
} while (TOKEN_ARGUMENT_SEPARATOR.equals(token())
&& nextToken());
}
if (TOKEN_PAREN_END.equals(token())) {
nextToken();
}
return functionCall;
}
return NULL;
}
private Expression parseVarRef() {
return NULL;
}
private Expression parseLiteral() {
if (!hasToken())
return NULL;
String token = token();
if (RE_INTEGER.matcher(token).matches()) {
nextToken();
return new Literal(
Integer.valueOf(Integer.parseInt(token, 10)));
} else if (RE_DOUBLE.matcher(token).matches()) {
nextToken();
return new Literal(Double.valueOf(Double.parseDouble(token)));
} else {
Matcher m = RE_STRING.matcher(token);
if (m.matches()) {
nextToken();
String string;
if (m.group(1) != null) {
string = m.group(1).replace("''", "'"); //$NON-NLS-1$ //$NON-NLS-2$
} else if (m.group(2) != null) {
string = m.group(2).replace("\"\"", "\""); //$NON-NLS-1$ //$NON-NLS-2$
} else {
string = ""; //$NON-NLS-1$
}
return new Literal(string);
}
}
return NULL;
}
private Expression parseAxisExpression() {
if (!hasToken())
return NULL;
String axis = token();
if (TOKEN_AXIS_SEPARATOR.equals(token(1))) {
nextToken(2);
String toTest = token();
if (toTest != null && RE_NAME.matcher(toTest).matches()) {
nextToken();
if (TOKEN_PAREN_START.equals(token(1))
&& TOKEN_PAREN_END.equals(token(2))) {
nextToken(2);
return new AxisExpression(axis, null, toTest);
} else {
return new AxisExpression(axis, toTest, null);
}
} else {
return new AxisExpression(axis, null, KIND_NODE);
}
} else if (TOKEN_AXIS_ATTRIBUTE.equals(axis)) {
nextToken();
String name = token();
if (RE_NAME.matcher(name).matches()) {
nextToken();
return new AxisExpression(AXIS_ATTRIBUTE, name, null);
}
} else {
String name = token();
if (RE_NAME.matcher(name).matches()) {
nextToken();
return new AxisExpression(AXIS_CHILD, name, null);
}
}
return NULL;
}
}
}