package org.openrosa.client.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.Map.Entry;
import org.openrosa.client.OpenRosaConstants;
import org.openrosa.client.model.ModelConstants;
import org.openrosa.client.util.FormUtil;
import org.openrosa.client.util.Itext;
import org.openrosa.client.xforms.XformConstants;
import org.openrosa.client.xforms.XformUtil;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.XMLParser;
/**
* Definition of a form. This has some meta data about the form definition and
* a collection of pages together with question branching or skipping rules.
* A form is sent as defined in one language. For instance, those using
* Swahili would get forms in that language, etc. We don't support runtime
* changing of a form language in order to have a more efficient implementation
* as a trade off for more flexibility which may not be used most of the times.
*
* @author Daniel Kayiwa
*
*/
public class FormDef implements IFormElement, Serializable{
//TODO May not need to serialize this property for smaller pay load. Then we may just rely on the id.
//afterall it is not even guaranteed to be unique.
/** The string unique identifier of the form definition. */
private String questionID = ModelConstants.EMPTY_STRING;
/** The display name of the form. */
private String name = ModelConstants.EMPTY_STRING;
private String formKey = ModelConstants.EMPTY_STRING;
/** The numeric unique identifier of the form definition. */
private int id = ModelConstants.NULL_ID;
/** The collection of rules (SkipRule objects) for this form. */
private Vector skipRules;
/** The collection of rules (ValidationRule objects) for this form. */
private Vector validationRules;
/** The collection of calculations (Calculation objects) for this form. */
private Vector calculations;
/** A string consisting for form fields that describe its data. eg description-template="${/data/question1}$ Market" */
private String descriptionTemplate = ModelConstants.EMPTY_STRING;
/** A mapping of dynamic lists keyed by the id of the question whose values
* determine possible values of another question as specified in the DynamicOptionDef object.
*/
private HashMap<Integer,DynamicOptionDef> dynamicOptions;
/** The xforms document.(for easy syncing between the object model and actual xforms document. */
private Document doc;
/**
* The data node of the xform that this form represents.
* This is the node immediately under the instace node.
*/
private Element dataNode;
/** The top level node of the xform that this form represents. */
private Element xformsNode;
/** The model node of the xform that this form represents. */
private Element modelNode;
/** The body node. */
private Element bodyNode;
/** The layout xml for this form. */
private String layoutXml;
/** The javascript source for this form. */
private String javaScriptSource;
/** The xforms xml for this form. */
private String xformXml;
/** The language xml for this form. */
private String languageXml;
/**
* Flag to determine if we can change the form structure.
* For a read only form, we can only change the Text and Help Text.
*
*/
private boolean readOnly = false;
private String itextId;
public static final int INSERT_BEFORE = 0;
public static final int INSERT_AFTER = 1;
List<IFormElement> children;
/** Constructs a form definition object. */
public FormDef() {
}
/**
* Creates a new copy of the form from an existing one.
*
* @param formDef the form to copy from.
*/
public FormDef(FormDef formDef) {
this(formDef,true);
}
/**
* Creates a new copy of the form from an existing one, with a flag which
* tells whether we should copy the validation rules too.
*
* @param formDef the form to copy from.
* @param copyValidationRules set to true if you also want to copy the validation rules, else false.
*/
public FormDef(FormDef formDef, boolean copyValidationRules) {
setId(formDef.getId());
setName(formDef.getName());
setFormKey(formDef.getFormKey());
//I just don't think we need this in addition to the id
setVariableName(formDef.getVariableName());
setDescriptionTemplate(formDef.getDescriptionTemplate());
copyChildren(formDef.getChildren());
copySkipRules(formDef.getSkipRules());
copyCalculations(formDef.getCalculations());
//This is a temporary fix for an infinite recursion that happens when validation
//rule copy constructor tries to set a formdef using the FormDef copy constructor.
if(copyValidationRules)
copyValidationRules(formDef.getValidationRules());
copyDynamicOptions(formDef.getDynamicOptions());
}
/**
* Constructs a form definition object from these parameters.
*
* @param name - the numeric unique identifier of the form definition.
* @param name - the display name of the form.
* @param variableName - the string unique identifier of the form definition.
* @param pages - collection of page definitions.
* @param rules - collection of branching rules.
*/
public FormDef(int id, String name, String formKey, String variableName,List<IFormElement> children, Vector skipRules, Vector validationRules, HashMap<Integer,DynamicOptionDef> dynamicOptions, String descTemplate, Vector calculations) {
setId(id);
setName(name);
setFormKey(formKey);
//I just don't think we need this in addition to the id
setVariableName(variableName);
setChildren(children);
setSkipRules(skipRules);
setValidationRules(validationRules);
setDynamicOptions(dynamicOptions);
setDescriptionTemplate((descTemplate == null) ? ModelConstants.EMPTY_STRING : descTemplate);
setCalculations(calculations);
}
public List<String> getAllChildrenItextIDs(){
ArrayList<String> list = new ArrayList<String>();
if(children == null){ return null; }
//add self ItextID(s) for this edge case
list.addAll(Itext.getFullAvailableTextForms(this.getItextId()));
for(IFormElement child : children){
list.addAll(Itext.getFullAvailableTextForms(child.getItextId()));
list.addAll(child.getAllChildrenItextIDs()); //recurse down to children
}
return list;
}
public SkipRule getSkipRuleAt(int index) {
if(skipRules == null)
return null;
return (SkipRule)skipRules.elementAt(index);
}
public ValidationRule getValidationRuleAt(int index) {
if(validationRules == null)
return null;
return (ValidationRule)validationRules.elementAt(index);
}
public Calculation getCalculationAt(int index) {
if(calculations == null)
return null;
return (Calculation)calculations.elementAt(index);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFormKey() {
return formKey;
}
public void setFormKey(String formKey) {
this.formKey = formKey;
}
//I just don't think we need this in addition to the id
private String getVariableName() {
return questionID;
}
private void setVariableName(String variableName) {
this.questionID = variableName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getItextId() {
return itextId;
}
public void setItextId(String itextId) {
this.itextId = itextId;
}
public Vector getSkipRules() {
return skipRules;
}
public void setSkipRules(Vector skipRules) {
this.skipRules = skipRules;
}
public Vector getValidationRules() {
return validationRules;
}
public void setValidationRules(Vector validationRules) {
this.validationRules = validationRules;
}
public Vector getCalculations() {
return calculations;
}
public void setCalculations(Vector calculations) {
this.calculations = calculations;
}
public HashMap<Integer,DynamicOptionDef> getDynamicOptions() {
return dynamicOptions;
}
public void setDynamicOptions(HashMap<Integer,DynamicOptionDef> dynamicOptions) {
this.dynamicOptions = dynamicOptions;
}
public String getDescriptionTemplate() {
return descriptionTemplate;
}
public void setDescriptionTemplate(String descriptionTemplate) {
this.descriptionTemplate = descriptionTemplate;
}
public String getJavaScriptSource() {
return javaScriptSource;
}
public void setJavaScriptSource(String javaScriptSource) {
this.javaScriptSource = javaScriptSource;
}
public String getLayoutXml() {
return layoutXml;
}
public void setLayoutXml(String layout) {
this.layoutXml = layout;
}
public String getLanguageXml() {
return languageXml;
}
public void setLanguageXml(String languageXml) {
this.languageXml = languageXml;
}
public String getXformXml() {
return xformXml;
}
public void setXformXml(String xform) {
this.xformXml = xform;
}
public boolean isReadOnly() {
return readOnly;
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Gets the first skip rule which has a given question as one of its targets.
*
* @param questionDef the question.
* @return the skip rule.
*/
public SkipRule getSkipRule(IFormElement questionDef){
if(skipRules == null)
return null;
for(int i=0; i<skipRules.size(); i++){
SkipRule rule = (SkipRule)skipRules.elementAt(i);
Vector targets = rule.getActionTargets();
for(int j=0; j<targets.size(); j++){
if(((Integer)targets.elementAt(j))
.intValue() ==
questionDef.getId())
return rule;
}
}
return null;
}
public Calculation getCalculation(IFormElement questionDef){
if(calculations == null){
return null;
}
for(int i=0; i<calculations.size(); i++){
Calculation calculation = (Calculation)calculations.elementAt(i);
if(calculation.getQuestionId() == questionDef.getId())
return calculation;
}
return null;
}
/**
* Gets the validation rule for a given question.
*
* @param questionDef the question.
* @return the validation rule.
*/
public ValidationRule getValidationRule(IFormElement questionDef){
if(validationRules == null)
return null;
for(int i=0; i<validationRules.size(); i++){
ValidationRule rule = (ValidationRule)validationRules.elementAt(i);
if(questionDef.getId() == rule.getQuestionId())
return rule;
}
return null;
}
public FormDef getParentFormDef(){
return this;
}
/**
* Updates the xforms document with the current changes in the form.
*
* @param withData set to true if you want question answers to also be saved as part of the xform.
*/
public void updateDoc(boolean withData){
dataNode.setAttribute(XformConstants.ATTRIBUTE_NAME_NAME, name);
String sid = dataNode.getAttribute(XformConstants.ATTRIBUTE_NAME_ID);
String dataNodeName = dataNode.getNodeName();
if(!dataNodeName.equalsIgnoreCase(questionID)){
dataNode = XformUtil.renameNode(dataNode,questionID);
updateDataNodes();
}
if(dataNode != null){
if(descriptionTemplate == null || descriptionTemplate.trim().length() == 0)
dataNode.removeAttribute(XformConstants.ATTRIBUTE_NAME_DESCRIPTION_TEMPLATE);
}
if(children != null){
if(bodyNode == null && children.size() > 0){
bodyNode = (Element)children.get(0).getControlNode().getParentNode();
}
for(int i=0; i<children.size(); i++){
IFormElement element = children.get(i);
if(element instanceof GroupDef)
((GroupDef)element).updateDoc(doc,bodyNode,this,dataNode,modelNode,withData,dataNodeName);
else
((QuestionDef)element).updateDoc(doc,xformsNode,this,dataNode,modelNode,bodyNode,true,withData, dataNodeName);;
}
}
if(skipRules != null){
for(int i=0; i<skipRules.size(); i++){
SkipRule skipRule = (SkipRule)skipRules.elementAt(i);
skipRule.updateDoc(this, this.getDoc());
}
}
if(validationRules != null){
for(int i=0; i<validationRules.size(); i++){
ValidationRule validationRule = (ValidationRule)validationRules.elementAt(i);
validationRule.updateDoc(this);
}
}
if(dynamicOptions != null){
Iterator<Entry<Integer,DynamicOptionDef>> iterator = dynamicOptions.entrySet().iterator();
while(iterator.hasNext()){
Entry<Integer,DynamicOptionDef> entry = iterator.next();
DynamicOptionDef dynamicOptionDef = entry.getValue();
QuestionDef questionDef = (QuestionDef)getElement(entry.getKey());
if(questionDef == null)
continue;
dynamicOptionDef.updateDoc(this,questionDef);
}
}
if(calculations != null){
for(int i=0; i<calculations.size(); i++){
Calculation calculation = (Calculation)calculations.elementAt(i);
if(getElement(calculation.getQuestionId()) == null) //possibly question deleted
calculations.remove(i);
else
calculation.updateDoc(this);
}
}
}
private void updateDataNodes(){
if(children == null)
return;
for(int i=0; i<children.size(); i++)
((IFormElement)children.get(i)).updateDataNodes(dataNode);
}
public String toString() {
return getName();
}
// /**
// * Gets a question identified by a variable name.
// *
// * @param varName - the string identifier of the question.
// * @return the question reference.
// */
// public IFormElement getElement(String varName){
// if(varName == null || children == null)
// return null;
//
// for(int i=0; i<children.size(); i++){
// IFormElement def = children.get(i);
// if(varName.equals(def.getBinding()))
// return def;
//
// if(def instanceof GroupDef){
// def = ((GroupDef)def).getElement(varName);
// if(def != null)
// return def;
// }
// }
//
// return null;
// }
/**
* Returns the element specified by varName.
* if varName matches the parent node, return self,
* else go through children elements and return a match
* If no match is found, return null.
* @param varName
* @return the IFormElement that matches varName or null if no match.
*/
public IFormElement getElement(String varName){
if(varName == null || children == null){
return null;
}
IFormElement retElement;
for(int i=0;i<children.size();i++){
IFormElement def = children.get(i);
retElement = def.getElement(varName);
if(retElement != null){
return retElement; //there should only ever be one match.
}
}
return null;
}
public QuestionDef getQuestion(String varName){
return (QuestionDef)getElement(varName);
}
public QuestionDef getQuestion(int id){
return (QuestionDef)getElement(id);
}
/**
* Gets a question identified by an id
*
* @param id - the numeric identifier of the question.
* @return the question reference.
*/
public IFormElement getElement(int id){
if(children == null)
return null;
for(int i=0; i<children.size(); i++){
IFormElement def = children.get(i);
if(id == def.getId())
return def;
if(def instanceof GroupDef){
def = ((GroupDef)def).getElement(id);
if(def != null)
return def;
}
}
return null;
}
/**
* Gets a numeric question identifier for a given question variable name.
*
* @param varName - the string identifier of the question.
* @return the numeric question identifier.
*/
public int getQuestionId(String varName){
IFormElement qtn = getElement(varName);
if(qtn != null)
return qtn.getId();
return ModelConstants.NULL_ID;
}
// /**
// * Adds a new question to the form.
// *
// * @param qtn the new question to add.
// */
// public void addElement(IFormElement qtn){
// if(children == null)
// children = new ArrayList<IFormElement>();
//
// children.add(qtn);
// qtn.setParent(this);
// }
/**
* Copies a given list of pages into this form.
*
* @param pages the pages to copy.
*/
private void copyChildren(List<IFormElement> children){
if(children != null){
this.children = new ArrayList<IFormElement>();
for(int i=0; i<children.size(); i++) //Should have atleast one page is why we are not checking for nulls.
this.children.add(children.get(i).copy(this));
}
}
/**
* Copies a given list of skip rules into this form.
*
* @param rules the skip rules.
*/
private void copySkipRules(Vector rules){
if(rules != null)
{
this.skipRules = new Vector();
for(int i=0; i<rules.size(); i++)
this.skipRules.addElement(new SkipRule((SkipRule)rules.elementAt(i)));
}
}
/**
* Copies a given list of validation rules into this form.
*
* @param rules the validation rules.
*/
private void copyValidationRules(Vector rules){
if(rules != null)
{
this.validationRules = new Vector();
for(int i=0; i<rules.size(); i++)
this.validationRules.addElement(new ValidationRule((ValidationRule)rules.elementAt(i)));
}
}
private void copyDynamicOptions(HashMap<Integer,DynamicOptionDef> options){
if(options != null)
{
dynamicOptions = new HashMap<Integer,DynamicOptionDef>();
Iterator<Entry<Integer,DynamicOptionDef>> iterator = options.entrySet().iterator();
while(iterator.hasNext()){
Entry<Integer,DynamicOptionDef> entry = iterator.next();
DynamicOptionDef dynamicOptionDef = entry.getValue();
QuestionDef questionDef = (QuestionDef)getElement(dynamicOptionDef.getQuestionId());
if(questionDef == null)
return;
dynamicOptions.put(new Integer(entry.getKey()), new DynamicOptionDef(dynamicOptionDef,questionDef));
}
}
}
private void copyCalculations(Vector calculations){
if(calculations != null)
{
this.calculations = new Vector();
for(int i=0; i<calculations.size(); i++)
this.calculations.addElement(new Calculation((Calculation)calculations.elementAt(i)));
}
}
/*private void copyDynamicOptions(HashMap<Integer,DynamicOptionDef>){
}*/
/**
* Removes a page from the form.
*
* @param pageFef the page to remove.
*/
public boolean removeChild(IFormElement element){
if(element instanceof GroupDef){
((GroupDef)element).removeAllElements(this);
if(((GroupDef)element).getGroupNode() != null){
((GroupDef)element).getGroupNode().getParentNode().removeChild(((GroupDef)element).getGroupNode());
}
}
else
GroupDef.removeElement2((QuestionDef)element, this, true);
return children.remove(element);
}
/**
* Sets the xforms document represented by this form.
* @param doc
*/
public void setDoc(Document doc){
this.doc = doc;
}
/**
* Gets the xforms document represented by this form.
* @return
*/
public Document getDoc(){
return doc;
}
/**
* @return the dataNode
*/
public Element getDataNode() {
return dataNode;
}
/**
* @param dataNode the dataNode to set
*/
public void setDataNode(Element dataNode) {
this.dataNode = dataNode;
}
/**
* @return the xformsNode
*/
public Element getXformsNode() {
return xformsNode;
}
/**
* @param xformsNode the xformsNode to set
*/
public void setXformsNode(Element xformsNode) {
this.xformsNode = xformsNode;
}
/**
* @return the modelNode
*/
public Element getModelNode() {
return modelNode;
}
/**
* @param modelNode the modelNode to set
*/
public void setModelNode(Element modelNode) {
this.modelNode = modelNode;
if(modelNode != null){
String prefix = modelNode.getPrefix();
if(prefix != null && prefix.trim().length() > 0)
XformConstants.updatePrefixConstants(prefix);
}
}
public Element getBodyNode() {
return bodyNode;
}
public void setBodyNode(Element bodyNode) {
this.bodyNode = bodyNode;
}
/**
* Moves a page one position up in the form.
*
* @param pageDef the page to move.
*/
/*public void movePageUp(IFormElement element){
int index = children.indexOf(element);
children.remove(element);
Node parentNode = element.getControlNode().getParentNode();
if(element.getControlNode() != null)
parentNode.removeChild(element.getControlNode()); //xformsNode.removeChild(element.getControlNode());
IFormElement currentElement;
List<IFormElement> list = new ArrayList<IFormElement>();
while(children.size() >= index){
currentElement = children.get(index-1);
list.add(currentElement);
children.remove(currentElement);
}
children.add(element);
for(int i=0; i<list.size(); i++){
if(i == 0){
IFormElement elem = list.get(i);
if(elem.getControlNode() != null)
parentNode.insertBefore(element.getControlNode(), elem.getControlNode()); //xformsNode.insertBefore(element.getControlNode(), elem.getControlNode());
}
children.add(list.get(i));
}
}*/
/**
* Moves a page one position down in the form.
*
* @param pageDef the page to move.
*/
/*public void movePageDown(IFormElement element){
int index = children.indexOf(element);
children.remove(element);
Node parentNode = element.getControlNode().getParentNode();
if(element.getControlNode() != null)
parentNode.removeChild(element.getControlNode()); //xformsNode.removeChild(element.getControlNode());
IFormElement currentItem; // = parent.getChild(index - 1);
List<IFormElement> list = new ArrayList<IFormElement>();
while(children.size() > 0 && children.size() > index){
currentItem = children.get(index);
list.add(currentItem);
children.remove(currentItem);
}
for(int i=0; i<list.size(); i++){
if(i == 1){
children.add(element); //Add after the first item.
IFormElement pgDef = list.get(i);
if(pgDef.getControlNode() != null)
parentNode.insertBefore(element.getControlNode(), pgDef.getControlNode()); //xformsNode.insertBefore(element.getControlNode(), pgDef.getGroupNode());
}
children.add(list.get(i));
}
if(list.size() == 1){
children.add(element);
if(element.getControlNode() != null)
parentNode.appendChild(element.getControlNode()); //xformsNode.appendChild(element.getControlNode());
}
}*/
/**
* Removes a question from the form.
*
* @param qtnDef the question to remove.
* @return true if the question has been found and removed, else false.
*/
public boolean removeQuestion(IFormElement qtnDef, boolean delete){
for(int i=0; i<children.size(); i++){
IFormElement element = children.get(i);
if(element == qtnDef){
children.remove(qtnDef);
return true;
}
else if(element instanceof GroupDef){
if(((GroupDef)element).removeElement(element, this, delete))
return true;
}
}
return false;
}
/**
* Removes a question for the validation rules list.
*
* @param questionDef the question to remove.
*/
private void removeQtnFromValidationRules(IFormElement questionDef){
for(int index = 0; index < this.getValidationRuleCount(); index++){
ValidationRule validationRule = getValidationRuleAt(index);
validationRule.removeQuestion(questionDef);
if(validationRule.getConditionCount() == 0){
removeValidationRule(validationRule);
index++;
}
}
}
/**
* Removes a question from skip rules list.
*
* @param questionDef the question to remove.
*/
private void removeQtnFromSkipRules(IFormElement questionDef){
for(int index = 0; index < getSkipRuleCount(); index++){
SkipRule skipRule = getSkipRuleAt(index);
skipRule.removeQuestion(questionDef);
if(skipRule.getActionTargetCount() == 0 || skipRule.getConditionCount() == 0){
removeSkipRule(skipRule);
index++;
}
}
}
/**
* Removes a question from the validation rules which are referencing it.
*
* @param qtnDef the question to remove.
*/
public void removeQtnFromRules(IFormElement qtnDef){
removeQtnFromValidationRules(qtnDef);
removeQtnFromSkipRules(qtnDef);
}
/**
* Check if a question is referenced by any dynamic selection list relationship
* and if so, removes the relationship.
*
* @param questionDef the question to check.
*/
public void removeQtnFromDynamicLists(IFormElement questionDef){
if(!(questionDef instanceof QuestionDef))
return; //only QuestionDefs can be referenced by DynamicOptionDef s
if(dynamicOptions != null){
Object[] keys = dynamicOptions.keySet().toArray();
for(int index = 0; index < keys.length; index++){
Integer questionId = (Integer)keys[index];
DynamicOptionDef dynamicOptionDef = dynamicOptions.get(questionId);
//Check if the deleted question is the parent of a dynamic selection
//list relationship. And if so, delete the relationship.
if(questionId.intValue() == questionDef.getId()){
dynamicOptions.remove(questionId);
removeDynamicInstanceNode(dynamicOptionDef);
continue;
}
//Check if the deleted question is the child of a dynamic selection
//list relationship. And if so, delete the relationship.
if(dynamicOptionDef.getQuestionId() == questionDef.getId()){
dynamicOptions.remove(questionId);
removeDynamicInstanceNode(dynamicOptionDef);
continue;
}
dynamicOptionDef.updateDoc(this,(QuestionDef)questionDef);
}
}
}
public void removeQtnFromCalculations(QuestionDef questionDef){
for(int index = 0; index < getCalculationCount(); index++){
Calculation calculation = getCalculationAt(index);
if(calculation.getQuestionId() == questionDef.getId()){
calculations.remove(index);
Element node = questionDef.getBindNode() != null ? questionDef.getBindNode() : questionDef.getControlNode();
if(questionDef.getBindNode() != null)
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_CALCULATE);
return;
}
}
}
public void updateCalculation(QuestionDef questionDef, String calculateExpression){
if(calculateExpression == null || calculateExpression.trim().length() == 0)
removeQtnFromCalculations(questionDef);
else{
Calculation calculation = getCalculation(questionDef);
if(calculation == null)
addCalculation(new Calculation(questionDef.getId(),calculateExpression));
else
calculation.setCalculateExpression(calculateExpression);
}
}
/**
* Removes the instance node referenced by a dynamic selection list object.
*
* @param dynamicOptionDef the dynamic selection list object.
*/
private static void removeDynamicInstanceNode(DynamicOptionDef dynamicOptionDef){
//dataNode points to <dynamiclist>
//dataNode.getParentNode() points to <xf:instance id="theid">
//dataNode.getParentNode().getParentNode() points to <xf:model>
Element dataNode = dynamicOptionDef.getDataNode();
if(dataNode != null && dataNode.getParentNode() != null
&& dataNode.getParentNode().getParentNode() != null){
dataNode.getParentNode().getParentNode().removeChild(dataNode.getParentNode());
}
}
/**
* Gets the number of skip rules in the form.
*
* @return the number of skip rules.
*/
public int getSkipRuleCount(){
if(skipRules == null)
return 0;
return skipRules.size();
}
public int getCalculationCount(){
if(calculations == null)
return 0;
return calculations.size();
}
/**
* Gets the number of validation rules in the form.
*
* @return the number of validation rules.
*/
public int getValidationRuleCount(){
if(validationRules == null)
return 0;
return validationRules.size();
}
/**
* Gets questions with given display text.
*
* @param text the display text to look for.
* @return the question of found, else null.
*/
public IFormElement getQuestionWithText(String text){
for(int i=0; i<children.size(); i++){
IFormElement element = children.get(i);
if(text.equals(Itext.getDisplayText(element))){
return element;
}
if(element instanceof GroupDef){
element = ((GroupDef)element).getQuestionWithText(text);
if(element != null)
return element;
}
}
return null;
}
/**
* Checks if the form has a particular skip rule.
*
* @param skipRule the skip rule to check.
* @return true if the skip rule has been found, else false.
*/
public boolean containsSkipRule(SkipRule skipRule){
if(skipRules == null)
return false;
return skipRules.contains(skipRule);
}
/**
* Checks if a form has a particular validation rule.
*
* @param validationRule the validation rule to check.
* @return true if the validation rule has been found, else false.
*/
public boolean containsValidationRule(ValidationRule validationRule){
if(validationRules == null)
return false;
return validationRules.contains(validationRule);
}
/**
* Adds a new skip rule to the form.
*
* @param skipRule the new skip rule to add.
*/
public void addSkipRule(SkipRule skipRule){
if(skipRules == null)
skipRules = new Vector();
skipRules.addElement(skipRule);
}
/**
* Adds a new validation rule to the form.
*
* @param validationRule the new validation rule to add.
*/
public void addValidationRule(ValidationRule validationRule){
if(validationRules == null)
validationRules = new Vector();
validationRules.addElement(validationRule);
}
public void addCalculation(Calculation calculation){
if(calculations == null)
calculations = new Vector();
calculations.addElement(calculation);
}
/**
* Removes a skip rule from the form.
*
* @param skipRule the skip rule to remove.
* @return true if the skip rule has been found and removed, else false.
*/
public boolean removeSkipRule(SkipRule skipRule){
if(skipRules == null)
return false;
boolean ret = skipRules.remove(skipRule);
if(dataNode != null){
for(int index = 0; index < skipRule.getActionTargetCount(); index++){
IFormElement elementDef = getElement(skipRule.getActionTargetAt(index));
if(elementDef != null){
Element node = elementDef.getBindNode() != null ? elementDef.getBindNode() : elementDef.getControlNode();
if(node != null){
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_RELEVANT);
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_ACTION);
}
}
}
}
return ret;
}
/**
* Removes a validation rule from the form.
*
* @param validationRule the validation rule to remove.
* @return true if the validation rule has been found and removed.
*/
public boolean removeValidationRule(ValidationRule validationRule){
if(validationRules == null)
return false;
boolean ret = validationRules.remove(validationRule);
if(dataNode != null){
IFormElement questionDef = getElement(validationRule.getQuestionId());
if(questionDef != null){
Element node = questionDef.getBindNode() != null ? questionDef.getBindNode() : questionDef.getControlNode();
if(node != null){
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_CONSTRAINT);
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_CONSTRAINT_MESSAGE);
}
}
}
return ret;
}
public void setDynamicOptionDef(Integer questionId, DynamicOptionDef dynamicOptionDef){
//The parent or child question may have been deleted.
if(getElement(questionId) == null || getElement(dynamicOptionDef.getQuestionId()) == null)
return;
if(dynamicOptions == null)
dynamicOptions = new HashMap<Integer,DynamicOptionDef>();
dynamicOptions.put(questionId, dynamicOptionDef);
}
public DynamicOptionDef getDynamicOptions(Integer questionId){
if(dynamicOptions == null)
return null;
return dynamicOptions.get(questionId);
}
public DynamicOptionDef getChildDynamicOptions(Integer questionId){
if(dynamicOptions == null)
return null;
Iterator<Entry<Integer,DynamicOptionDef>> iterator = dynamicOptions.entrySet().iterator();
while(iterator.hasNext()){
Entry<Integer,DynamicOptionDef> entry = iterator.next();
DynamicOptionDef dynamicOptionDef = entry.getValue();
if(dynamicOptionDef.getQuestionId() == questionId)
return dynamicOptionDef;
}
return null;
}
public QuestionDef getDynamicOptionsParent(Integer questionId){
if(dynamicOptions == null)
return null;
Iterator<Entry<Integer,DynamicOptionDef>> iterator = dynamicOptions.entrySet().iterator();
while(iterator.hasNext()){
Entry<Integer,DynamicOptionDef> entry = iterator.next();
DynamicOptionDef dynamicOptionDef = entry.getValue();
if(dynamicOptionDef.getQuestionId() == questionId)
return (QuestionDef)getElement(entry.getKey());
}
return null;
}
public OptionDef getDynamicOptionDef(Integer questionId, int id){
if(dynamicOptions == null)
return null;
Iterator<Entry<Integer,DynamicOptionDef>> iterator = dynamicOptions.entrySet().iterator();
while(iterator.hasNext()){
Entry<Integer,DynamicOptionDef> entry = iterator.next();
DynamicOptionDef dynamicOptionDef = entry.getValue();
if(dynamicOptionDef.getQuestionId() == questionId)
return dynamicOptionDef.getOptionWithId(id);
}
return null;
}
/**
* Removes a dynamic selection list relationship referenced by a given question.
*
* @param questionId the question to check from dynamic selection lists.
*/
public void removeDynamicOptions(Integer questionId){
if(dynamicOptions != null){
DynamicOptionDef dynamciOptionDef = dynamicOptions.get(questionId);
if(dynamciOptionDef == null)
return;
removeDynamicInstanceNode(dynamciOptionDef);
dynamicOptions.remove(questionId);
}
}
/**
* Updates this formDef (as the main from the refresh source) with the parameter one
*
* @param formDef the old formDef to copy from.
*/
public void refresh(FormDef formDef){
this.id = formDef.getId();
if(questionID.equals(formDef.getVariableName()))
name = formDef.getName();
for(int index = 0; index < formDef.getChildren().size(); index++){
IFormElement element = formDef.getChildren().get(index);
if(element instanceof GroupDef)
;//((GroupDef)element).refresh(groupDef);
//refresh((PageDef)formDef.getPageAt(index));
}
//Clear existing skip rules if any. Already existing skip rules will always
//overwrite those from the refresh source.
skipRules = new Vector();
for(int index = 0; index < formDef.getSkipRuleCount(); index++)
formDef.getSkipRuleAt(index).refresh(this, formDef);
//Clear existing validation rules if any. Already existing validation rules
//will always overwrite those from the refresh source.
validationRules = new Vector();
for(int index = 0; index < formDef.getValidationRuleCount(); index++)
formDef.getValidationRuleAt(index).refresh(this, formDef);
//If we already had dynamic options, they will always overwrite all
//from the refresh source.
//TODO May need to do a smarter refresh by only overwriting those that have
//come from the server and then leave the rest.
if(formDef.getDynamicOptions() != null){
dynamicOptions = new HashMap<Integer,DynamicOptionDef>();
Iterator<Entry<Integer,DynamicOptionDef>> iterator = formDef.getDynamicOptions().entrySet().iterator();
while(iterator.hasNext()){
Entry<Integer,DynamicOptionDef> entry = iterator.next();
QuestionDef oldParentQtnDef = (QuestionDef)formDef.getElement(entry.getKey());
if(oldParentQtnDef == null)
continue; //How can this be missing in the original formdef???
QuestionDef newParentQtnDef = (QuestionDef)getElement(oldParentQtnDef.getQuestionID());
if(newParentQtnDef == null)
continue; //My be deleted by refresh source.
DynamicOptionDef oldDynOptionDef = entry.getValue();
QuestionDef oldChildQtnDef = (QuestionDef)formDef.getElement(oldDynOptionDef.getQuestionId());
if(oldChildQtnDef == null)
return; //can this be lost in the old formdef????
QuestionDef newChildQtnDef = (QuestionDef)getElement(oldChildQtnDef.getQuestionID());
if(newChildQtnDef == null)
continue; //possibly deleted by refresh sourced (eg server).
DynamicOptionDef newDynOptionDef = new DynamicOptionDef();
newDynOptionDef.setQuestionId(newChildQtnDef.getId());
newDynOptionDef.refresh(this, formDef, newDynOptionDef, oldDynOptionDef,newParentQtnDef,oldParentQtnDef,newChildQtnDef,oldChildQtnDef);
dynamicOptions.put(new Integer(newParentQtnDef.getId()),newDynOptionDef);
}
}
//add calculations for questions that still exist.
calculations = new Vector();
for(int index = 0; index < formDef.getCalculationCount(); index++){
Calculation calculation = formDef.getCalculationAt(index);
QuestionDef questionDef = (QuestionDef)getElement(formDef.getElement(calculation.getQuestionId()).getQuestionID());
if(questionDef != null)
addCalculation(new Calculation(questionDef.getId(),calculation.getCalculateExpression()));
}
}
public void refresh(IFormElement element){
//for(int index = 0; index < children.size(); index++){
// ((PageDef)children.get(index)).refresh(pageDef);
//}
}
public int getChildCount(){
if(children == null)
return 0;
return children.size();
}
public IFormElement getChildAt(int index){
return children.get(index);
}
/**
* Gets the total number of questions contained in the form.
*
* @return the number of questions.
*/
public int getQuestionCount(){
if(children == null)
return 0;
int count = 0;
for(int index = 0; index < children.size(); index++){
IFormElement element = children.get(index);
if(element instanceof GroupDef)
count += ((GroupDef)element).getChildCount();
else{
assert(element instanceof QuestionDef);
count += 1;
}
}
return count;
}
/**
* Gets the element at a given position on the first level.
*
* @param index the element position.
* @return the element object.
*/
public IFormElement getElementAt(int index){
if(children == null)
return null;
return children.get(index);
}
public void updateRuleConditionValue(String origValue, String newValue){
for(int index = 0; index < getSkipRuleCount(); index++)
getSkipRuleAt(index).updateConditionValue(origValue, newValue);
for(int index = 0; index < getValidationRuleCount(); index++)
getValidationRuleAt(index).updateConditionValue(origValue, newValue);
}
public Element getLanguageNode() {
com.google.gwt.xml.client.Document doc = XMLParser.createDocument();
doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""));
Element rootNode = doc.createElement("xform");
rootNode.setAttribute(XformConstants.ATTRIBUTE_NAME_ID, id+"");
doc.appendChild(rootNode);
if(dataNode != null){
Element node = doc.createElement(XformConstants.NODE_NAME_TEXT);
node.setAttribute(XformConstants.ATTRIBUTE_NAME_XPATH, FormUtil.getNodePath(dataNode)+"[@name]");
node.setAttribute(XformConstants.ATTRIBUTE_NAME_VALUE, name);
node.setAttribute(XformConstants.ATTRIBUTE_NAME_ID, itextId);
node.setAttribute(OpenRosaConstants.ATTRIBUTE_NAME_UNIQUE_ID, "FormDef"+id);
rootNode.appendChild(node);
if(children != null){
for(int index = 0; index < children.size(); index++){
IFormElement element = children.get(index);
if(element instanceof GroupDef)
((GroupDef)element).buildLanguageNodes(null, doc, rootNode);
else{
assert(element instanceof QuestionDef);
((QuestionDef)element).buildLanguageNodes(FormUtil.getNodePath(xformsNode), doc, xformsNode, rootNode);
}
}
}
if(validationRules != null){
for(int index = 0; index < validationRules.size(); index++)
((ValidationRule)validationRules.elementAt(index)).buildLanguageNodes(this, rootNode);
}
if(dynamicOptions != null){
Iterator<Entry<Integer,DynamicOptionDef>> iterator = dynamicOptions.entrySet().iterator();
while(iterator.hasNext())
iterator.next().getValue().buildLanguageNodes(this, rootNode);
}
}
return rootNode;
}
/**
* Gets the form to which a particular item (PageDef,QuestionDef,OptionDef) belongs.
*
* @param formItem the item.
* @return the form.
*/
public static FormDef getFormDef(IFormElement formItem){
if(formItem == null)
return null;
if(formItem instanceof FormDef)
return (FormDef)formItem;
else
return getFormDef(formItem.getParent());
}
/**
* Removes all question change event listeners.
*/
public void clearChangeListeners(){
if(children == null)
return;
for(int i=0; i<children.size(); i++)
children.get(i).clearChangeListeners();
}
public String getText(){
return name;
}
public void setText(String text){
setName(text);
}
public String getQuestionID(){
return questionID;
}
public void setQuestionID(String binding){
setVariableName(binding);
}
public List<IFormElement> getChildren(){
return children;
}
public void setChildren(List<IFormElement> children){
this.children = children;
}
public void addChild(IFormElement element){
if(children == null)
children = new ArrayList<IFormElement>();
children.add(element);
element.setParent(this);
}
public IFormElement getParent(){
return null;
}
public void setParent(IFormElement parent){
}
public Element getControlNode(){
return getBodyNode();
}
public void setControlNode(Element controlNode){
}
public Element getBindNode(){
return null;
}
public void setBindNode(Element bindNode){
}
public int getDataType(){
return QuestionDef.QTN_TYPE_NULL;
}
public void setDataType(int dataType){
}
public void updateDataNodes(Element parentDataNode){
}
public IFormElement copy(IFormElement parent){
return null;
}
public String getDisplayText(){
return name;
}
public String getHelpText(){
return null;
}
public void setHelpText(String helpText){
}
public Element getLabelNode(){
return null;
}
public void setLabelNode(Element labelNode){
}
public Element getHintNode(){
return null;
}
public void setHintNode(Element hintNode){
}
/**
* Convenience method that returns the total
* number of OptionDefs present in the form
* (useful for generating IDs for new option items)
* @return
*/
public int getOptionCount(){
return recurseCount(this);
}
private int recurseCount(IFormElement parent){
int count = 0;
if(parent == null){ return 0; }
boolean hasQuestionDefChildren = parent instanceof GroupDef || //is a group or
parent instanceof FormDef ||
(parent instanceof QuestionDef &&
parent.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || // or is a 1select
parent.getDataType() == QuestionDef.QTN_TYPE_LIST_MULTIPLE || // or is a select
parent.getDataType() == QuestionDef.QTN_TYPE_REPEAT); // or is a repeat
if(hasQuestionDefChildren){
if(parent.getChildren() == null || parent.getChildren().size() <= 0){ return 0; }
for(IFormElement item : parent.getChildren()){
count += recurseCount(item);
}
return count;
}else if(parent instanceof QuestionDef){
return 0; //not a question that has OptionDef for children
}else if(parent instanceof FormDef){
return ((FormDef) parent).getOptionCount();
}else if(parent instanceof OptionDef){
return 1;
}else{
return 0;
}
}
public FormDef getFormDef(){
return this;
}
public boolean isLocked(){
return false;
}
public boolean isRequired(){
return false;
}
public boolean isEnabled(){
return true;
}
public String getDefaultValue(){
return null;
}
@Override
public void setEnabled(boolean enabled) {
// TODO Auto-generated method stub
}
@Override
public void setLocked(boolean locked) {
// TODO Auto-generated method stub
}
@Override
public void setRequired(boolean required) {
// TODO Auto-generated method stub
}
/**
* Moves a given child (of this node) to
* a given index
* @param child
* @param index
* @throws Exception
*/
public void moveChildToIndex(IFormElement child, int index) throws Exception{
if(!children.contains(child)){
throw new Exception("Child not in Children list!");
}
children.remove(child);
int i = (index > children.size()) ? children.size() : index;
try{
children.add(i, child);
}catch(Exception e){
FormUtil.displayException(e);
}
}
/**
* Get the Nodeset ref that points to the data node where the question's answer will be stored.
* @return
*/
public String getDataNodesetPath(){
if(getParent() == null){
return "/"+getQuestionID();
}else{
return getParent().getDataNodesetPath() + "/"+getQuestionID();
}
}
public boolean hasUINode() {
return true;
}
public void setHasUINode(boolean hasUINode) {
return;
}
/** Not Applicable **/
public boolean hasAdvancedCalculate() {
return false;
}
/** Not Applicable **/
public boolean hasAdvancedConstraint() {
return false;
}
/** Not Applicable **/
public boolean hasAdvancedRelevant() {
return false;
}
/** Not Applicable **/
public void setHasAdvancedCalculate(boolean enabled) {
return;
}
/** Not Applicable **/
public void setHasAdvancedConstraint(boolean enabled) {
return;
}
/** Not Applicable **/
public void setHasAdvancedRelevant(boolean enabled) {
return;
}
/** Not Applicable **/
public String getAdvancedCalculate() {
return null;
}
/** Not Applicable **/
public String getAdvancedConstraint() {
return null;
}
/** Not Applicable **/
public String getAdvancedRelevant() {
return null;
}
/** Not Applicable **/
public void setAdvancedCalculate(String calcValue) {
return;
}
/** Not Applicable **/
public void setAdvancedConstraint(String constValue) {
return;
}
/** Not Applicable **/
public void setAdvancedRelevant(String releValue) {
return;
}
/**
* Walks through the skipRule list,
* finds the max id, and returns max+1
* @return
*/
public int getNextSkipRuleId(){
int max = 0;
int cur = 0;
Vector skipRules = getSkipRules();
if(skipRules == null){ skipRules = new Vector(); }
for(Object sr: skipRules){
cur = ((SkipRule)sr).getId();
if(cur > max){
max = cur;
}else{
continue;
}
}
return max+1;
}
public boolean insertChildAfter(IFormElement child, IFormElement target) {
if(child == null || target == null){ return false; }
boolean isChildQuestionOrGroupDef = (child instanceof GroupDef || child instanceof QuestionDef);
if(!isChildQuestionOrGroupDef){ return false; } //we don't want to insert OptionDefs into this list.
return FormDef.insertChildBeforeOrAfter(child, target, this.children, FormDef.INSERT_AFTER);
}
/**
* This is a static method use by all the classes implementing IFormElement interface (except OptionDef
* because it doesn't apply) to move things around.
* @param child
* @param target
* @param targetList
* @param beforeOrAfter
* @return
*/
protected static boolean insertChildBeforeOrAfter(IFormElement child, IFormElement target, List<IFormElement> targetList, int beforeOrAfter){
boolean isTargetInChildren = targetList.contains(target); //target must be in targetList
boolean isChildAlreadyInChildren = targetList.contains(child);
List<IFormElement> originalSiblings = child.getParent().getChildren();
if(isTargetInChildren){
if(isChildAlreadyInChildren){
targetList.remove(child);
}
int targetIndex = targetList.indexOf(target);
targetIndex += beforeOrAfter; //will add 1 for after, 0 for before. According to final INSERT_BEFORE and INSERT_AFTER fields in this class.
targetList.add(targetIndex,child);
child.setParent(target.getParent());
if(!isChildAlreadyInChildren){
originalSiblings.remove(child);
}
return true;
}else{
return false;
}
}
public boolean insertChildBefore(IFormElement child, IFormElement target) {
if(child == null || target == null){ return false; }
boolean isChildQuestionOrGroupDef = (child instanceof GroupDef || child instanceof QuestionDef);
if(!isChildQuestionOrGroupDef){ return false; } //we don't want to insert OptionDefs into this list.
return FormDef.insertChildBeforeOrAfter(child, target, this.children, FormDef.INSERT_BEFORE);
}
}