package org.openrosa.client.xforms;
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.model.Calculation;
import org.openrosa.client.model.DynamicOptionDef;
import org.openrosa.client.model.FormDef;
import org.openrosa.client.model.GroupDef;
import org.openrosa.client.model.IFormElement;
import org.openrosa.client.model.QuestionDef;
import org.openrosa.client.model.SkipRule;
import org.openrosa.client.model.ValidationRule;
import org.openrosa.client.util.FormUtil;
import org.openrosa.client.util.Itext;
import org.openrosa.client.util.UUID;
import org.openrosa.client.xforms.XformConstants;
import org.openrosa.client.xforms.XformUtil;
import org.openrosa.client.xforms.XmlUtil;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.XMLParser;
/**
* Builds an xforms document from a form definition object model components.
*
* @author daniel
*
*/
public class XformBuilder {
/**
* All methods in this class are static and hence we expect no external
* Instantiation of this class.
*/
private XformBuilder(){
}
/**
* Converts a form definition object to its xforms xml.
*
* @param formDef the form definition object.
* @return the xforms xml.
*/
public static String fromFormDef2Xform(FormDef formDef){
//Create a new document.
Document doc = XMLParser.createDocument();
doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""));
formDef.setDoc(doc);
//Create the document root node.
Element xformsNode = doc.createElement(XformConstants.NODE_NAME_XFORMS);
formDef.setXformsNode(xformsNode);
//Set the xf and xsd prefix values and then add the root node to the document.
xformsNode.setAttribute(XformConstants.XML_NAMESPACE /*XformConstants.XML_NAMESPACE_PREFIX+XformConstants.PREFIX_XFORMS*/, XformConstants.NAMESPACE_XFORMS);
xformsNode.setAttribute(XformConstants.XML_NAMESPACE_PREFIX+XformConstants.PREFIX_XML_SCHEMA, XformConstants.NAMESPACE_XML_SCHEMA);
doc.appendChild(xformsNode);
//Create the xforms model node and add it to the root node.
Element modelNode = doc.createElement(XformConstants.NODE_NAME_MODEL);
xformsNode.appendChild(modelNode);
//Now build the rest of the xforms elements and add them to the document.
buildXform(formDef,doc,xformsNode,modelNode);
//Return the string representation of the document to the caller.
return XmlUtil.fromDoc2String(doc);
}
/**
* Builds a form definition object's xforms document whose root and model
* elements have already been created.
*
* @param formDef the form definition object.
* @param doc the xforms document.
* @param parentNode the xforms node to which is the parent of the UI nodes.
* @param modelNode the xforms document model node.
*/
public static void buildXform(FormDef formDef, Document doc, Element parentNode, Element modelNode){
//Create the instance node and add it to the model node.
Element instanceNode = doc.createElement(XformConstants.NODE_NAME_INSTANCE);
modelNode.appendChild(instanceNode);
formDef.setModelNode(modelNode);
//Create the form data node and add it to the instance node.
Element parentDataNode = doc.createElement("data");
parentDataNode.setAttribute("xmlnsCHANGEME", "http://openrosa.org/formdesigner/"+UUID.uuid());
parentDataNode.setAttribute("xmlns:jrm", XformUtil.getDataXMLNSjrm());
parentDataNode.setAttribute("uiVersion", "1");
parentDataNode.setAttribute("version", "1");
instanceNode.appendChild(parentDataNode);
formDef.setDataNode(parentDataNode);
//Check if we have any pages.
if(formDef.getChildren() == null){
return;
}
//Build the ui nodes for all questions in each page.
for(int pageNo=0; pageNo<formDef.getChildCount(); pageNo++){
IFormElement element = formDef.getChildAt(pageNo);
if(element instanceof GroupDef){
fromGroupDef2Xform((GroupDef)element,doc,parentNode,formDef,parentDataNode,modelNode);
}else{
UiElementBuilder.fromQuestionDef2Xform((QuestionDef)element,doc,formDef,parentDataNode,modelNode,parentNode);
}
}
//Build relevant s for the skip rules.
Vector rules = formDef.getSkipRules();
if(rules != null){
for(int i=0; i<rules.size(); i++)
RelevantBuilder.fromSkipRule2Xform((SkipRule)rules.elementAt(i),formDef, doc);
}
//Build constraints for the validation rules.
rules = formDef.getValidationRules();
if(rules != null){
for(int i=0; i<rules.size(); i++)
ConstraintBuilder.fromValidationRule2Xform((ValidationRule)rules.elementAt(i),formDef);
}
//Build calculates for calculations
for(int index = 0; index < formDef.getCalculationCount(); index++){
Calculation calculation = formDef.getCalculationAt(index);
IFormElement elementDef = formDef.getElement(calculation.getQuestionId());
if(elementDef == null)
continue;
Element node = elementDef.getBindNode() != null ? elementDef.getBindNode() : elementDef.getControlNode();
String expr = calculation.getCalculateExpression();
expr = XmlUtil.escapeXMLAttribute(expr);
if(node != null && expr != null && expr.length() != 0){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_CALCULATE, expr);
}else{
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_CALCULATE);
}
}
//Build itemsets for dynamic option definition objects.
List<QuestionDef> orphanQuestions = new ArrayList<QuestionDef>();
HashMap<Integer,DynamicOptionDef> dynamicOptions = formDef.getDynamicOptions();
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 = formDef.getQuestion(entry.getKey());
if(questionDef == null)
continue;
if(!ItemsetBuilder.fromDynamicOptionDef2Xform(doc,dynamicOptionDef,questionDef,formDef))
orphanQuestions.add(questionDef);
}
}
//Cleanse binds of bad attributs:
cleanBindNodeRecurse(formDef);
//If there are any itemsets which were not built completely due to their dependent
//parent questions not having been parsed yet, build them now.
if(orphanQuestions.size() > 0)
ItemsetBuilder.updateDynamicOptions(dynamicOptions,orphanQuestions,formDef,doc);
}
/**
* A little helper function that you can throw all your removeAttribute statements in...
* @param bindNode
*/
private static void cleanBindNode(Element bindNode){
bindNode.removeAttribute("action");
}
private static void cleanBindNodeRecurse(IFormElement parent){
//first clean the parent
Element bindNode = parent.getBindNode();
if(bindNode != null){
cleanBindNode(bindNode);
}
if(parent.getChildren() != null){
//then recursively clean children
for(int i=0;i<parent.getChildren().size();i++){
IFormElement child = parent.getChildren().get(i);
cleanBindNodeRecurse(child);
}
}
}
private static void addMetaData(Document doc, Element dataNode){
Element metaNode = doc.createElement("orx:meta");
dataNode.appendChild(metaNode);
Element node = doc.createElement("orx:timeStart");
metaNode.appendChild(node);
node = doc.createElement("orx:timeEnd");
metaNode.appendChild(node);
node = doc.createElement("orx:instanceID");
metaNode.appendChild(node);
// node = doc.createElement("orx:userID");
// metaNode.appendChild(node);
//
// node = doc.createElement("orx:deviceID");
// metaNode.appendChild(node);
}
/**
* Converts a page definition object to xforms.
*
* @param groupDef the page definition object.
* @param doc the xforms document.
* @param xformsNode the root node of the xforms document.
* @param formDef the form definition object to which the page belongs.
* @param formNode the xforms instance data node.
* @param modelNode the xforms model node.
*/
public static void fromGroupDef2Xform(GroupDef groupDef, Document doc, Element xformsNode, FormDef formDef, Element formNode, Element modelNode){
// if(pageDef.getDataType() == QuestionDef.QTN_TYPE_REPEAT)
// UiElementBuilder.fromQuestionDef2Xform(pageDef,doc,xformsNode,formDef,formNode,modelNode,xformsNode);
// else{
//Create a group node
Element groupNode = doc.createElement(XformConstants.NODE_NAME_GROUP);
Element labelNode = doc.createElement(XformConstants.NODE_NAME_LABEL);
Element hintNode = doc.createElement(XformConstants.NODE_NAME_HINT);
Element bindNode = doc.createElement("bind");
Element parentDataNode = groupDef.getParent().getDataNode();
String nodesetPath = groupDef.getDataNodesetPath();
String qtnID = FormUtil.getQtnIDFromNodeSetPath(nodesetPath);
if(parentDataNode == null){
parentDataNode = formDef.getDataNode();
}
Element dataNode = doc.createElement(qtnID);
parentDataNode.appendChild(dataNode);
groupDef.setDataNode(dataNode);
boolean hasHintText = (groupDef.getHelpText()!= null && !groupDef.getHelpText().isEmpty() )||
(Itext.getDefaultLocale().hasID(groupDef.getItextId()+";hint"));
if(hasHintText){
UiElementBuilder.addHelpItextRefs(hintNode, groupDef);
groupDef.setHintNode(hintNode);
groupNode.appendChild(hintNode);
}
labelNode.appendChild(doc.createTextNode(groupDef.getText()));
UiElementBuilder.addItextRefs(labelNode, groupDef);
groupNode.setAttribute("nodeset", groupDef.getDataNodesetPath());
groupNode.appendChild(labelNode);
xformsNode.appendChild(groupNode);
if(groupDef.getDataType() == QuestionDef.QTN_TYPE_GROUP){
groupDef.setLabelNode(labelNode);
groupDef.setGroupNode(groupNode);
}else{
groupDef.getParent().setLabelNode(labelNode);
groupDef.getParent().setControlNode(groupNode);
}
bindNode.setAttribute("id", qtnID);
bindNode.setAttribute("nodeset", nodesetPath);
modelNode.appendChild(bindNode);
groupDef.setBindNode(bindNode);
//Check if we have any questions in this page.
List<IFormElement> questions = groupDef.getChildren();
if(questions == null){
return;
}
//Create ui nodes for each question.
for(int i=0; i<questions.size(); i++){
IFormElement qtn = questions.get(i);
if(qtn instanceof QuestionDef){
UiElementBuilder.fromQuestionDef2Xform((QuestionDef)qtn,doc,formDef,formNode,modelNode,groupNode);
}else{
fromGroupDef2Xform((GroupDef)qtn,doc,groupNode,formDef,formNode,modelNode);
}
}
// }
}
}