package org.openrosa.client.xforms;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import org.openrosa.client.model.Condition;
import org.openrosa.client.model.FormDef;
import org.openrosa.client.model.QuestionDef;
import org.openrosa.client.model.ValidationRule;
import org.openrosa.client.model.ModelConstants;
import org.openrosa.client.xforms.XformConstants;
import com.google.gwt.xml.client.Element;
/**
* Parses constraint attributes of xforms documents and builds the validation
* rule objects of the model.
*
* @author daniel
*
*/
public class ConstraintParser {
/**
* All methods in this class are static and hence we expect no external
* Instantiation of this class.
*/
private ConstraintParser(){
}
/**
* Builds validation rule object from a list of constraint attribute values.
*
* @param formDef the form defintion object to which the validation rules belong.
* @param constraints the map of constraint attribute values keyed by their
* question definition objects.
*/
public static void addValidationRules(FormDef formDef, HashMap constraints){
Vector rules = new Vector();
Iterator keys = constraints.keySet().iterator();
//int id = 0;
while(keys.hasNext()){
QuestionDef qtn = (QuestionDef)keys.next();
String valRuleString = ((String)constraints.get(qtn)).replace(">", ">").replace("<", "<");
boolean hasValidationRule = valRuleString != null && !valRuleString.isEmpty();
ValidationRule validationRule = buildValidationRule(formDef, qtn.getId(),(String)constraints.get(qtn));
if(validationRule != null)
rules.add(validationRule);
boolean similar = isConstraintEquivalent(valRuleString, validationRule, formDef);
if(similar){
qtn.setHasAdvancedConstraint(false);
}else{
qtn.setHasAdvancedConstraint(true);
qtn.setAdvancedConstraint(valRuleString);
}
}
formDef.setValidationRules(rules);
}
public static boolean isConstraintEquivalent(String valString, ValidationRule valRule, FormDef formDef){
String oldString = valString.trim();
oldString = XformUtil.ripOutWhitespace(oldString);
String newString = ConstraintBuilder.fromValidationRule2String(valRule, formDef);
newString = XformUtil.ripOutWhitespace(newString);
if(newString.toLowerCase().equals(oldString.toLowerCase())){
return true;
}else{
return false;
}
}
/**
* Creates a validation rule object from a constraint attribute value.
*
* @param formDef the form definition object to build the validation rule for.
* @param questionId the identifier of the question which is the target of the validation rule.
* @param constraint the constraint attribute value.
* @return the validation rule object.
*/
private static ValidationRule buildValidationRule(FormDef formDef, int questionId, String constraint){
ValidationRule validationRule = new ValidationRule(questionId,formDef);
validationRule.setConditions(getValidationRuleConditions(formDef,constraint,questionId));
validationRule.setConditionsOperator(XformParserUtil.getConditionsOperator(constraint));
QuestionDef questionDef = formDef.getQuestion(questionId);
if(questionDef == null){
return validationRule;
}
Element node = questionDef.getBindNode();
if(node == null)
validationRule.setErrorMessage("");
else
validationRule.setErrorMessage(node.getAttribute(XformConstants.ATTRIBUTE_NAME_CONSTRAINT_MESSAGE));
// If the validation rule has no conditions, then its as good as no rule at all.
// if(validationRule.getConditions() == null || validationRule.getConditions().size() == 0)
// return null;
return validationRule;
}
/**
* Creates a skip rule object from a relevent attribute value.
*
* @param formDef the form definition object to build the skip rule for.
* @param questionId the identifier of the question which is the target of the skip rule.
* @param constraint the relevant attribute value.
* @param id the identifier for the skip rule.
* @param action the skip rule action to apply to the above target question.
* @return the skip rule object.
*/
public static ValidationRule buildValidationRule(FormDef formDef, int questionId, String constraint, int id, int action){
ValidationRule validationRule = new ValidationRule(formDef);
validationRule.setQuestionId(id);
//TODO For now we are only dealing with enabling and disabling.
validationRule.setConditionsOperator(action);
validationRule.setConditions(getValidationRuleConditions(formDef,constraint,action));
validationRule.setConditionsOperator(XformParserUtil.getConditionsOperator(constraint));
// If skip rule has no conditions, then its as good as no skip rule at all.
if(validationRule.getConditions() == null || validationRule.getConditions().size() == 0){
return null;
}
return validationRule;
}
/**
* Gets a list of conditions for a validation rule as per the constraint attribute value.
*
* @param formDef the form definition object to which the validation rule belongs.
* @param constraint the relevant attribute value.
* @param questionId the identifier of the question which is the target of the validation rule.
* @return the conditions list.
*/
private static Vector getValidationRuleConditions(FormDef formDef, String constraint, int questionId){
Vector conditions = new Vector();
Vector list = XpathParser.getConditionsOperatorTokens(constraint);
Condition condition = new Condition();
for(int i=0; i<list.size(); i++){
condition = getValidationRuleCondition(formDef,(String)list.elementAt(i),questionId);
if(condition != null)
conditions.add(condition);
}
return conditions;
}
/**
* Creates a validation rule condition object from a portion of the constraint attribute value.
*
* @param formDef the form definition object to which the validation rule belongs.
* @param constraint the token or portion from the constraint attribute value.
* @param questionId the identifier of the question that has the validation rule.
* @return the new condition object.
*/
private static Condition getValidationRuleCondition(FormDef formDef, String constraint, int questionId){
Condition condition = new Condition();
condition.setId(questionId);
condition.setOperator(XformParserUtil.getOperator(constraint,ModelConstants.ACTION_ENABLE));
condition.setQuestionId(questionId);
//eg . <= 40"
int pos = XformParserUtil.getOperatorPos(constraint);
if(pos < 0)
return null;
QuestionDef questionDef = formDef.getQuestion(questionId);
if(questionDef == null)
return null;
String value;
//first try a value delimited by '
int pos2 = constraint.lastIndexOf('\'');
if(pos2 > 0){
//pos1++;
int pos1 = constraint.substring(0, pos2).lastIndexOf('\'',pos2);
if(pos1 < 0){
System.out.println("constraint value not closed with ' characher");
return null;
}
pos1++;
value = constraint.substring(pos1,pos2);
}
else //else we take whole value after operator
value = constraint.substring(pos+XformParserUtil.getOperatorSize(condition.getOperator(),ModelConstants.ACTION_ENABLE),constraint.length());
value = value.trim();
if(!(value.equals("null") || value.equals(""))){
condition.setValue(value);
//This is just for the designer
if(value.startsWith(formDef.getQuestionID() + "/")){
condition.setValueQtnDef((QuestionDef)formDef.getElement(value.substring(value.indexOf('/')+1)));
}
if(condition.getOperator() == ModelConstants.OPERATOR_NULL){
return null; //no operator set hence making the condition invalid
}
}
else
condition.setOperator(ModelConstants.OPERATOR_IS_NULL);
if(constraint.contains("length(.)") || constraint.contains("count(.)"))
condition.setFunction(ModelConstants.FUNCTION_LENGTH);
return condition;
}
}