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.IFormElement;
import org.openrosa.client.model.QuestionDef;
import org.openrosa.client.model.SkipRule;
import org.openrosa.client.model.ModelConstants;
import org.openrosa.client.model.ValidationRule;
/**
* Parses relevant attributes of xforms documents and builds the skip rule objects of the model.
*
* @author daniel
*
*/
public class RelevantParser {
/**
* All methods in this class are static and hence we expect no external
* Instantiation of this class.
*/
private RelevantParser(){
}
/**
* Builds skip rule object from a list of relevant attribute values.
*
* @param formDef the form defintion object to which the skip rules belong.
* @param relevants the map of relevant attribute values keyed by their
* question definition objects.
*/
public static void addSkipRules(FormDef formDef, HashMap relevants){
Vector rules = new Vector();
Iterator keys = relevants.keySet().iterator();
int id = 0;
while(keys.hasNext()){
QuestionDef qtn = (QuestionDef)keys.next();
String relevant = ((String)relevants.get(qtn)).replace(">", ">").replace("<", "<");
//If there is a skip rule with the same relevant as the current
//then just add this question as another action target to the skip
//rule instead of creating a new skip rule.
SkipRule skipRule = null;
boolean hasRelevant = (relevant != null && !relevant.isEmpty());
skipRule = buildSkipRule(formDef, qtn.getId(),relevant,++id,XformParserUtil.getAction(qtn));
if(skipRule != null){
rules.add(skipRule);
}
boolean similar = isRelevantsEquivalent(relevant, skipRule, formDef);
if(similar){
qtn.setHasAdvancedRelevant(false);
}else{
qtn.setHasAdvancedRelevant(true);
if(hasRelevant){
qtn.setAdvancedRelevant(relevant);
}
}
}
formDef.setSkipRules(rules);
}
/**
* 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 relevant 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 SkipRule buildSkipRule(FormDef formDef, int questionId, String relevant, int id, int action){
SkipRule skipRule = new SkipRule();
skipRule.setId(id);
//TODO For now we are only dealing with enabling and disabling.
skipRule.setAction(action);
skipRule.setConditions(getConditions(formDef,relevant,action));
skipRule.setConditionsOperator(XformParserUtil.getConditionsOperator(relevant));
//For now we only have one action target, much as the object model is
//flexible enough to support any number of them.
Vector actionTargets = new Vector();
actionTargets.add(new Integer(questionId));
skipRule.setActionTargets(actionTargets);
// If skip rule has no conditions, then its as good as no skip rule at all.
if(skipRule.getConditions() == null || skipRule.getConditions().size() == 0){
return null;
}
return skipRule;
}
/**
* Compares the given Relevant attribute (origRel) to the
* one put through the skiprule parse. If they're pretty
* much equivalent, return True
* @param origRel
* @param newRel
* @return true if args are equivalent (for loose values of equivalent), else false.
*/
public static boolean isRelevantsEquivalent(String origRel, SkipRule skipRule, FormDef formDef){
String oldString = origRel.trim();
oldString = XformUtil.ripOutWhitespace(oldString);
String newString = RelevantBuilder.fromSkipRule2String(skipRule, formDef);
newString = XformUtil.ripOutWhitespace(newString);
if(newString.toLowerCase().equals(oldString.toLowerCase())){
return true;
}else{
return false;
}
}
/**
* Gets a list of conditions for a skip rule as per the relevant attribute value.
*
* @param formDef the form definition object to which the skip rule belongs.
* @param relevant the relevant attribute value.
* @param action the skip rule target action.
* @return the conditions list.
*/
private static Vector getConditions(FormDef formDef, String relevant, int action){
Vector conditions = new Vector();
Vector list = XpathParser.getConditionsOperatorTokens(relevant);
Condition condition = new Condition();
for(int i=0; i<list.size(); i++){
condition = getCondition(formDef,(String)list.elementAt(i),(int)(i+1),action);
if(condition != null)
conditions.add(condition);
}
return conditions;
}
/**
* Creates a skip rule condition object from a portion of the relevant attribute value.
*
* @param formDef the form definition object to which the skip rule belongs.
* @param relevant the token or portion from the relevant attribute value.
* @param id the new condition identifier.
* @param action the skip rule target action.
* @return the new condition object.
*/
private static Condition getCondition(FormDef formDef, String relevant, int id, int action){
Condition condition = new Condition();
condition.setId(id);
condition.setOperator(XformParserUtil.getOperator(relevant,action));
//eg relevant="/data/question10='7'"
int pos = XformParserUtil.getOperatorPos(relevant);
if(pos < 0)
return null;
String varName = relevant.substring(0, pos);
IFormElement questionDef = formDef.getElement(varName.trim());
if(questionDef == null){
String prefix = "/" + formDef.getQuestionID() + "/";
if(varName.startsWith(prefix))
questionDef = formDef.getElement(varName.trim().substring(prefix.length(), varName.trim().length()));
if(questionDef == null)
return null;
}
condition.setQuestionId(questionDef.getId());
String value;
//first try a value delimited by '
int pos2 = relevant.lastIndexOf('\'');
if(pos2 > 0){
//pos1++;
int pos1 = relevant.substring(0, pos2).lastIndexOf('\'',pos2);
if(pos1 < 0){
System.out.println("Relevant value not closed with ' characher");
return null;
}
pos1++;
value = relevant.substring(pos1,pos2);
}
else //else we take whole value after operator
value = relevant.substring(pos+XformParserUtil.getOperatorSize(condition.getOperator(),action),relevant.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);
return condition;
}
}