package org.openrosa.client.xforms; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.openrosa.client.model.DynamicOptionDef; import org.openrosa.client.model.FormDef; import org.openrosa.client.model.OptionDef; import org.openrosa.client.model.QuestionDef; import org.openrosa.client.xforms.XformConstants; import org.openrosa.client.xforms.XformUtil; import org.openrosa.client.xforms.XmlUtil; import com.google.gwt.xml.client.Element; import com.google.gwt.xml.client.Node; import com.google.gwt.xml.client.NodeList; /** * Parse itemset portions of xforms documents, together with their instance data, * and builds the dynamic option definition objects of the model. * * @author daniel * */ public class ItemsetParser { /** * All methods in this class are static and hence we expect no external * Instantiation of this class. */ private ItemsetParser(){ } /** * Parses dynamic option lists for questions whose parent questions had not been yet * processed during the earlier passes because of question ordering in the xform. * * @param formDef the form definition. * @param orphanDynOptionQns the list of unprocessed dynamic option child questions. */ public static void parseOrphanDynOptionQns(FormDef formDef, List<QuestionDef> orphanDynOptionQns){ int orgSize = orphanDynOptionQns.size(); if(orgSize == 0) return; //We are using an array copy because we will modify the orphanDynOptionQns list //as we loop through it and hence do not want concurrency modification exceptions. Object[] qtns = orphanDynOptionQns.toArray(); for(int index = 0; index < qtns.length; index++){ QuestionDef questionDef = (QuestionDef)qtns[index]; if(questionDef.getFirstOptionNode() != null) parseDynamicOptionsList(questionDef, questionDef.getFirstOptionNode().getAttribute(XformConstants.ATTRIBUTE_NAME_NODESET), formDef,orphanDynOptionQns); } //If we have more to process and the number has reduced, just continue //Else if number is the same then we int newSize = orphanDynOptionQns.size(); if(newSize > 0 && newSize < orgSize) parseOrphanDynOptionQns(formDef,orphanDynOptionQns); } /** * Parses an xforms dynamic options instance node child, whose name is dynamiclist, and updates the given dynamic * option definition object. * * @param dynamicOptionDef the dynamic option definition object. * @param questionDef the child question that this dynamic option definition references * @param parentQuestionDef the parent question to this dyanamic option question. * @param node the instance node child, which called dynamiclist. * @param optionDef the child option is is currently being parsed. * @param parentOptionIdMap the map of child options keyed by their parent option ids. * @param formDef the form to which the dynamic option definition object belongs. * @param orphanDynOptionQns a list of dynamic option definition questions who parent * questions have not yet been parsed. */ public static void parseDynamicOptions(DynamicOptionDef dynamicOptionDef, QuestionDef questionDef, QuestionDef parentQuestionDef, Node node, OptionDef optionDef, HashMap<String,Integer> parentOptionIdMap, FormDef formDef, List<QuestionDef> orphanDynOptionQns){ String label = ""; String value = ""; Element labelNode = null; Element valueNode = null; NodeList nodes = node.getChildNodes(); for(int index = 0; index < nodes.getLength(); index++){ Node child = nodes.item(index); if(child.getNodeType() != Node.ELEMENT_NODE) continue; String name = child.getNodeName(); //if(name.equals(NODE_NAME_ITEM_MINUS_PREFIX)){ if(XmlUtil.nodeNameEquals(name,XformConstants.NODE_NAME_ITEM_MINUS_PREFIX)){ String parent = ((Element)child).getAttribute(XformConstants.ATTRIBUTE_NAME_PARENT); if(parent == null || parent.trim().length() == 0) continue; optionDef = new OptionDef(dynamicOptionDef.getNextOptionId(true),label, value,questionDef); optionDef.setControlNode((Element)child); Integer optionId = parentOptionIdMap.get(parent); if(optionId == null){ OptionDef optnDef = parentQuestionDef.getOptionWithValue(parent); if(parentQuestionDef.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC){ DynamicOptionDef dynOptionsDef = formDef.getChildDynamicOptions(parentQuestionDef.getId()); if(dynOptionsDef == null) return; optnDef = dynOptionsDef.getOptionWithValue(parent); } if(optnDef == null){ if(!orphanDynOptionQns.contains(questionDef)) orphanDynOptionQns.add(questionDef); continue; } optionId = optnDef.getId(); parentOptionIdMap.put(parent, optionId); } List<OptionDef> optionList = dynamicOptionDef.getOptionList(optionId); if(optionList == null){ optionList = new ArrayList<OptionDef>(); dynamicOptionDef.setOptionList(optionId, optionList); } optionList.add(optionDef); parseDynamicOptions(dynamicOptionDef,questionDef,parentQuestionDef,child,optionDef,parentOptionIdMap,formDef,orphanDynOptionQns); } //else if(name.equals(NODE_NAME_LABEL_MINUS_PREFIX)){ else if(XmlUtil.nodeNameEquals(name,XformConstants.NODE_NAME_LABEL_MINUS_PREFIX)){ if(child.getChildNodes().getLength() != 0){ label = child.getChildNodes().item(0).getNodeValue().trim(); //questionDef.setText(child.getChildNodes().item(0).getNodeValue().trim()); labelNode = (Element)child; } } //else if(name.equals(NODE_NAME_VALUE_MINUS_PREFIX)){ else if(XmlUtil.nodeNameEquals(name,XformConstants.NODE_NAME_VALUE_MINUS_PREFIX)){ if(child.getChildNodes().getLength() != 0){ value = child.getChildNodes().item(0).getNodeValue().trim(); valueNode = (Element)child; } } } if (!label.equals("") && !value.equals("")) { //$NON-NLS-1$ //$NON-NLS-2$ if (optionDef != null){ optionDef.setText(label); optionDef.setQuestionID(value); optionDef.setLabelNode(labelNode); optionDef.setValueNode(valueNode); } } } /** * Creates a new instance of a dynamic option definition object and builds its * child to parent options. * * @param questionDef the child question to be referenced by the dynamic option definition object. * @param nodeset the nodeset attribute value for the itemset of this dyanamic option definition object. * @param formDef the form definition object to which this dynamic option list belongs. * @param orphanDynOptionQns the list of unprocessed dynamic option child questions. */ public static void parseDynamicOptionsList(QuestionDef questionDef, String nodeset, FormDef formDef, List<QuestionDef> orphanDynOptionQns){ if(nodeset == null || nodeset.trim().length() == 0) return; //Get the parent question bind id. String binding = ItemsetUtil.getParentQuestionBindId(nodeset); assert(binding != null); if(binding == null) return; //This can only be a bug //Return if parent question has not yet been parsed. QuestionDef parentQuestionDef = (QuestionDef)formDef.getElement(binding); if(parentQuestionDef == null) return; //Get the dynamic option definition instance id. String instanceId = ItemsetUtil.getChildInstanceId(nodeset); assert(instanceId != null); if(instanceId == null) return; //This can only be a bug //Get the dynamic option definition instance node. Element instanceNode = XformUtil.getInstanceNode(formDef.getModelNode(), instanceId); //assert(instanceNode != null); if(instanceNode == null) return; //This can only be a bug //Create a new dynamic option definition object. DynamicOptionDef dynamicOptionDef = new DynamicOptionDef(); dynamicOptionDef.setQuestionId(questionDef.getId()); //Go through its instance node child and get the first and only element node //whose name should be dynamiclist, then parse its children. NodeList nodes = instanceNode.getChildNodes(); for(int index = 0; index < nodes.getLength(); index++){ Node child = nodes.item(index); if(child.getNodeType() == Node.ELEMENT_NODE){ assert(child.getNodeName().equals("dynamiclist")); dynamicOptionDef.setDataNode((Element)child); HashMap<String,Integer> parentOptionIdMap = new HashMap<String,Integer>(); parseDynamicOptions(dynamicOptionDef,questionDef,parentQuestionDef,child,null,parentOptionIdMap,formDef,orphanDynOptionQns); break; } } //If processed completely (as in parent question found), remove from orphan list. if(dynamicOptionDef.getParentToChildOptions() != null && orphanDynOptionQns.contains(questionDef)) orphanDynOptionQns.remove(questionDef); //Add this new dynamic option definition object to the form. formDef.setDynamicOptionDef(parentQuestionDef.getId(), dynamicOptionDef); } }