diff --git a/src/cli/java/org/commcare/util/screen/QueryScreen.java b/src/cli/java/org/commcare/util/screen/QueryScreen.java index 8f6a352759..210c0ced58 100644 --- a/src/cli/java/org/commcare/util/screen/QueryScreen.java +++ b/src/cli/java/org/commcare/util/screen/QueryScreen.java @@ -18,6 +18,7 @@ import org.commcare.session.RemoteQuerySessionManager; import org.commcare.suite.model.RemoteQueryDatum; import org.commcare.suite.model.QueryPrompt; +import org.commcare.suite.model.QueryGroup; import org.javarosa.core.model.instance.ExternalDataInstance; import org.javarosa.core.model.instance.ExternalDataInstanceSource; import org.javarosa.core.services.locale.Localization; @@ -44,6 +45,7 @@ public class QueryScreen extends Screen { private RemoteQuerySessionManager remoteQuerySessionManager; protected OrderedHashtable userInputDisplays; + protected Hashtable groupHeaders; private SessionWrapper sessionWrapper; private String[] fields; private String mTitle; @@ -94,6 +96,7 @@ public void init(SessionWrapper sessionWrapper) throws CommCareSessionException mTitle = getTitleLocaleString(); description = getDescriptionLocaleString(); dynamicSearch = getQueryDatum().getDynamicSearch(); + groupHeaders = getQueryDatum().getUserQueryGroupHeaders(); } private String getTitleLocaleString() { @@ -262,6 +265,21 @@ public OrderedHashtable getUserInputDisplays() { return userInputDisplays; } + public Hashtable getGroupHeaders() { + return groupHeaders; + } + + public Hashtable evalGroupHeaders() { + Hashtable queryGroupMap = new Hashtable<>(); + for (Map.Entry entry : groupHeaders.entrySet()) { + String key = entry.getKey(); + QueryGroup queryGroupItem = entry.getValue(); + String text = queryGroupItem.getDisplay().getText().evaluate(sessionWrapper.getEvaluationContext()); + queryGroupMap.put(key, text); + } + return queryGroupMap; + } + public String getCurrentMessage() { return currentMessage; } diff --git a/src/main/java/org/commcare/suite/model/QueryGroup.java b/src/main/java/org/commcare/suite/model/QueryGroup.java new file mode 100644 index 0000000000..c2f8b62f9b --- /dev/null +++ b/src/main/java/org/commcare/suite/model/QueryGroup.java @@ -0,0 +1,47 @@ +package org.commcare.suite.model; + +import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.core.util.externalizable.ExtUtil; +import org.javarosa.core.util.externalizable.Externalizable; +import org.javarosa.core.util.externalizable.PrototypeFactory; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +// Model for node +public class QueryGroup implements Externalizable { + + private String key; + private DisplayUnit display; + + @SuppressWarnings("unused") + public QueryGroup() { + } + + public QueryGroup(String key, DisplayUnit display) { + this.key = key; + this.display = display; + } + + @Override + public void readExternal(DataInputStream in, PrototypeFactory pf) + throws IOException, DeserializationException { + key = (String)ExtUtil.read(in, String.class, pf); + display = (DisplayUnit)ExtUtil.read(in, DisplayUnit.class, pf); + } + + @Override + public void writeExternal(DataOutputStream out) throws IOException { + ExtUtil.write(out, key); + ExtUtil.write(out, display); + } + + public String getKey() { + return key; + } + + public DisplayUnit getDisplay() { + return display; + } +} diff --git a/src/main/java/org/commcare/suite/model/QueryPrompt.java b/src/main/java/org/commcare/suite/model/QueryPrompt.java index 5653f2559f..98678922fc 100644 --- a/src/main/java/org/commcare/suite/model/QueryPrompt.java +++ b/src/main/java/org/commcare/suite/model/QueryPrompt.java @@ -74,6 +74,9 @@ public class QueryPrompt implements Externalizable { @Nullable private QueryPromptCondition validation; + @Nullable + private String groupKey; + @SuppressWarnings("unused") public QueryPrompt() { } @@ -81,7 +84,7 @@ public QueryPrompt() { public QueryPrompt(String key, String appearance, String input, String receive, String hidden, DisplayUnit display, ItemsetBinding itemsetBinding, XPathExpression defaultValueExpr, boolean allowBlankValue, XPathExpression exclude, - QueryPromptCondition required, QueryPromptCondition validation) { + QueryPromptCondition required, QueryPromptCondition validation, String groupKey) { this.key = key; this.appearance = appearance; this.input = input; @@ -94,6 +97,7 @@ public QueryPrompt(String key, String appearance, String input, String receive, this.exclude = exclude; this.required = required; this.validation = validation; + this.groupKey = groupKey; } @Override @@ -111,6 +115,7 @@ public void readExternal(DataInputStream in, PrototypeFactory pf) exclude = (XPathExpression)ExtUtil.read(in, new ExtWrapNullable(new ExtWrapTagged()), pf); validation = (QueryPromptCondition)ExtUtil.read(in, new ExtWrapNullable(QueryPromptCondition.class), pf); required = (QueryPromptCondition)ExtUtil.read(in, new ExtWrapNullable(QueryPromptCondition.class), pf); + groupKey = (String)ExtUtil.read(in, new ExtWrapNullable(String.class), pf); } @Override @@ -128,6 +133,7 @@ public void writeExternal(DataOutputStream out) throws IOException { ExtUtil.write(out, new ExtWrapNullable(exclude == null ? null : new ExtWrapTagged(exclude))); ExtUtil.write(out, new ExtWrapNullable(validation)); ExtUtil.write(out, new ExtWrapNullable(required)); + ExtUtil.write(out, new ExtWrapNullable(groupKey)); } public String getKey() { @@ -186,6 +192,11 @@ public QueryPromptCondition getValidation() { return validation; } + @Nullable + public String getGroupKey() { + return groupKey; + } + /** * @return whether the prompt has associated choices to select from */ diff --git a/src/main/java/org/commcare/suite/model/RemoteQueryDatum.java b/src/main/java/org/commcare/suite/model/RemoteQueryDatum.java index d25dc1cf87..7ad5dc5589 100644 --- a/src/main/java/org/commcare/suite/model/RemoteQueryDatum.java +++ b/src/main/java/org/commcare/suite/model/RemoteQueryDatum.java @@ -1,6 +1,7 @@ package org.commcare.suite.model; import org.javarosa.core.util.OrderedHashtable; +import java.util.Hashtable; import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.core.util.externalizable.ExtUtil; import org.javarosa.core.util.externalizable.ExtWrapBase; @@ -28,6 +29,7 @@ public class RemoteQueryDatum extends SessionDatum { private List hiddenQueryValues; private OrderedHashtable userQueryPrompts; + private Hashtable userQueryGroupHeaders; private boolean useCaseTemplate; private boolean defaultSearch; private boolean dynamicSearch; @@ -45,11 +47,13 @@ public RemoteQueryDatum() { */ public RemoteQueryDatum(URL url, String storageInstance, List hiddenQueryValues, - OrderedHashtable userQueryPrompts, - boolean useCaseTemplate, boolean defaultSearch, boolean dynamicSearch, Text title, Text description) { + OrderedHashtable userQueryPrompts, boolean useCaseTemplate, + boolean defaultSearch, boolean dynamicSearch, Text title, Text description, + Hashtable userQueryGroupHeaders) { super(storageInstance, url.toString()); this.hiddenQueryValues = hiddenQueryValues; this.userQueryPrompts = userQueryPrompts; + this.userQueryGroupHeaders = userQueryGroupHeaders; this.useCaseTemplate = useCaseTemplate; this.defaultSearch = defaultSearch; this.dynamicSearch = dynamicSearch; @@ -61,6 +65,10 @@ public OrderedHashtable getUserQueryPrompts() { return userQueryPrompts; } + public Hashtable getUserQueryGroupHeaders() { + return userQueryGroupHeaders; + } + public List getHiddenQueryValues() { return hiddenQueryValues; } @@ -104,6 +112,9 @@ public void readExternal(DataInputStream in, PrototypeFactory pf) userQueryPrompts = (OrderedHashtable)ExtUtil.read(in, new ExtWrapMap(String.class, QueryPrompt.class, ExtWrapMap.TYPE_ORDERED), pf); + userQueryGroupHeaders = + (Hashtable)ExtUtil.read(in, + new ExtWrapMap(String.class, QueryGroup.class, ExtWrapMap.TYPE_ORDERED), pf); title = (Text) ExtUtil.read(in, new ExtWrapNullable(Text.class), pf); description = (Text) ExtUtil.read(in, new ExtWrapNullable(Text.class), pf); useCaseTemplate = ExtUtil.readBool(in); @@ -116,6 +127,7 @@ public void writeExternal(DataOutputStream out) throws IOException { super.writeExternal(out); ExtUtil.write(out, new ExtWrapList(hiddenQueryValues, new ExtWrapTagged())); ExtUtil.write(out, new ExtWrapMap(userQueryPrompts)); + ExtUtil.write(out, new ExtWrapMap(userQueryGroupHeaders)); ExtUtil.write(out, new ExtWrapNullable(title)); ExtUtil.write(out, new ExtWrapNullable(description)); ExtUtil.writeBool(out, useCaseTemplate); diff --git a/src/main/java/org/commcare/xml/QueryGroupParser.java b/src/main/java/org/commcare/xml/QueryGroupParser.java new file mode 100644 index 0000000000..fe6850f41d --- /dev/null +++ b/src/main/java/org/commcare/xml/QueryGroupParser.java @@ -0,0 +1,48 @@ +package org.commcare.xml; + +import org.commcare.suite.model.DisplayUnit; +import org.commcare.suite.model.QueryGroup; +import org.javarosa.xml.util.InvalidStructureException; +import org.javarosa.xml.util.UnfullfilledRequirementsException; +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +public class QueryGroupParser extends CommCareElementParser { + + public static final String NAME_GROUP = "group"; + private static final String ATTR_KEY = "key"; + private static final String NAME_DISPLAY = "display"; + + public QueryGroupParser(KXmlParser parser) { + super(parser); + } + + @Override + public QueryGroup parse() throws InvalidStructureException, IOException, XmlPullParserException, + UnfullfilledRequirementsException { + checkNode(NAME_GROUP); + + String key = parser.getAttributeValue(null, ATTR_KEY); + DisplayUnit display = null; + + while (nextTagInBlock(NAME_GROUP)) { + if (NAME_DISPLAY.equalsIgnoreCase(parser.getName())) { + display = parseDisplayBlock(); + } else { + throw new InvalidStructureException( + "Unrecognised node " + parser.getName() + "in validation for group " + key); + } + } + + if (key == null) { + throw new InvalidStructureException(" block must define a 'key' attribute", parser); + } + if (display == null) { + throw new InvalidStructureException(" block must define a element", parser); + } + + return new QueryGroup(key, display); + } +} diff --git a/src/main/java/org/commcare/xml/QueryPromptParser.java b/src/main/java/org/commcare/xml/QueryPromptParser.java index fdae744720..cbf639b87a 100644 --- a/src/main/java/org/commcare/xml/QueryPromptParser.java +++ b/src/main/java/org/commcare/xml/QueryPromptParser.java @@ -40,6 +40,7 @@ public class QueryPromptParser extends CommCareElementParser { private static final String ATTR_REQUIRED = "required"; private static final String ATTR_VALIDATION_TEST = "test"; private static final String NAME_TEXT = "text"; + private static final String ATTR_GROUP_KEY = "group_key"; public QueryPromptParser(KXmlParser parser) { super(parser); @@ -60,6 +61,7 @@ public QueryPrompt parse() throws InvalidStructureException, IOException, XmlPul XPathExpression defaultValue = xpathPropertyValue(defaultValueString); String excludeValueString = parser.getAttributeValue(null, ATTR_EXCLUDE); XPathExpression exclude = xpathPropertyValue(excludeValueString); + String groupKey = parser.getAttributeValue(null, ATTR_GROUP_KEY); XPathExpression oldRequired = xpathPropertyValue(parser.getAttributeValue(null, ATTR_REQUIRED)); QueryPromptCondition validation = null; @@ -86,7 +88,7 @@ public QueryPrompt parse() throws InvalidStructureException, IOException, XmlPul return new QueryPrompt(key, appearance, input, receive, hidden, display, itemsetBinding, defaultValue, allowBlankValue, exclude, - required, validation); + required, validation, groupKey); } private QueryPromptCondition parseRequiredBlock(String key) diff --git a/src/main/java/org/commcare/xml/SessionDatumParser.java b/src/main/java/org/commcare/xml/SessionDatumParser.java index a841188065..78339104a7 100644 --- a/src/main/java/org/commcare/xml/SessionDatumParser.java +++ b/src/main/java/org/commcare/xml/SessionDatumParser.java @@ -6,6 +6,7 @@ import org.commcare.suite.model.FormIdDatum; import org.commcare.suite.model.MultiSelectEntityDatum; import org.commcare.suite.model.QueryData; +import org.commcare.suite.model.QueryGroup; import org.commcare.suite.model.QueryPrompt; import org.commcare.suite.model.RemoteQueryDatum; import org.commcare.suite.model.SessionDatum; @@ -20,6 +21,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Hashtable; /** * @author ctsims @@ -129,6 +131,7 @@ private RemoteQueryDatum parseRemoteQueryDatum() boolean dynamicSearch = "true".equals(parser.getAttributeValue(null, "dynamic_search")); Text title = null; Text description = null; + Hashtable groupPrompts = new Hashtable<>(); ArrayList hiddenQueryValues = new ArrayList(); while (nextTagInBlock("query")) { String tagName = parser.getName(); @@ -143,9 +146,12 @@ private RemoteQueryDatum parseRemoteQueryDatum() } else if ("description".equals(tagName)) { nextTagInBlock("description"); description = new TextParser(parser).parse(); + } else if (QueryGroupParser.NAME_GROUP.equals(tagName)){ + QueryGroup queryGroup = new QueryGroupParser(parser).parse(); + groupPrompts.put(queryGroup.getKey(), queryGroup); } } return new RemoteQueryDatum(queryUrl, queryResultStorageInstance, hiddenQueryValues, - userQueryPrompts, useCaseTemplate, defaultSearch, dynamicSearch, title, description); + userQueryPrompts, useCaseTemplate, defaultSearch, dynamicSearch, title, description, groupPrompts); } } diff --git a/src/test/java/org/commcare/xml/QueryDataParserTest.java b/src/test/java/org/commcare/xml/QueryDataParserTest.java index d5ea08833b..9f9f922ad0 100644 --- a/src/test/java/org/commcare/xml/QueryDataParserTest.java +++ b/src/test/java/org/commcare/xml/QueryDataParserTest.java @@ -7,6 +7,7 @@ import org.commcare.suite.model.QueryData; import org.commcare.suite.model.QueryPrompt; +import org.commcare.suite.model.QueryGroup; import org.javarosa.core.model.condition.EvaluationContext; import org.javarosa.core.model.instance.DataInstance; import org.javarosa.xml.util.InvalidStructureException; @@ -143,4 +144,26 @@ public void testParseQueryData_badNesting() throws XmlPullParserException, IOExc } catch (InvalidStructureException ignored) { } } + + @Test + public void testParseValueData_withGroupKeyAttribute() + throws InvalidStructureException, XmlPullParserException, + IOException, UnfullfilledRequirementsException { + String query = ""; + QueryPromptParser parser = ParserTestUtils.buildParser(query, QueryPromptParser.class); + QueryPrompt queryData = parser.parse(); + assertEquals("group_header_1", queryData.getGroupKey()); + } + + @Test + public void testParseValueData_withGroup() + throws InvalidStructureException, XmlPullParserException, + IOException, UnfullfilledRequirementsException { + String query = "" + + "" + + ""; + QueryGroupParser parser = ParserTestUtils.buildParser(query, QueryGroupParser.class); + QueryGroup queryData = parser.parse(); + assertEquals("group_header_0", queryData.getKey()); + } }