package org.activityinfo.server.endpoint.odk.build; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import org.activityinfo.model.expr.*; import org.activityinfo.model.expr.functions.ExprFunction; import org.activityinfo.model.resource.ResourceId; import org.activityinfo.model.type.FieldValue; import org.activityinfo.model.type.enumerated.EnumItem; import org.activityinfo.model.type.enumerated.EnumType; import org.activityinfo.model.type.primitive.BooleanFieldValue; import org.activityinfo.server.endpoint.odk.OdkField; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * Builds xpath expressions from AI expressions. */ public class XPathBuilder { private static final Logger LOGGER = Logger.getLogger(XPathBuilder.class.getName()); public static final String TRUE = "true()"; public static final String FALSE = "false()"; private final Map<String, String> symbolMap = new HashMap<>(); public XPathBuilder(List<OdkField> fields) { for(OdkField field : fields) { symbolMap.put(field.getModel().getId().asString(), field.getAbsoluteFieldName()); if(field.getModel().getType() instanceof EnumType) { EnumType type = (EnumType) field.getModel().getType(); for (EnumItem item : type.getValues()) { symbolMap.put(item.getId().asString(), quote(item.getId().asString())); } } } } private String quote(String value) { return "'" + value + "'"; } public String build(String expr) { if(Strings.isNullOrEmpty(expr)) { return null; } try { return build(ExprParser.parse(expr)); } catch (XPathBuilderException e) { LOGGER.log(Level.WARNING, "Exception translating expr '" + expr + "' to XPATH: " + e.getMessage()); return null; } } public String build(ExprNode exprNode) { if (exprNode == null) { return null; } else { StringBuilder xpath = new StringBuilder(); appendTo(exprNode, xpath); return xpath.toString(); } } private void appendTo(ExprNode exprNode, StringBuilder xpath) { if (exprNode instanceof ConstantExpr) { ConstantExpr constantExpr = (ConstantExpr) exprNode; FieldValue value = constantExpr.getValue(); if (value instanceof BooleanFieldValue) { BooleanFieldValue booleanFieldValue = (BooleanFieldValue) value; xpath.append(booleanFieldValue.asBoolean() ? TRUE : FALSE); } else { xpath.append(constantExpr.asExpression()); } } else if (exprNode instanceof FunctionCallNode) { FunctionCallNode functionCallNode = (FunctionCallNode) exprNode; List<ExprNode> arguments = functionCallNode.getArguments(); ExprFunction function = functionCallNode.getFunction(); switch (function.getId()) { case "&&": appendBinaryInfixTo("and", arguments, xpath); break; case "==": appendBinaryInfixTo("=", arguments, xpath); break; case "||": appendBinaryInfixTo("or", arguments, xpath); break; default: throw new XPathBuilderException("Unsupported function " + function.getId()); } } else if (exprNode instanceof GroupExpr) { GroupExpr groupExpr = (GroupExpr) exprNode; ExprNode expr = groupExpr.getExpr(); xpath.append("("); appendTo(expr, xpath); xpath.append(")"); } else if (exprNode instanceof SymbolExpr) { SymbolExpr symbolExpr = (SymbolExpr) exprNode; String name = symbolExpr.getName(); String xpathExpr = symbolMap.get(name); if(xpathExpr == null) { throw new XPathBuilderException("Unknown symbol '" + name + "'"); } xpath.append(xpathExpr); } else { throw new XPathBuilderException("Unknown expr node " + exprNode); } } private void appendBinaryInfixTo(String operatorName, List<ExprNode> arguments, StringBuilder xpath) { Preconditions.checkArgument(arguments.size() == 2); appendTo(arguments.get(0), xpath); xpath.append(" ").append(operatorName).append(" "); appendTo(arguments.get(1), xpath); } public static String fieldTagName(ResourceId fieldId) { return "field_" + fieldId.asString(); } }