package org.openrosa.client.xforms;
import java.util.HashMap;
import java.util.Iterator;
import org.openrosa.client.model.FormDef;
import org.openrosa.client.model.IFormElement;
import org.openrosa.client.model.QuestionDef;
import org.openrosa.client.model.ModelConstants;
import org.openrosa.client.xforms.XformConstants;
import com.google.gwt.xml.client.Element;
/**
* Utility methods used during the parsing of xforms documents.
*
* @author daniel
*
*/
public class XformParserUtil {
/**
* All methods in this class are static and hence we expect no external
* Instantiation of this class.
*/
private XformParserUtil(){
}
/**
* Converts an xpath operator in a relevant or constraint expression to its
* corresponding constant in our object model.
*
* @param expression the xpath expression.
* @param action the skip or validation rule action to the target questions.
* @return the operator constant.
*/
//TODO Add the other xpath operators
public static int getOperator(String expression, int action){
//We return the operator which is the opposite of the expression
if(expression.indexOf(">=") > 0 || expression.indexOf(">=") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_GREATER_EQUAL;
return ModelConstants.OPERATOR_LESS;
}
else if(expression.indexOf('>') > 0 || expression.indexOf(">") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_GREATER;
return ModelConstants.OPERATOR_LESS_EQUAL;
}
else if(expression.indexOf("<=") > 0 || expression.indexOf("<=") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_LESS_EQUAL;
return ModelConstants.OPERATOR_GREATER;
}
else if(expression.indexOf('<') > 0 || expression.indexOf("<") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_LESS;
return ModelConstants.OPERATOR_GREATER_EQUAL;
}
else if(expression.indexOf("!=") > 0 || expression.indexOf("!=") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_NOT_EQUAL;
return ModelConstants.OPERATOR_EQUAL;
}
else if(expression.indexOf('=') > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_EQUAL;
return ModelConstants.OPERATOR_NOT_EQUAL;
}
else if(expression.indexOf("not(starts-with") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_NOT_START_WITH;
return ModelConstants.OPERATOR_STARTS_WITH;
}
else if(expression.indexOf("starts-with") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_STARTS_WITH;
return ModelConstants.OPERATOR_NOT_START_WITH;
}
else if(expression.indexOf("not(contains") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_NOT_CONTAIN;
return ModelConstants.OPERATOR_CONTAINS;
}
else if(expression.indexOf("contains") > 0){
if(XformBuilderUtil.isPositiveAction(action))
return ModelConstants.OPERATOR_CONTAINS;
return ModelConstants.OPERATOR_NOT_CONTAIN;
}
return ModelConstants.OPERATOR_NULL;
}
/**
* Gets the xpath operator size of an operator constant.
*
* @param operator the operator constant.
* @param action the skip or validation rule target action.
* @return the xpath operator size.
*/
public static int getOperatorSize(int operator, int action){
if(operator == ModelConstants.OPERATOR_GREATER_EQUAL ||
operator == ModelConstants.OPERATOR_LESS_EQUAL ||
operator == ModelConstants.OPERATOR_NOT_EQUAL)
return XformBuilderUtil.isPositiveAction(action) ? 2 : 1;
else if(operator == ModelConstants.OPERATOR_LESS ||
operator == ModelConstants.OPERATOR_GREATER ||
operator == ModelConstants.OPERATOR_EQUAL)
return XformBuilderUtil.isPositiveAction(action) ? 1 : 2;
return 0;
}
/**
* Gets the position of an operator in an xpath expression.
*
* @param expression the xpath expression.
* @return the operator start position or index.
*/
public static int getOperatorPos(String expression){
//Using lastindexof because of expressions like:
//relevant="/ClinicalData/SubjectData/StudyEventData/FormData/ItemGroupData/ItemData[@ItemOID='I_REVI_IMPROVEMENT']/@Value = '1'"
int pos = expression.lastIndexOf("!=");
if(pos > 0)
return pos;
pos = expression.lastIndexOf(">=");
if(pos > 0)
return pos;
pos = expression.lastIndexOf("<=");
if(pos > 0)
return pos;
pos = expression.lastIndexOf('>');
if(pos > 0)
return pos;
pos = expression.lastIndexOf('<');
if(pos > 0)
return pos;
pos = expression.lastIndexOf('=');
if(pos > 0)
return pos;
//the order of the code below should not be changed as for example 'starts with' can be taken
//even when condition is 'not(starts-with'
pos = expression.lastIndexOf("not(starts-with");
if(pos > 0)
return pos;
pos = expression.lastIndexOf("starts-with");
if(pos > 0)
return pos;
pos = expression.lastIndexOf("not(contains");
if(pos > 0)
return pos;
pos = expression.lastIndexOf("contains");
if(pos > 0)
return pos;
return pos;
}
/**
* Gets the question variable name without the form prefix (/newform1/)
*
* @param bindNode the xforms bind node.
* @param formDef the form to which the question belongs.
* @return the question variable name.
*/
public static String getQuestionVariableName(Element bindNode, FormDef formDef){
String name = bindNode.getAttribute(XformConstants.ATTRIBUTE_NAME_NODESET);
// if(name.startsWith("/"+formDef.getVariableName()+"/"))
// name = name.replace("/"+formDef.getVariableName()+"/", "");
String[] tokens = name.split("/");
name = tokens[tokens.length-1];
return name;
}
/**
* Gets the IFormElement variable name (i.e. question ID) without any prefixes ("/data/group1/otherGroup/")
*
* checks for both 'nodeset' and 'ref' attributes. If neither exists, returns null.
*
* @param bindNode the xforms bind node.
* @param formDef the form to which the question belongs.
* @return the question variable name or null if neither nodeset, nor ref exists
*/
public static String getQuestionIDFromRefOrNodeset(Element Node, FormDef formDef){
String name = Node.getAttribute(XformConstants.ATTRIBUTE_NAME_NODESET);
if(name == null){
name = Node.getAttribute(XformConstants.ATTRIBUTE_NAME_REF);
}
if(name == null){
return null;
}
name = name.replace(" ", "");
String[] tokens = name.split("/");
name = tokens[tokens.length-1];
return name;
}
/**
* Sets the question definition object data type based on an xml xsd type.
*
* @param def the question definition object.
* @param type the xml xsd type.
* @param node the xforms node having the type attribute.
*/
public static void setQuestionType(IFormElement def, String type, Element node){
if(type != null){
if(type.equals(XformConstants.DATA_TYPE_TEXT) || type.indexOf("string") != -1 ){
String format = node.getAttribute(XformConstants.ATTRIBUTE_NAME_FORMAT);
if(XformConstants.ATTRIBUTE_VALUE_GPS.equals(format))
def.setDataType(QuestionDef.QTN_TYPE_GPS);
else
def.setDataType(QuestionDef.QTN_TYPE_TEXT);
}
else if((type.equals("xsd:integer") || type.equals(XformConstants.DATA_TYPE_INT)) || (type.indexOf("integer") != -1 || (type.indexOf("int") != -1) && !type.equals("geopoint") ))
def.setDataType(QuestionDef.QTN_TYPE_NUMERIC);
else if(type.equals("xsd:decimal") || type.indexOf("decimal") != -1 )
def.setDataType(QuestionDef.QTN_TYPE_DECIMAL);
else if(type.equals("xsd:long") || type.indexOf("long") != -1){
def.setDataType(QuestionDef.QTN_TYPE_LONG);
}else if(type.equals("xsd:dateTime") || type.indexOf("dateTime") != -1 )
def.setDataType(QuestionDef.QTN_TYPE_DATE_TIME);
else if(type.equals("xsd:time") || type.indexOf("time") != -1 )
def.setDataType(QuestionDef.QTN_TYPE_TIME);
else if(type.equals(XformConstants.DATA_TYPE_DATE) || type.indexOf("date") != -1 )
def.setDataType(QuestionDef.QTN_TYPE_DATE);
else if(type.equals(XformConstants.DATA_TYPE_BOOLEAN) || type.indexOf("boolean") != -1 )
def.setDataType(QuestionDef.QTN_TYPE_BOOLEAN);
else if(type.equals(XformConstants.DATA_TYPE_BINARY) || type.indexOf("base64Binary") != -1 ){
String format = node.getAttribute(XformConstants.ATTRIBUTE_NAME_FORMAT);
if(XformConstants.ATTRIBUTE_VALUE_VIDEO.equals(format))
def.setDataType(QuestionDef.QTN_TYPE_VIDEO);
else if(XformConstants.ATTRIBUTE_VALUE_AUDIO.equals(format))
def.setDataType(QuestionDef.QTN_TYPE_AUDIO);
else
def.setDataType(QuestionDef.QTN_TYPE_IMAGE);
}
//TODO These two are used by ODK
else if(type.equalsIgnoreCase("binary")){
def.setDataType(QuestionDef.QTN_TYPE_IMAGE);
}
else if(type.equalsIgnoreCase("geopoint")){
def.setDataType(QuestionDef.QTN_TYPE_GPS);
}
else if(type.equalsIgnoreCase("barcode"))
def.setDataType(QuestionDef.QTN_TYPE_BARCODE);
else if(type.toLowerCase().contains("1select")){
def.setDataType(QuestionDef.QTN_TYPE_LIST_EXCLUSIVE);
}else if(type.toLowerCase().contains("select")){
def.setDataType(QuestionDef.QTN_TYPE_LIST_MULTIPLE);
}
}
else
def.setDataType(QuestionDef.QTN_TYPE_TEXT); //QTN_TYPE_REPEAT
}
/**
* Goes through a given map of constraints attribute vaues and replaces the question
* whose variable name matches with a given question.
*
* @param constraints a map of contraints attribute values keyed by their question
* definition objects.
* @param questionDef the question definition object to replace that in the constraint map.
*/
public static void replaceConstraintQtn(HashMap constraints, QuestionDef questionDef){
Iterator keys = constraints.keySet().iterator();
while(keys.hasNext()){
QuestionDef qtn = (QuestionDef)keys.next();
if(qtn.getQuestionID().equals(questionDef.getQuestionID())){
String constraint = (String)constraints.get(qtn);
if(constraint != null){
constraints.remove(qtn);
constraints.put(questionDef, constraint);
}
return;
}
}
}
/**
* Gets the skip rule action for a question.
*
* @param qtn the question definition object.
* @return the skip rule action which can be (ModelConstants.ACTION_DISABLE,
* ModelConstants.ACTION_HIDE,ModelConstants.ACTION_SHOW or ModelConstants.ACTION_ENABLE)
*/
public static int getAction(QuestionDef qtn){
Element node = qtn.getBindNode();
if(node == null)
return ModelConstants.ACTION_ENABLE;
String value = node.getAttribute(XformConstants.ATTRIBUTE_NAME_ACTION);
if(value == null)
return ModelConstants.ACTION_ENABLE;
int action = 0;
if(value.equalsIgnoreCase(XformConstants.ATTRIBUTE_VALUE_ENABLE))
action |= ModelConstants.ACTION_ENABLE;
else if(value.equalsIgnoreCase(XformConstants.ATTRIBUTE_VALUE_DISABLE))
action |= ModelConstants.ACTION_DISABLE;
else if(value.equalsIgnoreCase(XformConstants.ATTRIBUTE_VALUE_SHOW))
action |= ModelConstants.ACTION_SHOW;
else if(value.equalsIgnoreCase(XformConstants.ATTRIBUTE_VALUE_HIDE))
action |= ModelConstants.ACTION_HIDE;
value = node.getAttribute(XformConstants.ATTRIBUTE_NAME_REQUIRED);
if(XformConstants.XPATH_VALUE_TRUE.equalsIgnoreCase(value))
action |= ModelConstants.ACTION_MAKE_MANDATORY;
else
action |= ModelConstants.ACTION_MAKE_OPTIONAL;
return action;
}
/**
* Gets the operator constant used to combine conditions in an expath expression.
* This will either be and AND or OR
* For now, we do not allow a mixture of these operators in the same expression.
* But we allow more than one as long as it is of the same type, either AND or OR.
*
* @param expression the xpath expression.
* @return the operator constant.
*/
public static int getConditionsOperator(String expression){
if(expression.toLowerCase().indexOf(XformConstants.CONDITIONS_OPERATOR_TEXT_AND) > 0)
return ModelConstants.CONDITIONS_OPERATOR_AND;
else if(expression.toLowerCase().indexOf(XformConstants.CONDITIONS_OPERATOR_TEXT_OR) > 0)
return ModelConstants.CONDITIONS_OPERATOR_OR;
return ModelConstants.CONDITIONS_OPERATOR_NULL;
}
}