From c15d2796c459b14b4bd68b27aa40912bc15480c3 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 9 Sep 2022 11:26:08 +0100 Subject: [PATCH 01/36] Add dummy entities API for Collect --- .../java/org/javarosa/core/model/Entity.java | 16 ++++++++++++++++ .../core/model/instance/FormInstance.java | 19 +++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/javarosa/core/model/Entity.java diff --git a/src/main/java/org/javarosa/core/model/Entity.java b/src/main/java/org/javarosa/core/model/Entity.java new file mode 100644 index 000000000..937b60fa3 --- /dev/null +++ b/src/main/java/org/javarosa/core/model/Entity.java @@ -0,0 +1,16 @@ +package org.javarosa.core.model; + +import kotlin.Pair; + +import java.util.List; + +public class Entity { + + public final String dataset; + public final List> fields; + + public Entity(String dataset, List> fields) { + this.dataset = dataset; + this.fields = fields; + } +} diff --git a/src/main/java/org/javarosa/core/model/instance/FormInstance.java b/src/main/java/org/javarosa/core/model/instance/FormInstance.java index f6f8dee9b..d4fca69a4 100644 --- a/src/main/java/org/javarosa/core/model/instance/FormInstance.java +++ b/src/main/java/org/javarosa/core/model/instance/FormInstance.java @@ -16,12 +16,7 @@ package org.javarosa.core.model.instance; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Date; -import java.util.HashMap; - +import org.javarosa.core.model.Entity; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.data.IAnswerData; import org.javarosa.core.model.instance.utils.ITreeVisitor; @@ -34,6 +29,14 @@ import org.javarosa.core.util.externalizable.ExtWrapNullable; import org.javarosa.core.util.externalizable.PrototypeFactory; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + /** * This class represents the xform model instance @@ -420,4 +423,8 @@ public Object getMetaData(String fieldName) { } throw new IllegalArgumentException("No metadata field " + fieldName + " in the form instance storage system"); } + + public List getEntities() { + return new ArrayList<>(); + } } From 07986596d1bef6f385af514122eb9a43552a99c5 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 9 Sep 2022 17:42:32 +0100 Subject: [PATCH 02/36] Spike out exposing created entity once form is finalized --- .../java/org/javarosa/core/model/FormDef.java | 45 ++++++--- .../core/model/instance/FormInstance.java | 13 ++- .../xform/parse/FormInstanceParser.java | 27 +++-- .../org/javarosa/xform/parse/XFormParser.java | 98 +++++++++++-------- 4 files changed, 118 insertions(+), 65 deletions(-) diff --git a/src/main/java/org/javarosa/core/model/FormDef.java b/src/main/java/org/javarosa/core/model/FormDef.java index 7fdd505c7..5b4b26654 100644 --- a/src/main/java/org/javarosa/core/model/FormDef.java +++ b/src/main/java/org/javarosa/core/model/FormDef.java @@ -16,19 +16,7 @@ package org.javarosa.core.model; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; +import kotlin.Pair; import org.javarosa.core.log.WrappedException; import org.javarosa.core.model.TriggerableDag.EventNotifierAccessor; import org.javarosa.core.model.actions.ActionController; @@ -75,6 +63,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.stream.Collectors; + /** * Definition of a form. This has some meta data about the form definition and a * collection of groups together with question branching or skipping rules. @@ -89,6 +92,7 @@ public class FormDef implements IFormElement, Localizable, Persistable, IMetaDat public static final int TEMPLATING_RECURSION_LIMIT = 10; private static EventNotifier defaultEventNotifier = new EventNotifierSilent(); + private List> saveTos = new ArrayList<>(); /** * Takes a (possibly relative) reference, and makes it absolute based on its parent. @@ -1004,7 +1008,16 @@ public void preloadInstance(TreeElement node) { public boolean postProcessInstance() { actionController.triggerActionsFromEvent(Actions.EVENT_XFORMS_REVALIDATE, elementsWithActionTriggeredByToplevelEvent, this); - return postProcessInstance(mainInstance.getRoot()); + boolean instanceChanged = postProcessInstance(mainInstance.getRoot()); + + List> fields = saveTos.stream().map(saveTo -> { + IDataReference reference = saveTo.getFirst(); + String answer = mainInstance.resolveReference(reference).getValue().getDisplayText(); + return new Pair<>(saveTo.getSecond(), answer); + }).collect(Collectors.toList()); + mainInstance.addEntity(fields); + + return instanceChanged; } /** @@ -1690,4 +1703,8 @@ private String getFormXmlPath() { public HashMap getFormInstances() { return formInstances; } + + public void setSaveTos(List> saveTos) { + this.saveTos = saveTos; + } } diff --git a/src/main/java/org/javarosa/core/model/instance/FormInstance.java b/src/main/java/org/javarosa/core/model/instance/FormInstance.java index d4fca69a4..51edd616a 100644 --- a/src/main/java/org/javarosa/core/model/instance/FormInstance.java +++ b/src/main/java/org/javarosa/core/model/instance/FormInstance.java @@ -16,6 +16,7 @@ package org.javarosa.core.model.instance; +import kotlin.Pair; import org.javarosa.core.model.Entity; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.data.IAnswerData; @@ -51,6 +52,8 @@ public class FormInstance extends DataInstance implements Persistab public String schema; public String formVersion; public String uiVersion; + private String dataset; + private final List entities = new ArrayList<>(); private HashMap namespaces = new HashMap(); @@ -425,6 +428,14 @@ public Object getMetaData(String fieldName) { } public List getEntities() { - return new ArrayList<>(); + return entities; + } + + public void setDataset(String dataset) { + this.dataset = dataset; + } + + public void addEntity(List> fields) { + this.entities.add(new Entity(dataset, fields)); } } diff --git a/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java b/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java index ae09d4a1c..22a0868df 100644 --- a/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java +++ b/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java @@ -1,13 +1,5 @@ package org.javarosa.xform.parse; -import static org.javarosa.xform.parse.XFormParser.buildInstanceStructure; -import static org.javarosa.xform.parse.XFormParser.getVagueLocation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; import org.javarosa.core.model.Constants; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; @@ -29,6 +21,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.javarosa.xform.parse.XFormParser.buildInstanceStructure; +import static org.javarosa.xform.parse.XFormParser.getVagueLocation; + class FormInstanceParser { private static final Logger logger = LoggerFactory.getLogger(FormInstanceParser.class); @@ -85,6 +86,16 @@ FormInstance parseInstance(Element e, boolean isMainInstance, String name, Map meta = root.getChildrenWithName("meta"); + if (!meta.isEmpty()) { + List entities = meta.get(0).getChildrenWithName("entities:entity"); + + if (!entities.isEmpty()) { + String dataset = entities.get(0).getAttributeValue(null, "dataset"); + instanceModel.setDataset(dataset); + } + } } applyInstanceProperties(instanceModel); diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 493d25e90..522906e41 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -16,48 +16,7 @@ package org.javarosa.xform.parse; -import static java.util.Arrays.asList; -import static java.util.Collections.unmodifiableList; -import static java.util.Collections.unmodifiableSet; -import static org.javarosa.core.model.Constants.CONTROL_AUDIO_CAPTURE; -import static org.javarosa.core.model.Constants.CONTROL_FILE_CAPTURE; -import static org.javarosa.core.model.Constants.CONTROL_IMAGE_CHOOSE; -import static org.javarosa.core.model.Constants.CONTROL_INPUT; -import static org.javarosa.core.model.Constants.CONTROL_OSM_CAPTURE; -import static org.javarosa.core.model.Constants.CONTROL_RANGE; -import static org.javarosa.core.model.Constants.CONTROL_RANK; -import static org.javarosa.core.model.Constants.CONTROL_SECRET; -import static org.javarosa.core.model.Constants.CONTROL_SELECT_MULTI; -import static org.javarosa.core.model.Constants.CONTROL_SELECT_ONE; -import static org.javarosa.core.model.Constants.CONTROL_TRIGGER; -import static org.javarosa.core.model.Constants.CONTROL_UPLOAD; -import static org.javarosa.core.model.Constants.CONTROL_VIDEO_CAPTURE; -import static org.javarosa.core.model.Constants.DATATYPE_CHOICE; -import static org.javarosa.core.model.Constants.DATATYPE_MULTIPLE_ITEMS; -import static org.javarosa.core.model.Constants.XFTAG_UPLOAD; -import static org.javarosa.core.services.ProgramFlow.die; -import static org.javarosa.xform.parse.Constants.ID_ATTR; -import static org.javarosa.xform.parse.Constants.NODESET_ATTR; -import static org.javarosa.xform.parse.Constants.RANK; -import static org.javarosa.xform.parse.Constants.SELECT; -import static org.javarosa.xform.parse.Constants.SELECTONE; -import static org.javarosa.xform.parse.RandomizeHelper.cleanNodesetDefinition; -import static org.javarosa.xform.parse.RandomizeHelper.cleanSeedDefinition; -import static org.javarosa.xform.parse.RangeParser.populateQuestionWithRangeAttributes; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.GroupDef; @@ -114,6 +73,50 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableSet; +import static org.javarosa.core.model.Constants.CONTROL_AUDIO_CAPTURE; +import static org.javarosa.core.model.Constants.CONTROL_FILE_CAPTURE; +import static org.javarosa.core.model.Constants.CONTROL_IMAGE_CHOOSE; +import static org.javarosa.core.model.Constants.CONTROL_INPUT; +import static org.javarosa.core.model.Constants.CONTROL_OSM_CAPTURE; +import static org.javarosa.core.model.Constants.CONTROL_RANGE; +import static org.javarosa.core.model.Constants.CONTROL_RANK; +import static org.javarosa.core.model.Constants.CONTROL_SECRET; +import static org.javarosa.core.model.Constants.CONTROL_SELECT_MULTI; +import static org.javarosa.core.model.Constants.CONTROL_SELECT_ONE; +import static org.javarosa.core.model.Constants.CONTROL_TRIGGER; +import static org.javarosa.core.model.Constants.CONTROL_UPLOAD; +import static org.javarosa.core.model.Constants.CONTROL_VIDEO_CAPTURE; +import static org.javarosa.core.model.Constants.DATATYPE_CHOICE; +import static org.javarosa.core.model.Constants.DATATYPE_MULTIPLE_ITEMS; +import static org.javarosa.core.model.Constants.XFTAG_UPLOAD; +import static org.javarosa.core.services.ProgramFlow.die; +import static org.javarosa.xform.parse.Constants.ID_ATTR; +import static org.javarosa.xform.parse.Constants.NODESET_ATTR; +import static org.javarosa.xform.parse.Constants.RANK; +import static org.javarosa.xform.parse.Constants.SELECT; +import static org.javarosa.xform.parse.Constants.SELECTONE; +import static org.javarosa.xform.parse.RandomizeHelper.cleanNodesetDefinition; +import static org.javarosa.xform.parse.RandomizeHelper.cleanSeedDefinition; +import static org.javarosa.xform.parse.RangeParser.populateQuestionWithRangeAttributes; + /* droos: i think we need to start storing the contents of the s in the formdef again */ /** @@ -162,6 +165,7 @@ public class XFormParser implements IXFormParserFunctions { private Localizer localizer; private HashMap bindingsByID; private List bindings; + private List> saveTos; private List actionTargets; private List repeats; private List itemsets; @@ -337,6 +341,7 @@ private void initState() { mainInstanceNode = null; instanceNodes = new ArrayList<>(); instanceNodeIdStrs = new ArrayList<>(); + saveTos = new ArrayList<>(); itextKnownForms = new ArrayList<>(4); itextKnownForms.add("long"); @@ -483,6 +488,7 @@ private void parseDoc(String formXmlSrc, Map namespacePrefixesBy collapseRepeatGroups(_f); + _f.setSaveTos(saveTos); final FormInstanceParser instanceParser = new FormInstanceParser(_f, defaultNamespace, bindings, repeats, itemsets, selectOnes, multipleItems, actionTargets); @@ -1921,6 +1927,14 @@ private void parseBind(Element element) { private void addBinding(DataBinding binding) { bindings.add(binding); + IDataReference reference = binding.getReference(); + Optional maybeSaveTo = binding.getAdditionalAttributes().stream().filter(treeElement -> treeElement.getName().equals("saveto")).findFirst(); + + if (maybeSaveTo.isPresent()) { + String saveTo = maybeSaveTo.get().getAttributeValue(); + saveTos.add(new Pair<>(reference, saveTo)); + } + if (binding.getId() != null) { if (bindingsByID.put(binding.getId(), binding) != null) { throw new XFormParseException("XForm Parse: s with duplicate ID: '" + binding.getId() + "'"); From 19e83e7a0c096e0bde491eafe70184508b47fe8d Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Tue, 13 Sep 2022 11:19:51 +0100 Subject: [PATCH 03/36] Make sure entities are only created when they should be --- .../core/model/instance/FormInstance.java | 4 +- .../xform/parse/FormInstanceParser.java | 12 ++- .../java/org/javarosa/core/test/Scenario.java | 75 ++++++++------- .../core/util/BindBuilderXFormsElement.java | 5 + .../org/javarosa/core/util/XFormsElement.java | 8 +- .../javarosa/xform/parse/EntitiesTest.java | 94 +++++++++++++++++++ 6 files changed, 155 insertions(+), 43 deletions(-) create mode 100644 src/test/java/org/javarosa/xform/parse/EntitiesTest.java diff --git a/src/main/java/org/javarosa/core/model/instance/FormInstance.java b/src/main/java/org/javarosa/core/model/instance/FormInstance.java index 51edd616a..f9af367dc 100644 --- a/src/main/java/org/javarosa/core/model/instance/FormInstance.java +++ b/src/main/java/org/javarosa/core/model/instance/FormInstance.java @@ -436,6 +436,8 @@ public void setDataset(String dataset) { } public void addEntity(List> fields) { - this.entities.add(new Entity(dataset, fields)); + if (dataset != null) { + this.entities.add(new Entity(dataset, fields)); + } } } diff --git a/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java b/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java index 22a0868df..8c94b8ac8 100644 --- a/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java +++ b/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java @@ -89,11 +89,15 @@ FormInstance parseInstance(Element e, boolean isMainInstance, String name, Map meta = root.getChildrenWithName("meta"); if (!meta.isEmpty()) { - List entities = meta.get(0).getChildrenWithName("entities:entity"); + List entity = meta.get(0).getChildrenWithName("entities:entity"); - if (!entities.isEmpty()) { - String dataset = entities.get(0).getAttributeValue(null, "dataset"); - instanceModel.setDataset(dataset); + if (!entity.isEmpty()) { + List create = entity.get(0).getChildrenWithName("entities:create"); + + if (!create.isEmpty()) { + String dataset = entity.get(0).getAttributeValue(null, "dataset"); + instanceModel.setDataset(dataset); + } } } } diff --git a/src/test/java/org/javarosa/core/test/Scenario.java b/src/test/java/org/javarosa/core/test/Scenario.java index 8ac2d8480..ff282e038 100644 --- a/src/test/java/org/javarosa/core/test/Scenario.java +++ b/src/test/java/org/javarosa/core/test/Scenario.java @@ -16,41 +16,6 @@ package org.javarosa.core.test; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.nio.file.Files.createTempDirectory; -import static java.nio.file.Files.createTempFile; -import static java.nio.file.Files.delete; -import static java.nio.file.Files.newInputStream; -import static java.nio.file.Files.newOutputStream; -import static java.nio.file.Files.write; -import static java.nio.file.StandardOpenOption.CREATE; -import static java.util.stream.Collectors.joining; -import static org.javarosa.core.model.instance.TreeReference.INDEX_TEMPLATE; -import static org.javarosa.form.api.FormEntryController.EVENT_BEGINNING_OF_FORM; -import static org.javarosa.form.api.FormEntryController.EVENT_END_OF_FORM; -import static org.javarosa.form.api.FormEntryController.EVENT_GROUP; -import static org.javarosa.form.api.FormEntryController.EVENT_PROMPT_NEW_REPEAT; -import static org.javarosa.form.api.FormEntryController.EVENT_QUESTION; -import static org.javarosa.form.api.FormEntryController.EVENT_REPEAT; -import static org.javarosa.form.api.FormEntryController.EVENT_REPEAT_JUNCTURE; -import static org.javarosa.test.utils.ResourcePathHelper.r; -import static org.javarosa.xpath.expr.XPathPathExpr.INIT_CONTEXT_RELATIVE; -import static org.javarosa.xpath.expr.XPathStep.AXIS_ATTRIBUTE; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Path; -import java.sql.Date; -import java.time.LocalDate; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.javarosa.core.model.CoreModelModule; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.FormIndex; @@ -94,6 +59,42 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.sql.Date; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.Files.createTempDirectory; +import static java.nio.file.Files.createTempFile; +import static java.nio.file.Files.delete; +import static java.nio.file.Files.newInputStream; +import static java.nio.file.Files.newOutputStream; +import static java.nio.file.Files.write; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.util.stream.Collectors.joining; +import static org.javarosa.core.model.instance.TreeReference.INDEX_TEMPLATE; +import static org.javarosa.form.api.FormEntryController.EVENT_BEGINNING_OF_FORM; +import static org.javarosa.form.api.FormEntryController.EVENT_END_OF_FORM; +import static org.javarosa.form.api.FormEntryController.EVENT_GROUP; +import static org.javarosa.form.api.FormEntryController.EVENT_PROMPT_NEW_REPEAT; +import static org.javarosa.form.api.FormEntryController.EVENT_QUESTION; +import static org.javarosa.form.api.FormEntryController.EVENT_REPEAT; +import static org.javarosa.form.api.FormEntryController.EVENT_REPEAT_JUNCTURE; +import static org.javarosa.test.utils.ResourcePathHelper.r; +import static org.javarosa.xpath.expr.XPathPathExpr.INIT_CONTEXT_RELATIVE; +import static org.javarosa.xpath.expr.XPathStep.AXIS_ATTRIBUTE; + /** *
* Warning This class is probably incomplete. If your testing requirements @@ -319,6 +320,10 @@ public void trace(String msg) { log.info("==============================================================================="); } + public void finalizeInstance() { + formDef.postProcessInstance(); + } + public enum AnswerResult { OK(0), REQUIRED_BUT_EMPTY(1), CONSTRAINT_VIOLATED(2); diff --git a/src/test/java/org/javarosa/core/util/BindBuilderXFormsElement.java b/src/test/java/org/javarosa/core/util/BindBuilderXFormsElement.java index 3debeb345..359ff0554 100644 --- a/src/test/java/org/javarosa/core/util/BindBuilderXFormsElement.java +++ b/src/test/java/org/javarosa/core/util/BindBuilderXFormsElement.java @@ -80,6 +80,11 @@ public BindBuilderXFormsElement readonly(String expression) { return this; } + public BindBuilderXFormsElement saveTo(String expression) { + attributes.put("entities:saveto", expression); + return this; + } + @Override public String getName() { return "bind"; diff --git a/src/test/java/org/javarosa/core/util/XFormsElement.java b/src/test/java/org/javarosa/core/util/XFormsElement.java index 9df1e2097..960b9d072 100644 --- a/src/test/java/org/javarosa/core/util/XFormsElement.java +++ b/src/test/java/org/javarosa/core/util/XFormsElement.java @@ -16,12 +16,12 @@ package org.javarosa.core.util; -import static java.util.Collections.emptyMap; - import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import static java.util.Collections.emptyMap; + public interface XFormsElement { static String buildAttributesString(Map attributes) { StringBuilder attributesStringBuilder = new StringBuilder(); @@ -67,7 +67,9 @@ static XFormsElement html(HeadXFormsElement head, BodyXFormsElement body) { "xmlns=\"http://www.w3.org/2002/xforms\" " + "xmlns:h=\"http://www.w3.org/1999/xhtml\" " + "xmlns:jr=\"http://openrosa.org/javarosa\" " + - "xmlns:odk=\"http://www.opendatakit.org/xforms\"", + "xmlns:odk=\"http://www.opendatakit.org/xforms\" "+ + "xmlns:orx=\"http://openrosa.org/xforms\" "+ + "xmlns:entities=\"http://www.opendatakit.org/xforms/entities\"", head, body ); } diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java new file mode 100644 index 000000000..3a17d1362 --- /dev/null +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -0,0 +1,94 @@ +package org.javarosa.xform.parse; + +import kotlin.Pair; +import org.javarosa.core.model.Entity; +import org.javarosa.core.test.Scenario; +import org.javarosa.core.util.XFormsElement; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.javarosa.core.util.BindBuilderXFormsElement.bind; +import static org.javarosa.core.util.XFormsElement.body; +import static org.javarosa.core.util.XFormsElement.head; +import static org.javarosa.core.util.XFormsElement.input; +import static org.javarosa.core.util.XFormsElement.mainInstance; +import static org.javarosa.core.util.XFormsElement.model; +import static org.javarosa.core.util.XFormsElement.t; +import static org.javarosa.core.util.XFormsElement.title; + +public class EntitiesTest { + + @Test + public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOException { + Scenario scenario = Scenario.init("Entity form", XFormsElement.html( + head( + title("Entity form"), + model( + mainInstance( + t("data id=\"entity-form\"", + t("name"), + t("orx:meta", + t("entities:entity dataset=\"people\" id=\"\"", + t("entities:label") + ) + ) + ) + ), + bind("/data/name").type("string").saveTo("name"), + bind("/data/meta/entities:entity/entities:label").type("string").calculate("/data/name") + ) + ), + body( + input("/data/name") + ) + )); + + scenario.next(); + scenario.answer("Tom Wambsgans"); + scenario.finalizeInstance(); + + List entities = scenario.getFormDef().getMainInstance().getEntities(); + assertThat(entities.size(), equalTo(0)); + } + + @Test + public void fillingFormWithCreate_makesEntityAvailable() throws IOException { + Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("entities:entity dataset=\"people\" id=\"\"", + t("entities:create"), + t("entities:label") + ) + ) + ) + ), + bind("/data/name").type("string").saveTo("name"), + bind("/data/meta/entities:entity/entities:label").type("string").calculate("/data/name") + ) + ), + body( + input("/data/name") + ) + )); + + scenario.next(); + scenario.answer("Tom Wambsgans"); + scenario.finalizeInstance(); + + List entities = scenario.getFormDef().getMainInstance().getEntities(); + assertThat(entities.size(), equalTo(1)); + assertThat(entities.get(0).dataset, equalTo("people")); + assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); + } +} From c6e0d57a949e4636cc8250826e3fadcb1c3fb4d4 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Tue, 13 Sep 2022 14:02:34 +0100 Subject: [PATCH 04/36] Support caching form def/instance with entities --- .../java/org/javarosa/core/model/FormDef.java | 14 ++++-- .../core/model/instance/FormInstance.java | 4 +- .../org/javarosa/xform/parse/XFormParser.java | 7 ++- .../javarosa/xform/parse/EntitiesTest.java | 50 +++++++++++++++---- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/javarosa/core/model/FormDef.java b/src/main/java/org/javarosa/core/model/FormDef.java index 5b4b26654..6971748a6 100644 --- a/src/main/java/org/javarosa/core/model/FormDef.java +++ b/src/main/java/org/javarosa/core/model/FormDef.java @@ -92,7 +92,7 @@ public class FormDef implements IFormElement, Localizable, Persistable, IMetaDat public static final int TEMPLATING_RECURSION_LIMIT = 10; private static EventNotifier defaultEventNotifier = new EventNotifierSilent(); - private List> saveTos = new ArrayList<>(); + private HashMap saveTos = new HashMap<>(); /** * Takes a (possibly relative) reference, and makes it absolute based on its parent. @@ -1010,10 +1010,10 @@ public boolean postProcessInstance() { actionController.triggerActionsFromEvent(Actions.EVENT_XFORMS_REVALIDATE, elementsWithActionTriggeredByToplevelEvent, this); boolean instanceChanged = postProcessInstance(mainInstance.getRoot()); - List> fields = saveTos.stream().map(saveTo -> { - IDataReference reference = saveTo.getFirst(); + List> fields = saveTos.entrySet().stream().map(saveTo -> { + IDataReference reference = saveTo.getKey(); String answer = mainInstance.resolveReference(reference).getValue().getDisplayText(); - return new Pair<>(saveTo.getSecond(), answer); + return new Pair<>(saveTo.getValue(), answer); }).collect(Collectors.toList()); mainInstance.addEntity(fields); @@ -1125,6 +1125,8 @@ public void readExternal(DataInputStream dis, PrototypeFactory pf) throws IOExce List treeReferencesWithActions = (List) ExtUtil.read(dis, new ExtWrapListPoly(), pf); elementsWithActionTriggeredByToplevelEvent = getElementsFromReferences(treeReferencesWithActions); + + saveTos = (HashMap) ExtUtil.read(dis, new ExtWrapMap(XPathReference.class, String.class), pf); } /** @@ -1228,6 +1230,8 @@ public void writeExternal(DataOutputStream dos) throws IOException { ExtUtil.write(dos, new ExtWrapNullable(actionController)); ExtUtil.write(dos, new ExtWrapListPoly(new ArrayList<>(actions))); ExtUtil.write(dos, new ExtWrapListPoly(getReferencesFromElements(elementsWithActionTriggeredByToplevelEvent))); + + ExtUtil.write(dos, new ExtWrapMap(saveTos)); } /** @@ -1704,7 +1708,7 @@ public HashMap getFormInstances() { return formInstances; } - public void setSaveTos(List> saveTos) { + public void setSaveTos(HashMap saveTos) { this.saveTos = saveTos; } } diff --git a/src/main/java/org/javarosa/core/model/instance/FormInstance.java b/src/main/java/org/javarosa/core/model/instance/FormInstance.java index f9af367dc..b8c6ef9fa 100644 --- a/src/main/java/org/javarosa/core/model/instance/FormInstance.java +++ b/src/main/java/org/javarosa/core/model/instance/FormInstance.java @@ -318,10 +318,9 @@ public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOExcep super.readExternal(in, pf); schema = (String) ExtUtil.read(in, new ExtWrapNullable(String.class), pf); dateSaved = (Date) ExtUtil.read(in, new ExtWrapNullable(Date.class), pf); - namespaces = (HashMap)ExtUtil.read(in, new ExtWrapMap(String.class, String.class)); + dataset = (String) ExtUtil.read(in, new ExtWrapNullable(String.class), pf); setRoot((TreeElement) ExtUtil.read(in, TreeElement.class, pf)); - } public void writeExternal(DataOutputStream out) throws IOException { @@ -329,6 +328,7 @@ public void writeExternal(DataOutputStream out) throws IOException { ExtUtil.write(out, new ExtWrapNullable(schema)); ExtUtil.write(out, new ExtWrapNullable(dateSaved)); ExtUtil.write(out, new ExtWrapMap(namespaces)); + ExtUtil.write(out, new ExtWrapNullable(dataset)); ExtUtil.write(out, getRoot()); } diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 522906e41..5ffb0d0f6 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -16,7 +16,6 @@ package org.javarosa.xform.parse; -import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.GroupDef; @@ -165,7 +164,7 @@ public class XFormParser implements IXFormParserFunctions { private Localizer localizer; private HashMap bindingsByID; private List bindings; - private List> saveTos; + private HashMap saveTos; private List actionTargets; private List repeats; private List itemsets; @@ -341,7 +340,7 @@ private void initState() { mainInstanceNode = null; instanceNodes = new ArrayList<>(); instanceNodeIdStrs = new ArrayList<>(); - saveTos = new ArrayList<>(); + saveTos = new HashMap<>(); itextKnownForms = new ArrayList<>(4); itextKnownForms.add("long"); @@ -1932,7 +1931,7 @@ private void addBinding(DataBinding binding) { if (maybeSaveTo.isPresent()) { String saveTo = maybeSaveTo.get().getAttributeValue(); - saveTos.add(new Pair<>(reference, saveTo)); + saveTos.put((XPathReference) reference, saveTo); } if (binding.getId() != null) { diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index 3a17d1362..13622fdf3 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -4,6 +4,7 @@ import org.javarosa.core.model.Entity; import org.javarosa.core.test.Scenario; import org.javarosa.core.util.XFormsElement; +import org.javarosa.core.util.externalizable.DeserializationException; import org.junit.Test; import java.io.IOException; @@ -33,14 +34,11 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti t("data id=\"entity-form\"", t("name"), t("orx:meta", - t("entities:entity dataset=\"people\" id=\"\"", - t("entities:label") - ) + t("entities:entity dataset=\"people\" id=\"\"") ) ) ), - bind("/data/name").type("string").saveTo("name"), - bind("/data/meta/entities:entity/entities:label").type("string").calculate("/data/name") + bind("/data/name").type("string").saveTo("name") ) ), body( @@ -67,14 +65,12 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { t("name"), t("orx:meta", t("entities:entity dataset=\"people\" id=\"\"", - t("entities:create"), - t("entities:label") + t("entities:create") ) ) ) ), - bind("/data/name").type("string").saveTo("name"), - bind("/data/meta/entities:entity/entities:label").type("string").calculate("/data/name") + bind("/data/name").type("string").saveTo("name") ) ), body( @@ -91,4 +87,40 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { assertThat(entities.get(0).dataset, equalTo("people")); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } + + @Test + public void entityFormCanBeSerialized() throws IOException, DeserializationException { + Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("entities:entity dataset=\"people\" id=\"\"", + t("entities:create") + ) + ) + ) + ), + bind("/data/name").type("string").saveTo("name") + ) + ), + body( + input("/data/name") + ) + )); + + Scenario deserializedScenario = scenario.serializeAndDeserializeForm(); + + deserializedScenario.next(); + deserializedScenario.answer("Shiv Roy"); + deserializedScenario.finalizeInstance(); + + List entities = deserializedScenario.getFormDef().getMainInstance().getEntities(); + assertThat(entities.size(), equalTo(1)); + assertThat(entities.get(0).dataset, equalTo("people")); + assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Shiv Roy")))); + } } From 6b458a770d671e8a1bb8b3a674c668434070d98a Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Tue, 13 Sep 2022 14:24:09 +0100 Subject: [PATCH 05/36] Move back to using List for saveto refs --- .../java/org/javarosa/core/model/FormDef.java | 17 ++++++++++------- .../org/javarosa/xform/parse/XFormParser.java | 7 ++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/javarosa/core/model/FormDef.java b/src/main/java/org/javarosa/core/model/FormDef.java index 6971748a6..5d3167abb 100644 --- a/src/main/java/org/javarosa/core/model/FormDef.java +++ b/src/main/java/org/javarosa/core/model/FormDef.java @@ -92,7 +92,7 @@ public class FormDef implements IFormElement, Localizable, Persistable, IMetaDat public static final int TEMPLATING_RECURSION_LIMIT = 10; private static EventNotifier defaultEventNotifier = new EventNotifierSilent(); - private HashMap saveTos = new HashMap<>(); + private List> saveTos = new ArrayList<>(); /** * Takes a (possibly relative) reference, and makes it absolute based on its parent. @@ -1010,10 +1010,10 @@ public boolean postProcessInstance() { actionController.triggerActionsFromEvent(Actions.EVENT_XFORMS_REVALIDATE, elementsWithActionTriggeredByToplevelEvent, this); boolean instanceChanged = postProcessInstance(mainInstance.getRoot()); - List> fields = saveTos.entrySet().stream().map(saveTo -> { - IDataReference reference = saveTo.getKey(); + List> fields = saveTos.stream().map(saveTo -> { + IDataReference reference = saveTo.getFirst(); String answer = mainInstance.resolveReference(reference).getValue().getDisplayText(); - return new Pair<>(saveTo.getValue(), answer); + return new Pair<>(saveTo.getSecond(), answer); }).collect(Collectors.toList()); mainInstance.addEntity(fields); @@ -1126,7 +1126,8 @@ public void readExternal(DataInputStream dis, PrototypeFactory pf) throws IOExce List treeReferencesWithActions = (List) ExtUtil.read(dis, new ExtWrapListPoly(), pf); elementsWithActionTriggeredByToplevelEvent = getElementsFromReferences(treeReferencesWithActions); - saveTos = (HashMap) ExtUtil.read(dis, new ExtWrapMap(XPathReference.class, String.class), pf); + HashMap saveToMap = (HashMap) ExtUtil.read(dis, new ExtWrapMap(XPathReference.class, String.class), pf); + saveTos = saveToMap.entrySet().stream().map(entry -> new Pair<>(entry.getKey(), entry.getValue())).collect(Collectors.toList()); } /** @@ -1231,7 +1232,9 @@ public void writeExternal(DataOutputStream dos) throws IOException { ExtUtil.write(dos, new ExtWrapListPoly(new ArrayList<>(actions))); ExtUtil.write(dos, new ExtWrapListPoly(getReferencesFromElements(elementsWithActionTriggeredByToplevelEvent))); - ExtUtil.write(dos, new ExtWrapMap(saveTos)); + Map saveTosMap = saveTos.stream() + .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)); + ExtUtil.write(dos, new ExtWrapMap(new HashMap<>(saveTosMap))); } /** @@ -1708,7 +1711,7 @@ public HashMap getFormInstances() { return formInstances; } - public void setSaveTos(HashMap saveTos) { + public void setSaveTos(List> saveTos) { this.saveTos = saveTos; } } diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 5ffb0d0f6..ea2f4c7bf 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -16,6 +16,7 @@ package org.javarosa.xform.parse; +import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.GroupDef; @@ -164,7 +165,7 @@ public class XFormParser implements IXFormParserFunctions { private Localizer localizer; private HashMap bindingsByID; private List bindings; - private HashMap saveTos; + private List> saveTos; private List actionTargets; private List repeats; private List itemsets; @@ -340,7 +341,7 @@ private void initState() { mainInstanceNode = null; instanceNodes = new ArrayList<>(); instanceNodeIdStrs = new ArrayList<>(); - saveTos = new HashMap<>(); + saveTos = new ArrayList<>(); itextKnownForms = new ArrayList<>(4); itextKnownForms.add("long"); @@ -1931,7 +1932,7 @@ private void addBinding(DataBinding binding) { if (maybeSaveTo.isPresent()) { String saveTo = maybeSaveTo.get().getAttributeValue(); - saveTos.put((XPathReference) reference, saveTo); + saveTos.add(new Pair<>((XPathReference) reference, saveTo)); } if (binding.getId() != null) { From 0b036bde7496f1900f4f6da6c0df200951ad146f Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 15 Sep 2022 17:08:26 +0100 Subject: [PATCH 06/36] Remove id attribute from test forms --- src/test/java/org/javarosa/xform/parse/EntitiesTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index 13622fdf3..a28a3f274 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -34,7 +34,7 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti t("data id=\"entity-form\"", t("name"), t("orx:meta", - t("entities:entity dataset=\"people\" id=\"\"") + t("entities:entity dataset=\"people\"") ) ) ), @@ -64,7 +64,7 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { t("data id=\"create-entity-form\"", t("name"), t("orx:meta", - t("entities:entity dataset=\"people\" id=\"\"", + t("entities:entity dataset=\"people\"", t("entities:create") ) ) @@ -98,7 +98,7 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep t("data id=\"create-entity-form\"", t("name"), t("orx:meta", - t("entities:entity dataset=\"people\" id=\"\"", + t("entities:entity dataset=\"people\"", t("entities:create") ) ) From 7b31a822a035ee7968688cb4d01dd049e1bdb9a8 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 21 Sep 2022 15:33:52 +0100 Subject: [PATCH 07/36] Make sure namespace prefix isn't hardcoded for entities --- .../xform/parse/FormInstanceParser.java | 4 +- .../core/util/BindBuilderXFormsElement.java | 4 +- .../org/javarosa/core/util/XFormsElement.java | 24 ++++++++- .../javarosa/xform/parse/EntitiesTest.java | 52 +++++++++++++++++-- 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java b/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java index 8c94b8ac8..b457070b1 100644 --- a/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java +++ b/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java @@ -89,10 +89,10 @@ FormInstance parseInstance(Element e, boolean isMainInstance, String name, Map meta = root.getChildrenWithName("meta"); if (!meta.isEmpty()) { - List entity = meta.get(0).getChildrenWithName("entities:entity"); + List entity = meta.get(0).getChildrenWithName("entity"); if (!entity.isEmpty()) { - List create = entity.get(0).getChildrenWithName("entities:create"); + List create = entity.get(0).getChildrenWithName("create"); if (!create.isEmpty()) { String dataset = entity.get(0).getAttributeValue(null, "dataset"); diff --git a/src/test/java/org/javarosa/core/util/BindBuilderXFormsElement.java b/src/test/java/org/javarosa/core/util/BindBuilderXFormsElement.java index 359ff0554..e5442b82a 100644 --- a/src/test/java/org/javarosa/core/util/BindBuilderXFormsElement.java +++ b/src/test/java/org/javarosa/core/util/BindBuilderXFormsElement.java @@ -80,8 +80,8 @@ public BindBuilderXFormsElement readonly(String expression) { return this; } - public BindBuilderXFormsElement saveTo(String expression) { - attributes.put("entities:saveto", expression); + public BindBuilderXFormsElement withAttribute(String namespace, String name, String expression) { + attributes.put(namespace + ":" + name, expression); return this; } diff --git a/src/test/java/org/javarosa/core/util/XFormsElement.java b/src/test/java/org/javarosa/core/util/XFormsElement.java index 960b9d072..f005ba299 100644 --- a/src/test/java/org/javarosa/core/util/XFormsElement.java +++ b/src/test/java/org/javarosa/core/util/XFormsElement.java @@ -16,9 +16,14 @@ package org.javarosa.core.util; +import kotlin.Pair; + import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import static java.util.Collections.emptyMap; @@ -68,12 +73,27 @@ static XFormsElement html(HeadXFormsElement head, BodyXFormsElement body) { "xmlns:h=\"http://www.w3.org/1999/xhtml\" " + "xmlns:jr=\"http://openrosa.org/javarosa\" " + "xmlns:odk=\"http://www.opendatakit.org/xforms\" "+ - "xmlns:orx=\"http://openrosa.org/xforms\" "+ - "xmlns:entities=\"http://www.opendatakit.org/xforms/entities\"", + "xmlns:orx=\"http://openrosa.org/xforms\"", head, body ); } + static XFormsElement html(List> additionalNamespaces, HeadXFormsElement head, BodyXFormsElement body) { + String additionalNamespacesString = additionalNamespaces.stream() + .map(namespace -> "xmlns:" + namespace.getFirst() + "=" + namespace.getSecond() + " ") + .collect(Collectors.joining()); + + return t("h:html " + + "xmlns=\"http://www.w3.org/2002/xforms\" " + + "xmlns:h=\"http://www.w3.org/1999/xhtml\" " + + "xmlns:jr=\"http://openrosa.org/javarosa\" " + + "xmlns:odk=\"http://www.opendatakit.org/xforms\" " + + "xmlns:orx=\"http://openrosa.org/xforms\" " + + additionalNamespacesString, + head, body + ); + } + static HeadXFormsElement head(XFormsElement... children) { return new HeadXFormsElement(children); } diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index a28a3f274..a18d2e236 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -27,6 +27,9 @@ public class EntitiesTest { @Test public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOException { Scenario scenario = Scenario.init("Entity form", XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), head( title("Entity form"), model( @@ -38,7 +41,7 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti ) ) ), - bind("/data/name").type("string").saveTo("name") + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") ) ), body( @@ -57,6 +60,9 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti @Test public void fillingFormWithCreate_makesEntityAvailable() throws IOException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), head( title("Create entity form"), model( @@ -70,7 +76,7 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { ) ) ), - bind("/data/name").type("string").saveTo("name") + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") ) ), body( @@ -91,6 +97,9 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { @Test public void entityFormCanBeSerialized() throws IOException, DeserializationException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), head( title("Create entity form"), model( @@ -104,7 +113,7 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep ) ) ), - bind("/data/name").type("string").saveTo("name") + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") ) ), body( @@ -123,4 +132,41 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep assertThat(entities.get(0).dataset, equalTo("people")); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Shiv Roy")))); } + + @Test + public void entitiesNamespaceWorksRegardlessOfName() throws IOException, DeserializationException { + Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + asList( + new Pair<>("blah", "http://www.opendatakit.org/xforms/entities") + ), + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("blah:entity dataset=\"people\"", + t("blah:create") + ) + ) + ) + ), + bind("/data/name").type("string").withAttribute("blah", "saveto", "name") + ) + ), + body( + input("/data/name") + ) + )); + + scenario.next(); + scenario.answer("Tom Wambsgans"); + scenario.finalizeInstance(); + + List entities = scenario.getFormDef().getMainInstance().getEntities(); + assertThat(entities.size(), equalTo(1)); + assertThat(entities.get(0).dataset, equalTo("people")); + assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); + } } From d4f3c49b7afad3aed77fa64664ecbfaf17ec075f Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 21 Sep 2022 15:44:13 +0100 Subject: [PATCH 08/36] Add test for selects --- .../javarosa/xform/parse/EntitiesTest.java | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index a18d2e236..12c6707d8 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -17,8 +17,10 @@ import static org.javarosa.core.util.XFormsElement.body; import static org.javarosa.core.util.XFormsElement.head; import static org.javarosa.core.util.XFormsElement.input; +import static org.javarosa.core.util.XFormsElement.item; import static org.javarosa.core.util.XFormsElement.mainInstance; import static org.javarosa.core.util.XFormsElement.model; +import static org.javarosa.core.util.XFormsElement.select1; import static org.javarosa.core.util.XFormsElement.t; import static org.javarosa.core.util.XFormsElement.title; @@ -166,7 +168,42 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria List entities = scenario.getFormDef().getMainInstance().getEntities(); assertThat(entities.size(), equalTo(1)); - assertThat(entities.get(0).dataset, equalTo("people")); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } + + @Test + public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEntity() throws IOException { + Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("team"), + t("orx:meta", + t("entities:entity dataset=\"people\"", + t("entities:create") + ) + ) + ) + ), + bind("/data/team").type("string").withAttribute("entities", "saveto", "team") + ) + ), + body( + select1("/data/team", item("kendall", "Kendall"), item("logan", "Logan")) + ) + )); + + scenario.next(); + scenario.answer(scenario.choicesOf("/data/team").get(0)); + scenario.finalizeInstance(); + + List entities = scenario.getFormDef().getMainInstance().getEntities(); + assertThat(entities.size(), equalTo(1)); + assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("team", "kendall")))); + } } From b547b78a602b6a006fbcf1a6d3e47be32c01a64e Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 21 Sep 2022 16:48:09 +0100 Subject: [PATCH 09/36] Reimplement entity parsing and processing using plugin style architecture --- .../java/org/javarosa/core/model/FormDef.java | 43 +++++---- .../core/model/instance/FormInstance.java | 23 +---- .../externalizable/ExtWrapExternalizable.java | 48 ++++++++++ .../{core/model => entities}/Entity.java | 2 +- .../entities/EntityFormPostProcessor.java | 42 +++++++++ .../entities/EntityXFormParserFactory.java | 46 ++++++++++ .../entities/internal/EntitiesAttachment.java | 18 ++++ .../internal/EntityFormParseAttachment.java | 56 ++++++++++++ .../internal/EntityFormParseProcessor.java | 58 ++++++++++++ .../form/api/FormEntryController.java | 16 +++- .../org/javarosa/form/api/FormEntryModel.java | 12 +++ .../javarosa/form/api/FormPostProcessor.java | 6 ++ .../parse/BindingAttributeProcessor.java | 12 +++ .../xform/parse/FormDefProcessor.java | 7 ++ .../xform/parse/FormInstanceParser.java | 14 --- .../StandardBindAttributesProcessor.java | 29 +++++- .../org/javarosa/xform/parse/XFormParser.java | 34 +++---- .../java/org/javarosa/core/test/Scenario.java | 6 +- .../javarosa/xform/parse/EntitiesTest.java | 91 ++++++++++++++++--- 19 files changed, 472 insertions(+), 91 deletions(-) create mode 100644 src/main/java/org/javarosa/core/util/externalizable/ExtWrapExternalizable.java rename src/main/java/org/javarosa/{core/model => entities}/Entity.java (89%) create mode 100644 src/main/java/org/javarosa/entities/EntityFormPostProcessor.java create mode 100644 src/main/java/org/javarosa/entities/EntityXFormParserFactory.java create mode 100644 src/main/java/org/javarosa/entities/internal/EntitiesAttachment.java create mode 100644 src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java create mode 100644 src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java create mode 100644 src/main/java/org/javarosa/form/api/FormPostProcessor.java create mode 100644 src/main/java/org/javarosa/xform/parse/BindingAttributeProcessor.java create mode 100644 src/main/java/org/javarosa/xform/parse/FormDefProcessor.java diff --git a/src/main/java/org/javarosa/core/model/FormDef.java b/src/main/java/org/javarosa/core/model/FormDef.java index 5d3167abb..44513e7e6 100644 --- a/src/main/java/org/javarosa/core/model/FormDef.java +++ b/src/main/java/org/javarosa/core/model/FormDef.java @@ -16,7 +16,6 @@ package org.javarosa.core.model; -import kotlin.Pair; import org.javarosa.core.log.WrappedException; import org.javarosa.core.model.TriggerableDag.EventNotifierAccessor; import org.javarosa.core.model.actions.ActionController; @@ -45,10 +44,12 @@ import org.javarosa.core.services.storage.Persistable; import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.core.util.externalizable.ExtUtil; +import org.javarosa.core.util.externalizable.ExtWrapExternalizable; import org.javarosa.core.util.externalizable.ExtWrapListPoly; import org.javarosa.core.util.externalizable.ExtWrapMap; import org.javarosa.core.util.externalizable.ExtWrapNullable; import org.javarosa.core.util.externalizable.ExtWrapTagged; +import org.javarosa.core.util.externalizable.Externalizable; import org.javarosa.core.util.externalizable.PrototypeFactory; import org.javarosa.debug.EvaluationResult; import org.javarosa.debug.Event; @@ -76,7 +77,8 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; /** * Definition of a form. This has some meta data about the form definition and a @@ -92,7 +94,7 @@ public class FormDef implements IFormElement, Localizable, Persistable, IMetaDat public static final int TEMPLATING_RECURSION_LIMIT = 10; private static EventNotifier defaultEventNotifier = new EventNotifierSilent(); - private List> saveTos = new ArrayList<>(); + private Map parseAttachments = new HashMap<>(); /** * Takes a (possibly relative) reference, and makes it absolute based on its parent. @@ -1006,18 +1008,9 @@ public void preloadInstance(TreeElement node) { // } } - public boolean postProcessInstance() { + public void postProcessInstance() { actionController.triggerActionsFromEvent(Actions.EVENT_XFORMS_REVALIDATE, elementsWithActionTriggeredByToplevelEvent, this); - boolean instanceChanged = postProcessInstance(mainInstance.getRoot()); - - List> fields = saveTos.stream().map(saveTo -> { - IDataReference reference = saveTo.getFirst(); - String answer = mainInstance.resolveReference(reference).getValue().getDisplayText(); - return new Pair<>(saveTo.getSecond(), answer); - }).collect(Collectors.toList()); - mainInstance.addEntity(fields); - - return instanceChanged; + postProcessInstance(mainInstance.getRoot()); } /** @@ -1126,8 +1119,7 @@ public void readExternal(DataInputStream dis, PrototypeFactory pf) throws IOExce List treeReferencesWithActions = (List) ExtUtil.read(dis, new ExtWrapListPoly(), pf); elementsWithActionTriggeredByToplevelEvent = getElementsFromReferences(treeReferencesWithActions); - HashMap saveToMap = (HashMap) ExtUtil.read(dis, new ExtWrapMap(XPathReference.class, String.class), pf); - saveTos = saveToMap.entrySet().stream().map(entry -> new Pair<>(entry.getKey(), entry.getValue())).collect(Collectors.toList()); + parseAttachments = (HashMap) ExtUtil.read(dis, new ExtWrapMap(String.class, new ExtWrapExternalizable()), pf); } /** @@ -1232,9 +1224,12 @@ public void writeExternal(DataOutputStream dos) throws IOException { ExtUtil.write(dos, new ExtWrapListPoly(new ArrayList<>(actions))); ExtUtil.write(dos, new ExtWrapListPoly(getReferencesFromElements(elementsWithActionTriggeredByToplevelEvent))); - Map saveTosMap = saveTos.stream() - .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)); - ExtUtil.write(dos, new ExtWrapMap(new HashMap<>(saveTosMap))); + HashMap wrappedParseAttachments = new HashMap<>(); + parseAttachments.forEach((key, value) -> { + wrappedParseAttachments.put(key, new ExtWrapExternalizable(value)); + }); + + ExtUtil.write(dos, new ExtWrapMap(wrappedParseAttachments)); } /** @@ -1620,7 +1615,7 @@ public String getAdditionalAttribute(String namespace, String name) { @Override public List getAdditionalAttributes() { // Not supported. - return Collections.emptyList(); + return emptyList(); } public X getExtension(Class extension) { @@ -1711,7 +1706,11 @@ public HashMap getFormInstances() { return formInstances; } - public void setSaveTos(List> saveTos) { - this.saveTos = saveTos; + public void putParseAttachment(Externalizable value) { + parseAttachments.put(value.getClass().getName(), value); + } + + public T getParseAttachment(Class key) { + return (T) parseAttachments.get(key.getName()); } } diff --git a/src/main/java/org/javarosa/core/model/instance/FormInstance.java b/src/main/java/org/javarosa/core/model/instance/FormInstance.java index b8c6ef9fa..c7ee4c935 100644 --- a/src/main/java/org/javarosa/core/model/instance/FormInstance.java +++ b/src/main/java/org/javarosa/core/model/instance/FormInstance.java @@ -16,8 +16,6 @@ package org.javarosa.core.model.instance; -import kotlin.Pair; -import org.javarosa.core.model.Entity; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.data.IAnswerData; import org.javarosa.core.model.instance.utils.ITreeVisitor; @@ -33,10 +31,9 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; -import java.util.List; +import java.util.Map; /** @@ -52,8 +49,6 @@ public class FormInstance extends DataInstance implements Persistab public String schema; public String formVersion; public String uiVersion; - private String dataset; - private final List entities = new ArrayList<>(); private HashMap namespaces = new HashMap(); @@ -319,7 +314,6 @@ public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOExcep schema = (String) ExtUtil.read(in, new ExtWrapNullable(String.class), pf); dateSaved = (Date) ExtUtil.read(in, new ExtWrapNullable(Date.class), pf); namespaces = (HashMap)ExtUtil.read(in, new ExtWrapMap(String.class, String.class)); - dataset = (String) ExtUtil.read(in, new ExtWrapNullable(String.class), pf); setRoot((TreeElement) ExtUtil.read(in, TreeElement.class, pf)); } @@ -328,7 +322,6 @@ public void writeExternal(DataOutputStream out) throws IOException { ExtUtil.write(out, new ExtWrapNullable(schema)); ExtUtil.write(out, new ExtWrapNullable(dateSaved)); ExtUtil.write(out, new ExtWrapMap(namespaces)); - ExtUtil.write(out, new ExtWrapNullable(dataset)); ExtUtil.write(out, getRoot()); } @@ -426,18 +419,4 @@ public Object getMetaData(String fieldName) { } throw new IllegalArgumentException("No metadata field " + fieldName + " in the form instance storage system"); } - - public List getEntities() { - return entities; - } - - public void setDataset(String dataset) { - this.dataset = dataset; - } - - public void addEntity(List> fields) { - if (dataset != null) { - this.entities.add(new Entity(dataset, fields)); - } - } } diff --git a/src/main/java/org/javarosa/core/util/externalizable/ExtWrapExternalizable.java b/src/main/java/org/javarosa/core/util/externalizable/ExtWrapExternalizable.java new file mode 100644 index 000000000..60d1b77d9 --- /dev/null +++ b/src/main/java/org/javarosa/core/util/externalizable/ExtWrapExternalizable.java @@ -0,0 +1,48 @@ +package org.javarosa.core.util.externalizable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class ExtWrapExternalizable extends ExternalizableWrapper { + + public ExtWrapExternalizable() { + } + + public ExtWrapExternalizable(Externalizable externalizable) { + this.val = externalizable; + } + + @Override + public ExternalizableWrapper clone(Object val) { + return null; + } + + @Override + public void metaReadExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { + + } + + @Override + public void metaWriteExternal(DataOutputStream out) throws IOException { + + } + + @Override + public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { + try { + String className = ExtUtil.readString(in); + Class clazz = Class.forName(className); + + this.val = ExtUtil.read(in, clazz); + } catch (ClassNotFoundException e) { + throw new DeserializationException("Couldn't find class from serialize class name!"); + } + } + + @Override + public void writeExternal(DataOutputStream out) throws IOException { + ExtUtil.write(out, val.getClass().getName()); + ExtUtil.write(out, val); + } +} diff --git a/src/main/java/org/javarosa/core/model/Entity.java b/src/main/java/org/javarosa/entities/Entity.java similarity index 89% rename from src/main/java/org/javarosa/core/model/Entity.java rename to src/main/java/org/javarosa/entities/Entity.java index 937b60fa3..9d15d9caa 100644 --- a/src/main/java/org/javarosa/core/model/Entity.java +++ b/src/main/java/org/javarosa/entities/Entity.java @@ -1,4 +1,4 @@ -package org.javarosa.core.model; +package org.javarosa.entities; import kotlin.Pair; diff --git a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java new file mode 100644 index 000000000..66a4f7401 --- /dev/null +++ b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java @@ -0,0 +1,42 @@ +package org.javarosa.entities; + +import kotlin.Pair; +import org.javarosa.core.model.FormDef; +import org.javarosa.core.model.IDataReference; +import org.javarosa.core.model.instance.FormInstance; +import org.javarosa.entities.internal.EntitiesAttachment; +import org.javarosa.entities.internal.EntityFormParseAttachment; +import org.javarosa.form.api.FormEntryModel; +import org.javarosa.form.api.FormPostProcessor; +import org.javarosa.model.xform.XPathReference; + +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; + +public class EntityFormPostProcessor implements FormPostProcessor { + + @Override + public void processForm(FormEntryModel formEntryModel) { + FormDef formDef = formEntryModel.getForm(); + FormInstance mainInstance = formDef.getMainInstance(); + + EntityFormParseAttachment entityFormParseAttachment = formDef.getParseAttachment(EntityFormParseAttachment.class); + String dataset = entityFormParseAttachment.getDataset(); + List> saveTos = entityFormParseAttachment.getSaveTos(); + + if (dataset != null) { + List> fields = saveTos.stream().map(saveTo -> { + IDataReference reference = saveTo.getFirst(); + String answer = mainInstance.resolveReference(reference).getValue().getDisplayText(); + return new Pair<>(saveTo.getSecond(), answer); + }).collect(Collectors.toList()); + + formEntryModel.putAttachment(new EntitiesAttachment(asList(new Entity(dataset, fields)))); + } else { + formEntryModel.putAttachment(new EntitiesAttachment(emptyList())); + } + } +} diff --git a/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java b/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java new file mode 100644 index 000000000..1de87343d --- /dev/null +++ b/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java @@ -0,0 +1,46 @@ +package org.javarosa.entities; + +import org.javarosa.entities.internal.EntityFormParseProcessor; +import org.javarosa.xform.parse.IXFormParserFactory; +import org.javarosa.xform.parse.XFormParser; +import org.kxml2.kdom.Document; + +import java.io.Reader; + +public class EntityXFormParserFactory implements IXFormParserFactory { + + private final IXFormParserFactory wrapped; + + public EntityXFormParserFactory(IXFormParserFactory wrapped) { + this.wrapped = wrapped; + } + + @Override + public XFormParser getXFormParser(Reader reader) { + return configureEntityParsing(wrapped.getXFormParser(reader)); + } + + @Override + public XFormParser getXFormParser(Document doc) { + return configureEntityParsing(wrapped.getXFormParser(doc)); + } + + @Override + public XFormParser getXFormParser(Reader form, Reader instance) { + return configureEntityParsing(wrapped.getXFormParser(form, instance)); + } + + @Override + public XFormParser getXFormParser(Document form, Document instance) { + return configureEntityParsing(wrapped.getXFormParser(form, instance)); + } + + private XFormParser configureEntityParsing(XFormParser xFormParser) { + EntityFormParseProcessor processor = new EntityFormParseProcessor(); + xFormParser.addBindingAttributeProcessor(processor); + xFormParser.addFormDefProcessor(processor); + + return xFormParser; + } + +} diff --git a/src/main/java/org/javarosa/entities/internal/EntitiesAttachment.java b/src/main/java/org/javarosa/entities/internal/EntitiesAttachment.java new file mode 100644 index 000000000..642613372 --- /dev/null +++ b/src/main/java/org/javarosa/entities/internal/EntitiesAttachment.java @@ -0,0 +1,18 @@ +package org.javarosa.entities.internal; + +import org.javarosa.entities.Entity; + +import java.util.List; + +public class EntitiesAttachment { + + private final List entities; + + public EntitiesAttachment(List entities) { + this.entities = entities; + } + + public List getEntities() { + return entities; + } +} diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java new file mode 100644 index 000000000..b9798db7f --- /dev/null +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java @@ -0,0 +1,56 @@ +package org.javarosa.entities.internal; + +import kotlin.Pair; +import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.core.util.externalizable.ExtUtil; +import org.javarosa.core.util.externalizable.ExtWrapMap; +import org.javarosa.core.util.externalizable.Externalizable; +import org.javarosa.core.util.externalizable.PrototypeFactory; +import org.javarosa.model.xform.XPathReference; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class EntityFormParseAttachment implements Externalizable { + + private String dataset; + private List> saveTos = new ArrayList<>(); + + public EntityFormParseAttachment() { + } + + public EntityFormParseAttachment(String dataset, List> saveTos) { + this.dataset = dataset; + this.saveTos = saveTos; + } + + public String getDataset() { + return dataset; + } + + public List> getSaveTos() { + return saveTos; + } + + @Override + public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { + dataset = ExtUtil.readString(in); + + HashMap saveToMap = (HashMap) ExtUtil.read(in, new ExtWrapMap(XPathReference.class, String.class), pf); + saveTos = saveToMap.entrySet().stream().map(entry -> new Pair<>(entry.getKey(), entry.getValue())).collect(Collectors.toList()); + } + + @Override + public void writeExternal(DataOutputStream out) throws IOException { + ExtUtil.writeString(out, dataset); + Map saveTosMap = saveTos.stream() + .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)); + ExtUtil.write(out, new ExtWrapMap(new HashMap<>(saveTosMap))); + } +} diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java new file mode 100644 index 000000000..1933307d9 --- /dev/null +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -0,0 +1,58 @@ +package org.javarosa.entities.internal; + +import kotlin.Pair; +import org.javarosa.core.model.DataBinding; +import org.javarosa.core.model.FormDef; +import org.javarosa.core.model.instance.FormInstance; +import org.javarosa.core.model.instance.TreeElement; +import org.javarosa.model.xform.XPathReference; +import org.javarosa.xform.parse.BindingAttributeProcessor; +import org.javarosa.xform.parse.FormDefProcessor; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class EntityFormParseProcessor implements BindingAttributeProcessor, FormDefProcessor { + + private final List> saveTos = new ArrayList<>(); + + @Override + public Set getUsedAttributes() { + HashSet attributes = new HashSet<>(); + attributes.add("saveto"); + + return attributes; + } + + @Override + public void processBindingAttribute(String name, String value, DataBinding binding) { + saveTos.add(new Pair<>((XPathReference) binding.getReference(), value)); + } + + @Override + public void processFormDef(FormDef formDef) { + String dataset = parseDataset(formDef.getMainInstance()); + EntityFormParseAttachment entityFormParseAttachment = new EntityFormParseAttachment(dataset, saveTos); + formDef.putParseAttachment(entityFormParseAttachment); + } + + private String parseDataset(FormInstance mainInstance) { + TreeElement root = mainInstance.getRoot(); + List meta = root.getChildrenWithName("meta"); + if (!meta.isEmpty()) { + List entity = meta.get(0).getChildrenWithName("entity"); + + if (!entity.isEmpty()) { + List create = entity.get(0).getChildrenWithName("create"); + + if (!create.isEmpty()) { + return entity.get(0).getAttributeValue(null, "dataset"); + } + } + } + + return null; + } +} diff --git a/src/main/java/org/javarosa/form/api/FormEntryController.java b/src/main/java/org/javarosa/form/api/FormEntryController.java index c53b8c33e..957d33593 100644 --- a/src/main/java/org/javarosa/form/api/FormEntryController.java +++ b/src/main/java/org/javarosa/form/api/FormEntryController.java @@ -27,6 +27,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + /** * This class is used to navigate through an xform and appropriately manipulate * the FormEntryModel's state. @@ -48,6 +51,8 @@ public class FormEntryController { FormEntryModel model; + private final List formPostProcessors = new ArrayList<>(); + /** * Creates a new form entry controller for the model provided * @@ -57,7 +62,6 @@ public FormEntryController(FormEntryModel model) { this.model = model; } - public FormEntryModel getModel() { return model; } @@ -198,6 +202,16 @@ public int stepToPreviousEvent() { return stepEvent(false); } + public void finalizeForm() { + model.getForm().postProcessInstance(); + formPostProcessors.forEach(formPostProcessor -> { + formPostProcessor.processForm(model); + }); + } + + public void addPostProcessor(FormPostProcessor formPostProcessor) { + formPostProcessors.add(formPostProcessor); + } /** * Moves the current FormIndex to the next/previous relevant position. diff --git a/src/main/java/org/javarosa/form/api/FormEntryModel.java b/src/main/java/org/javarosa/form/api/FormEntryModel.java index d34708c78..b5ea95b23 100644 --- a/src/main/java/org/javarosa/form/api/FormEntryModel.java +++ b/src/main/java/org/javarosa/form/api/FormEntryModel.java @@ -17,8 +17,10 @@ package org.javarosa.form.api; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.FormIndex; @@ -64,6 +66,8 @@ public class FormEntryModel { */ public static final int REPEAT_STRUCTURE_NON_LINEAR = 2; + private final Map, Object> attachments = new HashMap<>(); + public FormEntryModel(FormDef form) { this(form, REPEAT_STRUCTURE_LINEAR); @@ -661,6 +665,14 @@ public FormIndex decrementIndex(FormIndex index) { } } + public void putAttachment(Object value) { + attachments.put(value.getClass(), value); + } + + public T getAttachment(Class key) { + return (T) attachments.get(key); + } + private void decrementHelper(List indexes, List multiplicities, List elements) { int i = indexes.size() - 1; diff --git a/src/main/java/org/javarosa/form/api/FormPostProcessor.java b/src/main/java/org/javarosa/form/api/FormPostProcessor.java new file mode 100644 index 000000000..218817254 --- /dev/null +++ b/src/main/java/org/javarosa/form/api/FormPostProcessor.java @@ -0,0 +1,6 @@ +package org.javarosa.form.api; + +public interface FormPostProcessor { + + void processForm(FormEntryModel formEntryModel); +} diff --git a/src/main/java/org/javarosa/xform/parse/BindingAttributeProcessor.java b/src/main/java/org/javarosa/xform/parse/BindingAttributeProcessor.java new file mode 100644 index 000000000..0a8fee334 --- /dev/null +++ b/src/main/java/org/javarosa/xform/parse/BindingAttributeProcessor.java @@ -0,0 +1,12 @@ +package org.javarosa.xform.parse; + +import org.javarosa.core.model.DataBinding; + +import java.util.Set; + +public interface BindingAttributeProcessor { + + Set getUsedAttributes(); + + void processBindingAttribute(String name, String value, DataBinding binding); +} diff --git a/src/main/java/org/javarosa/xform/parse/FormDefProcessor.java b/src/main/java/org/javarosa/xform/parse/FormDefProcessor.java new file mode 100644 index 000000000..dd402f3c6 --- /dev/null +++ b/src/main/java/org/javarosa/xform/parse/FormDefProcessor.java @@ -0,0 +1,7 @@ +package org.javarosa.xform.parse; + +import org.javarosa.core.model.FormDef; + +public interface FormDefProcessor { + void processFormDef(FormDef formDef); +} diff --git a/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java b/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java index b457070b1..2be1bdfef 100644 --- a/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java +++ b/src/main/java/org/javarosa/xform/parse/FormInstanceParser.java @@ -86,20 +86,6 @@ FormInstance parseInstance(Element e, boolean isMainInstance, String name, Map meta = root.getChildrenWithName("meta"); - if (!meta.isEmpty()) { - List entity = meta.get(0).getChildrenWithName("entity"); - - if (!entity.isEmpty()) { - List create = entity.get(0).getChildrenWithName("create"); - - if (!create.isEmpty()) { - String dataset = entity.get(0).getAttributeValue(null, "dataset"); - instanceModel.setDataset(dataset); - } - } - } } applyInstanceProperties(instanceModel); diff --git a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java index 7f2a93963..90c969d63 100644 --- a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java @@ -4,8 +4,14 @@ import static org.javarosa.xform.parse.Constants.NODESET_ATTR; import static org.javarosa.xform.parse.XFormParser.NAMESPACE_JAVAROSA; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.IDataReference; @@ -31,7 +37,7 @@ class StandardBindAttributesProcessor { DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef, Collection usedAttributes, Collection passedThroughAttributes, - Element element) { + Element element, List bindingAttributeProcessors) { final DataBinding binding = new DataBinding(); binding.setId(element.getAttributeValue("", ID_ATTR)); @@ -110,7 +116,26 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef binding.setPreload(element.getAttributeValue(NAMESPACE_JAVAROSA, "preload")); binding.setPreloadParams(element.getAttributeValue(NAMESPACE_JAVAROSA, "preloadParams")); - saveUnusedAttributes(binding, element, usedAttributes, passedThroughAttributes); + bindingAttributeProcessors.forEach(bindingAttributeProcessor -> { + for (int i = 0; i < element.getAttributeCount(); i++) { + String name = element.getAttributeName(i); + if (bindingAttributeProcessor.getUsedAttributes().contains(name)) { + bindingAttributeProcessor.processBindingAttribute(name, element.getAttributeValue(i), binding); + } + } + }); + + List processorAttributes = bindingAttributeProcessors.stream() + .flatMap((Function>) bindingAttributeProcessor -> { + return bindingAttributeProcessor.getUsedAttributes().stream(); + }) + .collect(Collectors.toList()); + + List allUsedAttributes = new ArrayList<>(); + allUsedAttributes.addAll(usedAttributes); + allUsedAttributes.addAll(processorAttributes); + + saveUnusedAttributes(binding, element, allUsedAttributes, passedThroughAttributes); return binding; } diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index ea2f4c7bf..618896492 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -16,7 +16,6 @@ package org.javarosa.xform.parse; -import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.GroupDef; @@ -85,8 +84,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableList; @@ -165,7 +164,6 @@ public class XFormParser implements IXFormParserFunctions { private Localizer localizer; private HashMap bindingsByID; private List bindings; - private List> saveTos; private List actionTargets; private List repeats; private List itemsets; @@ -177,6 +175,9 @@ public class XFormParser implements IXFormParserFunctions { private List itextKnownForms; private static HashMap actionHandlers; + private final List bindingAttributeProcessors = new ArrayList<>(); + private final List formDefProcessors = new ArrayList<>(); + /** * The string IDs of all instances that are referenced in a instance() function call in the primary instance **/ @@ -189,7 +190,6 @@ public class XFormParser implements IXFormParserFunctions { private int serialQuestionID = 1; private static IAnswerResolver answerResolver; - public static IAnswerResolver getAnswerResolver() { return answerResolver; } @@ -341,7 +341,6 @@ private void initState() { mainInstanceNode = null; instanceNodes = new ArrayList<>(); instanceNodeIdStrs = new ArrayList<>(); - saveTos = new ArrayList<>(); itextKnownForms = new ArrayList<>(4); itextKnownForms.add("long"); @@ -401,9 +400,19 @@ public FormDef parse(String formXmlSrc, String lastSavedSrc) throws IOException loadXmlInstance(_f, _instDoc); } } + + formDefProcessors.forEach(formDefProcessor -> formDefProcessor.processFormDef(_f)); return _f; } + public void addBindingAttributeProcessor(BindingAttributeProcessor bindingAttributeProcessor) { + bindingAttributeProcessors.add(bindingAttributeProcessor); + } + + public void addFormDefProcessor(FormDefProcessor formDefProcessor) { + formDefProcessors.add(formDefProcessor); + } + /** * Extracts the namespaces from the given element and creates a map of URI to prefix */ @@ -488,7 +497,6 @@ private void parseDoc(String formXmlSrc, Map namespacePrefixesBy collapseRepeatGroups(_f); - _f.setSaveTos(saveTos); final FormInstanceParser instanceParser = new FormInstanceParser(_f, defaultNamespace, bindings, repeats, itemsets, selectOnes, multipleItems, actionTargets); @@ -1879,9 +1887,9 @@ private boolean hasSpecialFormMapping(String textID, String locale) { return false; } - private DataBinding processStandardBindAttributes(List usedAtts, List passedThroughAtts, Element element) { + private DataBinding processStandardBindAttributes(List usedAtts, List passedThroughAtts, Element element, List bindingAttributeProcessors) { return new StandardBindAttributesProcessor(typeMappings). - createBinding(this, _f, usedAtts, passedThroughAtts, element); + createBinding(this, _f, usedAtts, passedThroughAtts, element, bindingAttributeProcessors); } /** @@ -1913,7 +1921,7 @@ private DataBinding processStandardBindAttributes(List usedAtts, List maybeSaveTo = binding.getAdditionalAttributes().stream().filter(treeElement -> treeElement.getName().equals("saveto")).findFirst(); - - if (maybeSaveTo.isPresent()) { - String saveTo = maybeSaveTo.get().getAttributeValue(); - saveTos.add(new Pair<>((XPathReference) reference, saveTo)); - } - if (binding.getId() != null) { if (bindingsByID.put(binding.getId(), binding) != null) { throw new XFormParseException("XForm Parse: s with duplicate ID: '" + binding.getId() + "'"); diff --git a/src/test/java/org/javarosa/core/test/Scenario.java b/src/test/java/org/javarosa/core/test/Scenario.java index ff282e038..e36f74571 100644 --- a/src/test/java/org/javarosa/core/test/Scenario.java +++ b/src/test/java/org/javarosa/core/test/Scenario.java @@ -321,7 +321,11 @@ public void trace(String msg) { } public void finalizeInstance() { - formDef.postProcessInstance(); + controller.finalizeForm(); + } + + public FormEntryController getFormEntryController() { + return controller; } public enum AnswerResult { diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index 12c6707d8..fd78d556f 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -1,18 +1,30 @@ package org.javarosa.xform.parse; import kotlin.Pair; -import org.javarosa.core.model.Entity; +import org.javarosa.core.model.instance.TreeElement; import org.javarosa.core.test.Scenario; import org.javarosa.core.util.XFormsElement; import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.entities.Entity; +import org.javarosa.entities.EntityFormPostProcessor; +import org.javarosa.entities.EntityXFormParserFactory; +import org.javarosa.entities.internal.EntitiesAttachment; +import org.javarosa.model.xform.XPathReference; +import org.javarosa.xform.util.XFormUtils; +import org.javarosa.xpath.XPathParseTool; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.List; +import java.util.function.Predicate; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.javarosa.core.util.BindBuilderXFormsElement.bind; import static org.javarosa.core.util.XFormsElement.body; import static org.javarosa.core.util.XFormsElement.head; @@ -26,6 +38,18 @@ public class EntitiesTest { + private final EntityXFormParserFactory entityXFormParserFactory = new EntityXFormParserFactory(new XFormParserFactory()); + + @Before + public void setup() { + XFormUtils.setXFormParserFactory(entityXFormParserFactory); + } + + @After + public void teardown() { + XFormUtils.setXFormParserFactory(new XFormParserFactory()); + } + @Test public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOException { Scenario scenario = Scenario.init("Entity form", XFormsElement.html( @@ -51,11 +75,13 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti ) )); + scenario.getFormEntryController().addPostProcessor(new EntityFormPostProcessor()); + scenario.next(); scenario.answer("Tom Wambsgans"); - scenario.finalizeInstance(); - List entities = scenario.getFormDef().getMainInstance().getEntities(); + scenario.finalizeInstance(); + List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); assertThat(entities.size(), equalTo(0)); } @@ -86,16 +112,19 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { ) )); + scenario.getFormEntryController().addPostProcessor(new EntityFormPostProcessor()); + scenario.next(); scenario.answer("Tom Wambsgans"); - scenario.finalizeInstance(); - List entities = scenario.getFormDef().getMainInstance().getEntities(); + scenario.finalizeInstance(); + List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).dataset, equalTo("people")); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } + @Test public void entityFormCanBeSerialized() throws IOException, DeserializationException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( @@ -123,13 +152,16 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep ) )); + scenario.getFormEntryController().addPostProcessor(new EntityFormPostProcessor()); + Scenario deserializedScenario = scenario.serializeAndDeserializeForm(); + deserializedScenario.getFormEntryController().addPostProcessor(new EntityFormPostProcessor()); deserializedScenario.next(); deserializedScenario.answer("Shiv Roy"); - deserializedScenario.finalizeInstance(); - List entities = deserializedScenario.getFormDef().getMainInstance().getEntities(); + deserializedScenario.finalizeInstance(); + List entities = deserializedScenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).dataset, equalTo("people")); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Shiv Roy")))); @@ -162,11 +194,13 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria ) )); + scenario.getFormEntryController().addPostProcessor(new EntityFormPostProcessor()); + scenario.next(); scenario.answer("Tom Wambsgans"); - scenario.finalizeInstance(); - List entities = scenario.getFormDef().getMainInstance().getEntities(); + scenario.finalizeInstance(); + List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } @@ -198,12 +232,47 @@ public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEnti ) )); + scenario.getFormEntryController().addPostProcessor(new EntityFormPostProcessor()); + scenario.next(); scenario.answer(scenario.choicesOf("/data/team").get(0)); - scenario.finalizeInstance(); - List entities = scenario.getFormDef().getMainInstance().getEntities(); + scenario.finalizeInstance(); + List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("team", "kendall")))); } + + @Test + public void savetoIsRemovedFromBindAttributesForClients() throws IOException { + Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("entities:entity dataset=\"people\"", + t("entities:create") + ) + ) + ) + ), + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") + ) + ), + body( + input("/data/name") + ) + )); + + scenario.next(); + List bindAttributes = scenario.getFormEntryPromptAtIndex().getBindAttributes(); + boolean containsSaveTo = bindAttributes.stream().anyMatch(treeElement -> treeElement.getName().equals("saveto")); + assertThat(containsSaveTo, is(false)); + } } From b6ba3025d9fa4d9d13bd194e27cfeac809b02ddb Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 23 Sep 2022 11:10:57 +0100 Subject: [PATCH 10/36] Ignore entities:create if the namespace is incorrect --- .../internal/EntityFormParseProcessor.java | 6 ++- .../org/javarosa/core/util/XFormsElement.java | 2 +- .../javarosa/xform/parse/EntitiesTest.java | 42 ++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 1933307d9..3eff0ca00 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; public class EntityFormParseProcessor implements BindingAttributeProcessor, FormDefProcessor { @@ -42,7 +43,10 @@ private String parseDataset(FormInstance mainInstance) { TreeElement root = mainInstance.getRoot(); List meta = root.getChildrenWithName("meta"); if (!meta.isEmpty()) { - List entity = meta.get(0).getChildrenWithName("entity"); + List entity = meta.get(0).getChildrenWithName("entity") + .stream() + .filter(node -> node.getNamespace().equals("http://www.opendatakit.org/xforms/entities")) + .collect(Collectors.toList()); if (!entity.isEmpty()) { List create = entity.get(0).getChildrenWithName("create"); diff --git a/src/test/java/org/javarosa/core/util/XFormsElement.java b/src/test/java/org/javarosa/core/util/XFormsElement.java index f005ba299..5b9b8c1f9 100644 --- a/src/test/java/org/javarosa/core/util/XFormsElement.java +++ b/src/test/java/org/javarosa/core/util/XFormsElement.java @@ -80,7 +80,7 @@ static XFormsElement html(HeadXFormsElement head, BodyXFormsElement body) { static XFormsElement html(List> additionalNamespaces, HeadXFormsElement head, BodyXFormsElement body) { String additionalNamespacesString = additionalNamespaces.stream() - .map(namespace -> "xmlns:" + namespace.getFirst() + "=" + namespace.getSecond() + " ") + .map(namespace -> "xmlns:" + namespace.getFirst() + "=\"" + namespace.getSecond() + "\" ") .collect(Collectors.joining()); return t("h:html " + diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index fd78d556f..6fcd4eec9 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -9,20 +9,16 @@ import org.javarosa.entities.EntityFormPostProcessor; import org.javarosa.entities.EntityXFormParserFactory; import org.javarosa.entities.internal.EntitiesAttachment; -import org.javarosa.model.xform.XPathReference; import org.javarosa.xform.util.XFormUtils; -import org.javarosa.xpath.XPathParseTool; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.List; -import java.util.function.Predicate; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.javarosa.core.util.BindBuilderXFormsElement.bind; @@ -124,7 +120,6 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } - @Test public void entityFormCanBeSerialized() throws IOException, DeserializationException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( @@ -205,6 +200,43 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } + @Test + public void mustUseCorrectNamespace() throws IOException { + Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + asList( + new Pair<>("entities", "http://www.example.com/xforms/entities") + ), + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("entities:entity dataset=\"people\"", + t("entities:create") + ) + ) + ) + ), + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") + ) + ), + body( + input("/data/name") + ) + )); + + scenario.getFormEntryController().addPostProcessor(new EntityFormPostProcessor()); + + scenario.next(); + scenario.answer("Tom Wambsgans"); + + scenario.finalizeInstance(); + List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + assertThat(entities.size(), equalTo(0)); + } + @Test public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEntity() throws IOException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( From 15a0861d6a5dd4ee296158e3e80927e1680dfa09 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 23 Sep 2022 12:04:43 +0100 Subject: [PATCH 11/36] Don't create entities if create is not relevant --- .../entities/EntityFormPostProcessor.java | 24 ++++++++++- .../javarosa/xform/parse/EntitiesTest.java | 41 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java index 66a4f7401..c0f8b6844 100644 --- a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java +++ b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java @@ -4,6 +4,7 @@ import org.javarosa.core.model.FormDef; import org.javarosa.core.model.IDataReference; import org.javarosa.core.model.instance.FormInstance; +import org.javarosa.core.model.instance.TreeElement; import org.javarosa.entities.internal.EntitiesAttachment; import org.javarosa.entities.internal.EntityFormParseAttachment; import org.javarosa.form.api.FormEntryModel; @@ -27,7 +28,7 @@ public void processForm(FormEntryModel formEntryModel) { String dataset = entityFormParseAttachment.getDataset(); List> saveTos = entityFormParseAttachment.getSaveTos(); - if (dataset != null) { + if (dataset != null && createIsRelevant(mainInstance)) { List> fields = saveTos.stream().map(saveTo -> { IDataReference reference = saveTo.getFirst(); String answer = mainInstance.resolveReference(reference).getValue().getDisplayText(); @@ -39,4 +40,25 @@ public void processForm(FormEntryModel formEntryModel) { formEntryModel.putAttachment(new EntitiesAttachment(emptyList())); } } + + private boolean createIsRelevant(FormInstance mainInstance) { + TreeElement root = mainInstance.getRoot(); + List meta = root.getChildrenWithName("meta"); + if (!meta.isEmpty()) { + List entity = meta.get(0).getChildrenWithName("entity") + .stream() + .filter(node -> node.getNamespace().equals("http://www.opendatakit.org/xforms/entities")) + .collect(Collectors.toList()); + + if (!entity.isEmpty()) { + List create = entity.get(0).getChildrenWithName("create"); + + if (!create.isEmpty()) { + return create.get(0).isRelevant(); + } + } + } + + return false; + } } diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index 6fcd4eec9..13b82e589 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -120,6 +120,47 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } + @Test + public void fillingFormWithNonRelevantCreate_doesNotCreateAnyEntities() throws IOException { + Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("join"), + t("orx:meta", + t("entities:entity dataset=\"members\"", + t("entities:create") + ) + ) + ) + ), + bind("/data/orx:meta/entities:entity/entities:create").relevant("/data/join = 'yes'"), + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") + ) + ), + body( + input("/data/name"), + select1("/data/join", item("yes", "Yes"), item("no", "No")) + ) + )); + + scenario.getFormEntryController().addPostProcessor(new EntityFormPostProcessor()); + + scenario.next(); + scenario.answer("Roman Roy"); + scenario.answer(scenario.choicesOf("/data/join").get(1)); + + scenario.finalizeInstance(); + List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + assertThat(entities.size(), equalTo(0)); + } + @Test public void entityFormCanBeSerialized() throws IOException, DeserializationException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( From 444143db60e8e8a8ecf5d65d818d524dd8d595c9 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 23 Sep 2022 12:09:47 +0100 Subject: [PATCH 12/36] Only parse dataset out during processing --- .../entities/EntityFormPostProcessor.java | 14 +++++++---- .../internal/EntityFormParseAttachment.java | 11 +-------- .../internal/EntityFormParseProcessor.java | 24 +------------------ 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java index c0f8b6844..ecbf79cd7 100644 --- a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java +++ b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java @@ -10,6 +10,7 @@ import org.javarosa.form.api.FormEntryModel; import org.javarosa.form.api.FormPostProcessor; import org.javarosa.model.xform.XPathReference; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.stream.Collectors; @@ -25,10 +26,10 @@ public void processForm(FormEntryModel formEntryModel) { FormInstance mainInstance = formDef.getMainInstance(); EntityFormParseAttachment entityFormParseAttachment = formDef.getParseAttachment(EntityFormParseAttachment.class); - String dataset = entityFormParseAttachment.getDataset(); List> saveTos = entityFormParseAttachment.getSaveTos(); - if (dataset != null && createIsRelevant(mainInstance)) { + String dataset = getDatasetToCreateWith(mainInstance); + if (dataset != null) { List> fields = saveTos.stream().map(saveTo -> { IDataReference reference = saveTo.getFirst(); String answer = mainInstance.resolveReference(reference).getValue().getDisplayText(); @@ -41,7 +42,8 @@ public void processForm(FormEntryModel formEntryModel) { } } - private boolean createIsRelevant(FormInstance mainInstance) { + @Nullable + private String getDatasetToCreateWith(FormInstance mainInstance) { TreeElement root = mainInstance.getRoot(); List meta = root.getChildrenWithName("meta"); if (!meta.isEmpty()) { @@ -54,11 +56,13 @@ private boolean createIsRelevant(FormInstance mainInstance) { List create = entity.get(0).getChildrenWithName("create"); if (!create.isEmpty()) { - return create.get(0).isRelevant(); + if (create.get(0).isRelevant()) { + return entity.get(0).getAttributeValue(null, "dataset"); + } } } } - return false; + return null; } } diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java index b9798db7f..041787dce 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java @@ -19,36 +19,27 @@ public class EntityFormParseAttachment implements Externalizable { - private String dataset; private List> saveTos = new ArrayList<>(); public EntityFormParseAttachment() { } - public EntityFormParseAttachment(String dataset, List> saveTos) { - this.dataset = dataset; + public EntityFormParseAttachment(List> saveTos) { this.saveTos = saveTos; } - public String getDataset() { - return dataset; - } - public List> getSaveTos() { return saveTos; } @Override public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { - dataset = ExtUtil.readString(in); - HashMap saveToMap = (HashMap) ExtUtil.read(in, new ExtWrapMap(XPathReference.class, String.class), pf); saveTos = saveToMap.entrySet().stream().map(entry -> new Pair<>(entry.getKey(), entry.getValue())).collect(Collectors.toList()); } @Override public void writeExternal(DataOutputStream out) throws IOException { - ExtUtil.writeString(out, dataset); Map saveTosMap = saveTos.stream() .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)); ExtUtil.write(out, new ExtWrapMap(new HashMap<>(saveTosMap))); diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 3eff0ca00..e6ba9d1e8 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -34,29 +34,7 @@ public void processBindingAttribute(String name, String value, DataBinding bindi @Override public void processFormDef(FormDef formDef) { - String dataset = parseDataset(formDef.getMainInstance()); - EntityFormParseAttachment entityFormParseAttachment = new EntityFormParseAttachment(dataset, saveTos); + EntityFormParseAttachment entityFormParseAttachment = new EntityFormParseAttachment(saveTos); formDef.putParseAttachment(entityFormParseAttachment); } - - private String parseDataset(FormInstance mainInstance) { - TreeElement root = mainInstance.getRoot(); - List meta = root.getChildrenWithName("meta"); - if (!meta.isEmpty()) { - List entity = meta.get(0).getChildrenWithName("entity") - .stream() - .filter(node -> node.getNamespace().equals("http://www.opendatakit.org/xforms/entities")) - .collect(Collectors.toList()); - - if (!entity.isEmpty()) { - List create = entity.get(0).getChildrenWithName("create"); - - if (!create.isEmpty()) { - return entity.get(0).getAttributeValue(null, "dataset"); - } - } - } - - return null; - } } From 8072261221b5318e2e20d1038d0f01159f519bdb Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 23 Sep 2022 12:31:50 +0100 Subject: [PATCH 13/36] Don't return all matching children in cases we don't expect more than one --- .../model/instance/AbstractTreeElement.java | 5 +++++ .../core/model/instance/TreeElement.java | 8 +++++++ .../utils/TreeElementChildrenList.java | 2 ++ .../entities/EntityFormPostProcessor.java | 21 +++++++++---------- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java b/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java index ce61c1ec4..8de4a7187 100644 --- a/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java @@ -6,6 +6,7 @@ import org.javarosa.core.model.data.IAnswerData; import org.javarosa.core.model.instance.utils.ITreeVisitor; import org.javarosa.xpath.expr.XPathExpression; +import org.jetbrains.annotations.Nullable; public interface AbstractTreeElement { @@ -15,6 +16,10 @@ public interface AbstractTreeElement { public abstract String getInstanceName(); + @Nullable + public T getChild(String name); + + @Nullable public abstract T getChild(String name, int multiplicity); /** diff --git a/src/main/java/org/javarosa/core/model/instance/TreeElement.java b/src/main/java/org/javarosa/core/model/instance/TreeElement.java index a54fca661..c2a19f323 100644 --- a/src/main/java/org/javarosa/core/model/instance/TreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/TreeElement.java @@ -50,6 +50,7 @@ import org.javarosa.xpath.expr.XPathExpression; import org.javarosa.xpath.expr.XPathPathExpr; import org.javarosa.xpath.expr.XPathStringLiteral; +import org.jetbrains.annotations.Nullable; /** *

An element of a FormInstance.

@@ -221,6 +222,13 @@ public void setValue(IAnswerData value) { } @Override + @Nullable + public TreeElement getChild(String name) { + return getChild(name, 0); + } + + @Override + @Nullable public TreeElement getChild(String name, int multiplicity) { return children.get(name, multiplicity); } diff --git a/src/main/java/org/javarosa/core/model/instance/utils/TreeElementChildrenList.java b/src/main/java/org/javarosa/core/model/instance/utils/TreeElementChildrenList.java index f366a30b8..66d4ee365 100644 --- a/src/main/java/org/javarosa/core/model/instance/utils/TreeElementChildrenList.java +++ b/src/main/java/org/javarosa/core/model/instance/utils/TreeElementChildrenList.java @@ -2,6 +2,7 @@ import org.javarosa.core.model.instance.TreeElement; import org.javarosa.core.model.instance.TreeReference; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Iterator; @@ -72,6 +73,7 @@ public List get(String name) { } /** Gets the child with the specified name and multiplicity */ + @Nullable public TreeElement get(String name, int multiplicity) { TreeElementChildrenList.ElementAndLoc el = getChildAndLoc(name, multiplicity); if (el == null) { diff --git a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java index ecbf79cd7..01151bff5 100644 --- a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java +++ b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java @@ -20,6 +20,8 @@ public class EntityFormPostProcessor implements FormPostProcessor { + private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; + @Override public void processForm(FormEntryModel formEntryModel) { FormDef formDef = formEntryModel.getForm(); @@ -45,19 +47,16 @@ public void processForm(FormEntryModel formEntryModel) { @Nullable private String getDatasetToCreateWith(FormInstance mainInstance) { TreeElement root = mainInstance.getRoot(); - List meta = root.getChildrenWithName("meta"); - if (!meta.isEmpty()) { - List entity = meta.get(0).getChildrenWithName("entity") - .stream() - .filter(node -> node.getNamespace().equals("http://www.opendatakit.org/xforms/entities")) - .collect(Collectors.toList()); + TreeElement meta = root.getChild("meta"); + if (meta != null) { + TreeElement entity = meta.getChild("entity"); - if (!entity.isEmpty()) { - List create = entity.get(0).getChildrenWithName("create"); + if (entity != null && entity.getNamespace().equals(ENTITIES_NAMESPACE)) { + TreeElement create = entity.getChild("create"); - if (!create.isEmpty()) { - if (create.get(0).isRelevant()) { - return entity.get(0).getAttributeValue(null, "dataset"); + if (create != null) { + if (create.isRelevant()) { + return entity.getAttributeValue(null, "dataset"); } } } From a8e50234a366e294e65c1b33a116e990b6910002 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 23 Sep 2022 13:23:58 +0100 Subject: [PATCH 14/36] Use forEach supported by Android API 21 --- src/main/java/org/javarosa/form/api/FormEntryController.java | 2 +- .../javarosa/xform/parse/StandardBindAttributesProcessor.java | 2 +- src/main/java/org/javarosa/xform/parse/XFormParser.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/javarosa/form/api/FormEntryController.java b/src/main/java/org/javarosa/form/api/FormEntryController.java index 957d33593..fac98e37c 100644 --- a/src/main/java/org/javarosa/form/api/FormEntryController.java +++ b/src/main/java/org/javarosa/form/api/FormEntryController.java @@ -204,7 +204,7 @@ public int stepToPreviousEvent() { public void finalizeForm() { model.getForm().postProcessInstance(); - formPostProcessors.forEach(formPostProcessor -> { + formPostProcessors.stream().forEach(formPostProcessor -> { formPostProcessor.processForm(model); }); } diff --git a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java index 90c969d63..e48b940c0 100644 --- a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java @@ -116,7 +116,7 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef binding.setPreload(element.getAttributeValue(NAMESPACE_JAVAROSA, "preload")); binding.setPreloadParams(element.getAttributeValue(NAMESPACE_JAVAROSA, "preloadParams")); - bindingAttributeProcessors.forEach(bindingAttributeProcessor -> { + bindingAttributeProcessors.stream().forEach(bindingAttributeProcessor -> { for (int i = 0; i < element.getAttributeCount(); i++) { String name = element.getAttributeName(i); if (bindingAttributeProcessor.getUsedAttributes().contains(name)) { diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 618896492..91d8540b1 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -401,7 +401,7 @@ public FormDef parse(String formXmlSrc, String lastSavedSrc) throws IOException } } - formDefProcessors.forEach(formDefProcessor -> formDefProcessor.processFormDef(_f)); + formDefProcessors.stream().forEach(formDefProcessor -> formDefProcessor.processFormDef(_f)); return _f; } From 488511ca6e8c8da046bcd8e61f42e2f69893c7d2 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 23 Sep 2022 13:27:21 +0100 Subject: [PATCH 15/36] Optimize imports --- .../java/org/javarosa/core/model/instance/FormInstance.java | 1 - .../javarosa/entities/internal/EntityFormParseProcessor.java | 3 --- src/main/java/org/javarosa/xform/parse/XFormParser.java | 1 - src/test/java/org/javarosa/core/util/XFormsElement.java | 1 - 4 files changed, 6 deletions(-) diff --git a/src/main/java/org/javarosa/core/model/instance/FormInstance.java b/src/main/java/org/javarosa/core/model/instance/FormInstance.java index c7ee4c935..34b55eb0b 100644 --- a/src/main/java/org/javarosa/core/model/instance/FormInstance.java +++ b/src/main/java/org/javarosa/core/model/instance/FormInstance.java @@ -33,7 +33,6 @@ import java.io.IOException; import java.util.Date; import java.util.HashMap; -import java.util.Map; /** diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index e6ba9d1e8..21bbdd2a5 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -3,8 +3,6 @@ import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; -import org.javarosa.core.model.instance.FormInstance; -import org.javarosa.core.model.instance.TreeElement; import org.javarosa.model.xform.XPathReference; import org.javarosa.xform.parse.BindingAttributeProcessor; import org.javarosa.xform.parse.FormDefProcessor; @@ -13,7 +11,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; public class EntityFormParseProcessor implements BindingAttributeProcessor, FormDefProcessor { diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 91d8540b1..7b02bf6da 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -85,7 +85,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Consumer; import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableList; diff --git a/src/test/java/org/javarosa/core/util/XFormsElement.java b/src/test/java/org/javarosa/core/util/XFormsElement.java index 5b9b8c1f9..c4af22a9a 100644 --- a/src/test/java/org/javarosa/core/util/XFormsElement.java +++ b/src/test/java/org/javarosa/core/util/XFormsElement.java @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; import static java.util.Collections.emptyMap; From 98aac67d45f72d58fa4df2df8edb216d1e168639 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 15:15:17 +0100 Subject: [PATCH 16/36] Pull out common Extras --- .../java/org/javarosa/core/model/FormDef.java | 22 +++++---------- .../java/org/javarosa/core/util/Extras.java | 21 +++++++++++++++ .../externalizable/ExternalizableExtras.java | 27 +++++++++++++++++++ .../entities/EntityFormPostProcessor.java | 12 ++++----- ...{EntitiesAttachment.java => Entities.java} | 4 +-- ...seAttachment.java => EntityFormExtra.java} | 6 ++--- .../internal/EntityFormParseProcessor.java | 4 +-- .../org/javarosa/form/api/FormEntryModel.java | 21 ++++++--------- .../javarosa/xform/parse/EntitiesTest.java | 16 +++++------ 9 files changed, 84 insertions(+), 49 deletions(-) create mode 100644 src/main/java/org/javarosa/core/util/Extras.java create mode 100644 src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java rename src/main/java/org/javarosa/entities/internal/{EntitiesAttachment.java => Entities.java} (74%) rename src/main/java/org/javarosa/entities/internal/{EntityFormParseAttachment.java => EntityFormExtra.java} (89%) diff --git a/src/main/java/org/javarosa/core/model/FormDef.java b/src/main/java/org/javarosa/core/model/FormDef.java index 44513e7e6..b060b1904 100644 --- a/src/main/java/org/javarosa/core/model/FormDef.java +++ b/src/main/java/org/javarosa/core/model/FormDef.java @@ -42,14 +42,15 @@ import org.javarosa.core.services.locale.Localizer; import org.javarosa.core.services.storage.IMetaData; import org.javarosa.core.services.storage.Persistable; +import org.javarosa.core.util.Extras; import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.core.util.externalizable.ExtUtil; -import org.javarosa.core.util.externalizable.ExtWrapExternalizable; import org.javarosa.core.util.externalizable.ExtWrapListPoly; import org.javarosa.core.util.externalizable.ExtWrapMap; import org.javarosa.core.util.externalizable.ExtWrapNullable; import org.javarosa.core.util.externalizable.ExtWrapTagged; import org.javarosa.core.util.externalizable.Externalizable; +import org.javarosa.core.util.externalizable.ExternalizableExtras; import org.javarosa.core.util.externalizable.PrototypeFactory; import org.javarosa.debug.EvaluationResult; import org.javarosa.debug.Event; @@ -94,7 +95,7 @@ public class FormDef implements IFormElement, Localizable, Persistable, IMetaDat public static final int TEMPLATING_RECURSION_LIMIT = 10; private static EventNotifier defaultEventNotifier = new EventNotifierSilent(); - private Map parseAttachments = new HashMap<>(); + private ExternalizableExtras extras = new ExternalizableExtras(); /** * Takes a (possibly relative) reference, and makes it absolute based on its parent. @@ -1119,7 +1120,7 @@ public void readExternal(DataInputStream dis, PrototypeFactory pf) throws IOExce List treeReferencesWithActions = (List) ExtUtil.read(dis, new ExtWrapListPoly(), pf); elementsWithActionTriggeredByToplevelEvent = getElementsFromReferences(treeReferencesWithActions); - parseAttachments = (HashMap) ExtUtil.read(dis, new ExtWrapMap(String.class, new ExtWrapExternalizable()), pf); + extras = (ExternalizableExtras) ExtUtil.read(dis, ExternalizableExtras.class); } /** @@ -1224,12 +1225,7 @@ public void writeExternal(DataOutputStream dos) throws IOException { ExtUtil.write(dos, new ExtWrapListPoly(new ArrayList<>(actions))); ExtUtil.write(dos, new ExtWrapListPoly(getReferencesFromElements(elementsWithActionTriggeredByToplevelEvent))); - HashMap wrappedParseAttachments = new HashMap<>(); - parseAttachments.forEach((key, value) -> { - wrappedParseAttachments.put(key, new ExtWrapExternalizable(value)); - }); - - ExtUtil.write(dos, new ExtWrapMap(wrappedParseAttachments)); + ExtUtil.write(dos, extras); } /** @@ -1706,11 +1702,7 @@ public HashMap getFormInstances() { return formInstances; } - public void putParseAttachment(Externalizable value) { - parseAttachments.put(value.getClass().getName(), value); - } - - public T getParseAttachment(Class key) { - return (T) parseAttachments.get(key.getName()); + public Extras getExtras() { + return extras; } } diff --git a/src/main/java/org/javarosa/core/util/Extras.java b/src/main/java/org/javarosa/core/util/Extras.java new file mode 100644 index 000000000..899d18011 --- /dev/null +++ b/src/main/java/org/javarosa/core/util/Extras.java @@ -0,0 +1,21 @@ +package org.javarosa.core.util; + +import java.util.HashMap; + +/** + * Store for objects that allows them to be retrieved by type without any casting. + * + * @param allows a super type to be enforced for all the stored objects + */ +public class Extras { + + protected final HashMap map = new HashMap<>(); + + public void put(T extra) { + map.put(extra.getClass().getName(), extra); + } + + public U get(Class key) { + return (U) map.get(key.getName()); + } +} diff --git a/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java b/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java new file mode 100644 index 000000000..aa2744019 --- /dev/null +++ b/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java @@ -0,0 +1,27 @@ +package org.javarosa.core.util.externalizable; + +import org.javarosa.core.util.Extras; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.HashMap; + +public class ExternalizableExtras extends Extras implements Externalizable { + + @Override + public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { + HashMap extras = (HashMap) ExtUtil.read(in, new ExtWrapMap(String.class, new ExtWrapExternalizable()), pf); + extras.forEach((s, externalizable) -> put(externalizable)); + } + + @Override + public void writeExternal(DataOutputStream out) throws IOException { + HashMap wrappedParseAttachments = new HashMap<>(); + map.forEach((key, value) -> { + wrappedParseAttachments.put(key, new ExtWrapExternalizable(value)); + }); + + ExtUtil.write(out, new ExtWrapMap(wrappedParseAttachments)); + } +} diff --git a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java index 01151bff5..7aa3e2d38 100644 --- a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java +++ b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java @@ -5,8 +5,8 @@ import org.javarosa.core.model.IDataReference; import org.javarosa.core.model.instance.FormInstance; import org.javarosa.core.model.instance.TreeElement; -import org.javarosa.entities.internal.EntitiesAttachment; -import org.javarosa.entities.internal.EntityFormParseAttachment; +import org.javarosa.entities.internal.Entities; +import org.javarosa.entities.internal.EntityFormExtra; import org.javarosa.form.api.FormEntryModel; import org.javarosa.form.api.FormPostProcessor; import org.javarosa.model.xform.XPathReference; @@ -27,8 +27,8 @@ public void processForm(FormEntryModel formEntryModel) { FormDef formDef = formEntryModel.getForm(); FormInstance mainInstance = formDef.getMainInstance(); - EntityFormParseAttachment entityFormParseAttachment = formDef.getParseAttachment(EntityFormParseAttachment.class); - List> saveTos = entityFormParseAttachment.getSaveTos(); + EntityFormExtra entityFormExtra = formDef.getExtras().get(EntityFormExtra.class); + List> saveTos = entityFormExtra.getSaveTos(); String dataset = getDatasetToCreateWith(mainInstance); if (dataset != null) { @@ -38,9 +38,9 @@ public void processForm(FormEntryModel formEntryModel) { return new Pair<>(saveTo.getSecond(), answer); }).collect(Collectors.toList()); - formEntryModel.putAttachment(new EntitiesAttachment(asList(new Entity(dataset, fields)))); + formEntryModel.getExtras().put(new Entities(asList(new Entity(dataset, fields)))); } else { - formEntryModel.putAttachment(new EntitiesAttachment(emptyList())); + formEntryModel.getExtras().put(new Entities(emptyList())); } } diff --git a/src/main/java/org/javarosa/entities/internal/EntitiesAttachment.java b/src/main/java/org/javarosa/entities/internal/Entities.java similarity index 74% rename from src/main/java/org/javarosa/entities/internal/EntitiesAttachment.java rename to src/main/java/org/javarosa/entities/internal/Entities.java index 642613372..9064e9a2a 100644 --- a/src/main/java/org/javarosa/entities/internal/EntitiesAttachment.java +++ b/src/main/java/org/javarosa/entities/internal/Entities.java @@ -4,11 +4,11 @@ import java.util.List; -public class EntitiesAttachment { +public class Entities { private final List entities; - public EntitiesAttachment(List entities) { + public Entities(List entities) { this.entities = entities; } diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java b/src/main/java/org/javarosa/entities/internal/EntityFormExtra.java similarity index 89% rename from src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java rename to src/main/java/org/javarosa/entities/internal/EntityFormExtra.java index 041787dce..8b370a931 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseAttachment.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormExtra.java @@ -17,14 +17,14 @@ import java.util.Map; import java.util.stream.Collectors; -public class EntityFormParseAttachment implements Externalizable { +public class EntityFormExtra implements Externalizable { private List> saveTos = new ArrayList<>(); - public EntityFormParseAttachment() { + public EntityFormExtra() { } - public EntityFormParseAttachment(List> saveTos) { + public EntityFormExtra(List> saveTos) { this.saveTos = saveTos; } diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 21bbdd2a5..829f9916a 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -31,7 +31,7 @@ public void processBindingAttribute(String name, String value, DataBinding bindi @Override public void processFormDef(FormDef formDef) { - EntityFormParseAttachment entityFormParseAttachment = new EntityFormParseAttachment(saveTos); - formDef.putParseAttachment(entityFormParseAttachment); + EntityFormExtra entityFormExtra = new EntityFormExtra(saveTos); + formDef.getExtras().put(entityFormExtra); } } diff --git a/src/main/java/org/javarosa/form/api/FormEntryModel.java b/src/main/java/org/javarosa/form/api/FormEntryModel.java index b5ea95b23..38b9ec925 100644 --- a/src/main/java/org/javarosa/form/api/FormEntryModel.java +++ b/src/main/java/org/javarosa/form/api/FormEntryModel.java @@ -16,12 +16,6 @@ package org.javarosa.form.api; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - import org.javarosa.core.model.FormDef; import org.javarosa.core.model.FormIndex; import org.javarosa.core.model.GroupDef; @@ -32,9 +26,14 @@ import org.javarosa.core.model.instance.InvalidReferenceException; import org.javarosa.core.model.instance.TreeElement; import org.javarosa.core.model.instance.TreeReference; +import org.javarosa.core.util.Extras; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + /** * The data model used during form entry. Represents the current state of the * form and provides access to the objects required by the view and the @@ -66,7 +65,7 @@ public class FormEntryModel { */ public static final int REPEAT_STRUCTURE_NON_LINEAR = 2; - private final Map, Object> attachments = new HashMap<>(); + private final Extras extras = new Extras<>(); public FormEntryModel(FormDef form) { @@ -665,12 +664,8 @@ public FormIndex decrementIndex(FormIndex index) { } } - public void putAttachment(Object value) { - attachments.put(value.getClass(), value); - } - - public T getAttachment(Class key) { - return (T) attachments.get(key); + public Extras getExtras() { + return extras; } private void decrementHelper(List indexes, List multiplicities, List elements) { diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index 13b82e589..caf970f9d 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -8,7 +8,7 @@ import org.javarosa.entities.Entity; import org.javarosa.entities.EntityFormPostProcessor; import org.javarosa.entities.EntityXFormParserFactory; -import org.javarosa.entities.internal.EntitiesAttachment; +import org.javarosa.entities.internal.Entities; import org.javarosa.xform.util.XFormUtils; import org.junit.After; import org.junit.Before; @@ -77,7 +77,7 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti scenario.answer("Tom Wambsgans"); scenario.finalizeInstance(); - List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(0)); } @@ -114,7 +114,7 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { scenario.answer("Tom Wambsgans"); scenario.finalizeInstance(); - List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).dataset, equalTo("people")); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); @@ -157,7 +157,7 @@ public void fillingFormWithNonRelevantCreate_doesNotCreateAnyEntities() throws I scenario.answer(scenario.choicesOf("/data/join").get(1)); scenario.finalizeInstance(); - List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(0)); } @@ -197,7 +197,7 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep deserializedScenario.answer("Shiv Roy"); deserializedScenario.finalizeInstance(); - List entities = deserializedScenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + List entities = deserializedScenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).dataset, equalTo("people")); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Shiv Roy")))); @@ -236,7 +236,7 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria scenario.answer("Tom Wambsgans"); scenario.finalizeInstance(); - List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } @@ -274,7 +274,7 @@ public void mustUseCorrectNamespace() throws IOException { scenario.answer("Tom Wambsgans"); scenario.finalizeInstance(); - List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(0)); } @@ -311,7 +311,7 @@ public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEnti scenario.answer(scenario.choicesOf("/data/team").get(0)); scenario.finalizeInstance(); - List entities = scenario.getFormEntryController().getModel().getAttachment(EntitiesAttachment.class).getEntities(); + List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("team", "kendall")))); } From 4aec176ffe9236117807f27db8bd19de2441fa95 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 15:18:58 +0100 Subject: [PATCH 17/36] Rename method --- .../javarosa/core/model/instance/AbstractTreeElement.java | 2 +- .../java/org/javarosa/core/model/instance/TreeElement.java | 2 +- .../java/org/javarosa/entities/EntityFormPostProcessor.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java b/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java index 8de4a7187..441b2cf88 100644 --- a/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java @@ -17,7 +17,7 @@ public interface AbstractTreeElement { public abstract String getInstanceName(); @Nullable - public T getChild(String name); + public T getFirstChild(String name); @Nullable public abstract T getChild(String name, int multiplicity); diff --git a/src/main/java/org/javarosa/core/model/instance/TreeElement.java b/src/main/java/org/javarosa/core/model/instance/TreeElement.java index c2a19f323..a707c6fea 100644 --- a/src/main/java/org/javarosa/core/model/instance/TreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/TreeElement.java @@ -223,7 +223,7 @@ public void setValue(IAnswerData value) { @Override @Nullable - public TreeElement getChild(String name) { + public TreeElement getFirstChild(String name) { return getChild(name, 0); } diff --git a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java index 7aa3e2d38..02ce8fd3d 100644 --- a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java +++ b/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java @@ -47,12 +47,12 @@ public void processForm(FormEntryModel formEntryModel) { @Nullable private String getDatasetToCreateWith(FormInstance mainInstance) { TreeElement root = mainInstance.getRoot(); - TreeElement meta = root.getChild("meta"); + TreeElement meta = root.getFirstChild("meta"); if (meta != null) { - TreeElement entity = meta.getChild("entity"); + TreeElement entity = meta.getFirstChild("entity"); if (entity != null && entity.getNamespace().equals(ENTITIES_NAMESPACE)) { - TreeElement create = entity.getChild("create"); + TreeElement create = entity.getFirstChild("create"); if (create != null) { if (create.isRelevant()) { From 44970744c8b2f50638b3cf1bdacef56cfaa12464 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 15:19:25 +0100 Subject: [PATCH 18/36] Rename field --- src/main/java/org/javarosa/entities/Entity.java | 6 +++--- src/test/java/org/javarosa/xform/parse/EntitiesTest.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/javarosa/entities/Entity.java b/src/main/java/org/javarosa/entities/Entity.java index 9d15d9caa..69b5bf469 100644 --- a/src/main/java/org/javarosa/entities/Entity.java +++ b/src/main/java/org/javarosa/entities/Entity.java @@ -7,10 +7,10 @@ public class Entity { public final String dataset; - public final List> fields; + public final List> properties; - public Entity(String dataset, List> fields) { + public Entity(String dataset, List> properties) { this.dataset = dataset; - this.fields = fields; + this.properties = properties; } } diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java index caf970f9d..7c545bd78 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/xform/parse/EntitiesTest.java @@ -117,7 +117,7 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).dataset, equalTo("people")); - assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); + assertThat(entities.get(0).properties, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } @Test @@ -200,7 +200,7 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep List entities = deserializedScenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(1)); assertThat(entities.get(0).dataset, equalTo("people")); - assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Shiv Roy")))); + assertThat(entities.get(0).properties, equalTo(asList(new Pair<>("name", "Shiv Roy")))); } @Test @@ -238,7 +238,7 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria scenario.finalizeInstance(); List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(1)); - assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); + assertThat(entities.get(0).properties, equalTo(asList(new Pair<>("name", "Tom Wambsgans")))); } @Test @@ -313,7 +313,7 @@ public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEnti scenario.finalizeInstance(); List entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities(); assertThat(entities.size(), equalTo(1)); - assertThat(entities.get(0).fields, equalTo(asList(new Pair<>("team", "kendall")))); + assertThat(entities.get(0).properties, equalTo(asList(new Pair<>("team", "kendall")))); } @Test From ee3e78d1276bf566c3f7f0f8c11deca845c46076 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 15:23:36 +0100 Subject: [PATCH 19/36] Make naming less ambiguous --- ...PostProcessor.java => EntityFinalizer.java} | 4 ++-- .../entities/EntityXFormParserFactory.java | 2 +- .../internal/EntityFormParseProcessor.java | 4 ++-- .../javarosa/form/api/FormEntryController.java | 12 ++++++------ ...tProcessor.java => FormEntryFinalizer.java} | 2 +- ...cessor.java => BindAttributeProcessor.java} | 2 +- .../parse/StandardBindAttributesProcessor.java | 14 +++++++------- .../org/javarosa/xform/parse/XFormParser.java | 12 ++++++------ .../java/org/javarosa/core/test/Scenario.java | 2 +- .../org/javarosa/xform/parse/EntitiesTest.java | 18 +++++++++--------- 10 files changed, 36 insertions(+), 36 deletions(-) rename src/main/java/org/javarosa/entities/{EntityFormPostProcessor.java => EntityFinalizer.java} (95%) rename src/main/java/org/javarosa/form/api/{FormPostProcessor.java => FormEntryFinalizer.java} (69%) rename src/main/java/org/javarosa/xform/parse/{BindingAttributeProcessor.java => BindAttributeProcessor.java} (83%) diff --git a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java b/src/main/java/org/javarosa/entities/EntityFinalizer.java similarity index 95% rename from src/main/java/org/javarosa/entities/EntityFormPostProcessor.java rename to src/main/java/org/javarosa/entities/EntityFinalizer.java index 02ce8fd3d..ddeb103ad 100644 --- a/src/main/java/org/javarosa/entities/EntityFormPostProcessor.java +++ b/src/main/java/org/javarosa/entities/EntityFinalizer.java @@ -8,7 +8,7 @@ import org.javarosa.entities.internal.Entities; import org.javarosa.entities.internal.EntityFormExtra; import org.javarosa.form.api.FormEntryModel; -import org.javarosa.form.api.FormPostProcessor; +import org.javarosa.form.api.FormEntryFinalizer; import org.javarosa.model.xform.XPathReference; import org.jetbrains.annotations.Nullable; @@ -18,7 +18,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -public class EntityFormPostProcessor implements FormPostProcessor { +public class EntityFinalizer implements FormEntryFinalizer { private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; diff --git a/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java b/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java index 1de87343d..e09421705 100644 --- a/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java +++ b/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java @@ -37,7 +37,7 @@ public XFormParser getXFormParser(Document form, Document instance) { private XFormParser configureEntityParsing(XFormParser xFormParser) { EntityFormParseProcessor processor = new EntityFormParseProcessor(); - xFormParser.addBindingAttributeProcessor(processor); + xFormParser.addBindAttributeProcessor(processor); xFormParser.addFormDefProcessor(processor); return xFormParser; diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 829f9916a..c8d4ef47f 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -4,7 +4,7 @@ import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.model.xform.XPathReference; -import org.javarosa.xform.parse.BindingAttributeProcessor; +import org.javarosa.xform.parse.BindAttributeProcessor; import org.javarosa.xform.parse.FormDefProcessor; import java.util.ArrayList; @@ -12,7 +12,7 @@ import java.util.List; import java.util.Set; -public class EntityFormParseProcessor implements BindingAttributeProcessor, FormDefProcessor { +public class EntityFormParseProcessor implements BindAttributeProcessor, FormDefProcessor { private final List> saveTos = new ArrayList<>(); diff --git a/src/main/java/org/javarosa/form/api/FormEntryController.java b/src/main/java/org/javarosa/form/api/FormEntryController.java index fac98e37c..e2b49cbd1 100644 --- a/src/main/java/org/javarosa/form/api/FormEntryController.java +++ b/src/main/java/org/javarosa/form/api/FormEntryController.java @@ -51,7 +51,7 @@ public class FormEntryController { FormEntryModel model; - private final List formPostProcessors = new ArrayList<>(); + private final List formEntryFinalizers = new ArrayList<>(); /** * Creates a new form entry controller for the model provided @@ -202,15 +202,15 @@ public int stepToPreviousEvent() { return stepEvent(false); } - public void finalizeForm() { + public void finalizeFormEntry() { model.getForm().postProcessInstance(); - formPostProcessors.stream().forEach(formPostProcessor -> { - formPostProcessor.processForm(model); + formEntryFinalizers.stream().forEach(formEntryFinalizer -> { + formEntryFinalizer.processForm(model); }); } - public void addPostProcessor(FormPostProcessor formPostProcessor) { - formPostProcessors.add(formPostProcessor); + public void addPostProcessor(FormEntryFinalizer formEntryFinalizer) { + formEntryFinalizers.add(formEntryFinalizer); } /** diff --git a/src/main/java/org/javarosa/form/api/FormPostProcessor.java b/src/main/java/org/javarosa/form/api/FormEntryFinalizer.java similarity index 69% rename from src/main/java/org/javarosa/form/api/FormPostProcessor.java rename to src/main/java/org/javarosa/form/api/FormEntryFinalizer.java index 218817254..553ea3548 100644 --- a/src/main/java/org/javarosa/form/api/FormPostProcessor.java +++ b/src/main/java/org/javarosa/form/api/FormEntryFinalizer.java @@ -1,6 +1,6 @@ package org.javarosa.form.api; -public interface FormPostProcessor { +public interface FormEntryFinalizer { void processForm(FormEntryModel formEntryModel); } diff --git a/src/main/java/org/javarosa/xform/parse/BindingAttributeProcessor.java b/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java similarity index 83% rename from src/main/java/org/javarosa/xform/parse/BindingAttributeProcessor.java rename to src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java index 0a8fee334..bcd028a7f 100644 --- a/src/main/java/org/javarosa/xform/parse/BindingAttributeProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java @@ -4,7 +4,7 @@ import java.util.Set; -public interface BindingAttributeProcessor { +public interface BindAttributeProcessor { Set getUsedAttributes(); diff --git a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java index e48b940c0..3b4555949 100644 --- a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java @@ -37,7 +37,7 @@ class StandardBindAttributesProcessor { DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef, Collection usedAttributes, Collection passedThroughAttributes, - Element element, List bindingAttributeProcessors) { + Element element, List bindAttributeProcessors) { final DataBinding binding = new DataBinding(); binding.setId(element.getAttributeValue("", ID_ATTR)); @@ -116,18 +116,18 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef binding.setPreload(element.getAttributeValue(NAMESPACE_JAVAROSA, "preload")); binding.setPreloadParams(element.getAttributeValue(NAMESPACE_JAVAROSA, "preloadParams")); - bindingAttributeProcessors.stream().forEach(bindingAttributeProcessor -> { + bindAttributeProcessors.stream().forEach(bindAttributeProcessor -> { for (int i = 0; i < element.getAttributeCount(); i++) { String name = element.getAttributeName(i); - if (bindingAttributeProcessor.getUsedAttributes().contains(name)) { - bindingAttributeProcessor.processBindingAttribute(name, element.getAttributeValue(i), binding); + if (bindAttributeProcessor.getUsedAttributes().contains(name)) { + bindAttributeProcessor.processBindingAttribute(name, element.getAttributeValue(i), binding); } } }); - List processorAttributes = bindingAttributeProcessors.stream() - .flatMap((Function>) bindingAttributeProcessor -> { - return bindingAttributeProcessor.getUsedAttributes().stream(); + List processorAttributes = bindAttributeProcessors.stream() + .flatMap((Function>) bindAttributeProcessor -> { + return bindAttributeProcessor.getUsedAttributes().stream(); }) .collect(Collectors.toList()); diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 7b02bf6da..986c0cfa8 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -174,7 +174,7 @@ public class XFormParser implements IXFormParserFunctions { private List itextKnownForms; private static HashMap actionHandlers; - private final List bindingAttributeProcessors = new ArrayList<>(); + private final List bindAttributeProcessors = new ArrayList<>(); private final List formDefProcessors = new ArrayList<>(); /** @@ -404,8 +404,8 @@ public FormDef parse(String formXmlSrc, String lastSavedSrc) throws IOException return _f; } - public void addBindingAttributeProcessor(BindingAttributeProcessor bindingAttributeProcessor) { - bindingAttributeProcessors.add(bindingAttributeProcessor); + public void addBindAttributeProcessor(BindAttributeProcessor bindAttributeProcessor) { + bindAttributeProcessors.add(bindAttributeProcessor); } public void addFormDefProcessor(FormDefProcessor formDefProcessor) { @@ -1886,9 +1886,9 @@ private boolean hasSpecialFormMapping(String textID, String locale) { return false; } - private DataBinding processStandardBindAttributes(List usedAtts, List passedThroughAtts, Element element, List bindingAttributeProcessors) { + private DataBinding processStandardBindAttributes(List usedAtts, List passedThroughAtts, Element element, List bindAttributeProcessors) { return new StandardBindAttributesProcessor(typeMappings). - createBinding(this, _f, usedAtts, passedThroughAtts, element, bindingAttributeProcessors); + createBinding(this, _f, usedAtts, passedThroughAtts, element, bindAttributeProcessors); } /** @@ -1920,7 +1920,7 @@ private DataBinding processStandardBindAttributes(List usedAtts, List Date: Fri, 30 Sep 2022 15:40:15 +0100 Subject: [PATCH 20/36] Validate namespace for create action --- .../javarosa/entities/EntityFinalizer.java | 28 +-------- .../internal/EntityDatasetParser.java | 35 +++++++++++ .../entities/EntityDatasetParserTest.java | 61 +++++++++++++++++++ 3 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java create mode 100644 src/test/java/org/javarosa/entities/EntityDatasetParserTest.java diff --git a/src/main/java/org/javarosa/entities/EntityFinalizer.java b/src/main/java/org/javarosa/entities/EntityFinalizer.java index ddeb103ad..27af0a209 100644 --- a/src/main/java/org/javarosa/entities/EntityFinalizer.java +++ b/src/main/java/org/javarosa/entities/EntityFinalizer.java @@ -4,13 +4,12 @@ import org.javarosa.core.model.FormDef; import org.javarosa.core.model.IDataReference; import org.javarosa.core.model.instance.FormInstance; -import org.javarosa.core.model.instance.TreeElement; import org.javarosa.entities.internal.Entities; +import org.javarosa.entities.internal.EntityDatasetParser; import org.javarosa.entities.internal.EntityFormExtra; import org.javarosa.form.api.FormEntryModel; import org.javarosa.form.api.FormEntryFinalizer; import org.javarosa.model.xform.XPathReference; -import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.stream.Collectors; @@ -20,8 +19,6 @@ public class EntityFinalizer implements FormEntryFinalizer { - private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; - @Override public void processForm(FormEntryModel formEntryModel) { FormDef formDef = formEntryModel.getForm(); @@ -30,7 +27,7 @@ public void processForm(FormEntryModel formEntryModel) { EntityFormExtra entityFormExtra = formDef.getExtras().get(EntityFormExtra.class); List> saveTos = entityFormExtra.getSaveTos(); - String dataset = getDatasetToCreateWith(mainInstance); + String dataset = EntityDatasetParser.parseFirstDatasetToCreate(mainInstance); if (dataset != null) { List> fields = saveTos.stream().map(saveTo -> { IDataReference reference = saveTo.getFirst(); @@ -43,25 +40,4 @@ public void processForm(FormEntryModel formEntryModel) { formEntryModel.getExtras().put(new Entities(emptyList())); } } - - @Nullable - private String getDatasetToCreateWith(FormInstance mainInstance) { - TreeElement root = mainInstance.getRoot(); - TreeElement meta = root.getFirstChild("meta"); - if (meta != null) { - TreeElement entity = meta.getFirstChild("entity"); - - if (entity != null && entity.getNamespace().equals(ENTITIES_NAMESPACE)) { - TreeElement create = entity.getFirstChild("create"); - - if (create != null) { - if (create.isRelevant()) { - return entity.getAttributeValue(null, "dataset"); - } - } - } - } - - return null; - } } diff --git a/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java b/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java new file mode 100644 index 000000000..e384f4103 --- /dev/null +++ b/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java @@ -0,0 +1,35 @@ +package org.javarosa.entities.internal; + +import org.javarosa.core.model.instance.FormInstance; +import org.javarosa.core.model.instance.TreeElement; +import org.jetbrains.annotations.Nullable; + +public class EntityDatasetParser { + + private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; + + private EntityDatasetParser() { + + } + + @Nullable + public static String parseFirstDatasetToCreate(FormInstance mainInstance) { + TreeElement root = mainInstance.getRoot(); + TreeElement meta = root.getFirstChild("meta"); + if (meta != null) { + TreeElement entity = meta.getFirstChild("entity"); + + if (entity != null && entity.getNamespace().equals(ENTITIES_NAMESPACE)) { + TreeElement create = entity.getFirstChild("create"); + + if (create != null && create.getNamespace().equals(ENTITIES_NAMESPACE)) { + if (create.isRelevant()) { + return entity.getAttributeValue(null, "dataset"); + } + } + } + } + + return null; + } +} diff --git a/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java b/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java new file mode 100644 index 000000000..b3f047be4 --- /dev/null +++ b/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java @@ -0,0 +1,61 @@ +package org.javarosa.entities; + +import kotlin.Pair; +import org.javarosa.core.model.FormDef; +import org.javarosa.core.model.instance.FormInstance; +import org.javarosa.core.util.XFormsElement; +import org.javarosa.entities.internal.EntityDatasetParser; +import org.javarosa.xform.util.XFormUtils; +import org.junit.Test; + +import java.io.ByteArrayInputStream; + +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.javarosa.core.util.BindBuilderXFormsElement.bind; +import static org.javarosa.core.util.XFormsElement.body; +import static org.javarosa.core.util.XFormsElement.head; +import static org.javarosa.core.util.XFormsElement.input; +import static org.javarosa.core.util.XFormsElement.mainInstance; +import static org.javarosa.core.util.XFormsElement.model; +import static org.javarosa.core.util.XFormsElement.t; +import static org.javarosa.core.util.XFormsElement.title; + +public class EntityDatasetParserTest { + + @Test + public void parseFirstDatasetToCreate_ignoresDatasetWithCreateActionWithIncorrectNamespace() { + XFormsElement form = XFormsElement.html( + asList( + new Pair<>("correct", "http://www.opendatakit.org/xforms/entities"), + new Pair<>("incorrect", "blah") + ), + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("correct:entity dataset=\"people\"", + t("incorrect:create") + ) + ) + ) + ), + bind("/data/name").type("string").withAttribute("correct", "saveto", "name") + ) + ), + body( + input("/data/name") + ) + ); + + FormDef formDef = XFormUtils.getFormFromInputStream(new ByteArrayInputStream(form.asXml().getBytes())); + FormInstance mainInstance = formDef.getMainInstance(); + + String dataset = EntityDatasetParser.parseFirstDatasetToCreate(mainInstance); + assertThat(dataset, equalTo(null)); + } +} \ No newline at end of file From 96a057bb103b2a8e9141abc1b3ff2bc2361cf292 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 15:45:19 +0100 Subject: [PATCH 21/36] Add convenience method for getting namespaced children --- .../core/model/instance/AbstractTreeElement.java | 3 +++ .../javarosa/core/model/instance/TreeElement.java | 12 ++++++++++++ .../entities/internal/EntityDatasetParser.java | 8 ++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java b/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java index 441b2cf88..2e5fd37e4 100644 --- a/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/AbstractTreeElement.java @@ -19,6 +19,9 @@ public interface AbstractTreeElement { @Nullable public T getFirstChild(String name); + @Nullable + public T getFirstChild(String namespace, String name); + @Nullable public abstract T getChild(String name, int multiplicity); diff --git a/src/main/java/org/javarosa/core/model/instance/TreeElement.java b/src/main/java/org/javarosa/core/model/instance/TreeElement.java index a707c6fea..48bdad219 100644 --- a/src/main/java/org/javarosa/core/model/instance/TreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/TreeElement.java @@ -227,6 +227,18 @@ public TreeElement getFirstChild(String name) { return getChild(name, 0); } + @Nullable + @Override + public TreeElement getFirstChild(String namespace, String name) { + TreeElement firstChild = getFirstChild(name); + + if (firstChild == null || firstChild.getNamespace().equals(namespace)) { + return firstChild; + } else { + return null; + } + } + @Override @Nullable public TreeElement getChild(String name, int multiplicity) { diff --git a/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java b/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java index e384f4103..89a9e0d3e 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java +++ b/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java @@ -17,12 +17,12 @@ public static String parseFirstDatasetToCreate(FormInstance mainInstance) { TreeElement root = mainInstance.getRoot(); TreeElement meta = root.getFirstChild("meta"); if (meta != null) { - TreeElement entity = meta.getFirstChild("entity"); + TreeElement entity = meta.getFirstChild(ENTITIES_NAMESPACE, "entity"); - if (entity != null && entity.getNamespace().equals(ENTITIES_NAMESPACE)) { - TreeElement create = entity.getFirstChild("create"); + if (entity != null) { + TreeElement create = entity.getFirstChild(ENTITIES_NAMESPACE, "create"); - if (create != null && create.getNamespace().equals(ENTITIES_NAMESPACE)) { + if (create != null) { if (create.isRelevant()) { return entity.getAttributeValue(null, "dataset"); } From d8d28c64b49b1992ad4728498cbe2edbdbe3c5ab Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 15:45:51 +0100 Subject: [PATCH 22/36] Move test --- .../javarosa/{xform/parse => entities}/EntitiesTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename src/test/java/org/javarosa/{xform/parse => entities}/EntitiesTest.java (98%) diff --git a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java b/src/test/java/org/javarosa/entities/EntitiesTest.java similarity index 98% rename from src/test/java/org/javarosa/xform/parse/EntitiesTest.java rename to src/test/java/org/javarosa/entities/EntitiesTest.java index 6685c4193..95ae2ea1c 100644 --- a/src/test/java/org/javarosa/xform/parse/EntitiesTest.java +++ b/src/test/java/org/javarosa/entities/EntitiesTest.java @@ -1,14 +1,12 @@ -package org.javarosa.xform.parse; +package org.javarosa.entities; import kotlin.Pair; import org.javarosa.core.model.instance.TreeElement; import org.javarosa.core.test.Scenario; import org.javarosa.core.util.XFormsElement; import org.javarosa.core.util.externalizable.DeserializationException; -import org.javarosa.entities.Entity; -import org.javarosa.entities.EntityFinalizer; -import org.javarosa.entities.EntityXFormParserFactory; import org.javarosa.entities.internal.Entities; +import org.javarosa.xform.parse.XFormParserFactory; import org.javarosa.xform.util.XFormUtils; import org.junit.After; import org.junit.Before; From 43fff8de2af36d662bbf6b77043a5e942fd6b7dd Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 16:01:32 +0100 Subject: [PATCH 23/36] Ignore saveto attributes with incorrect namespaces --- .../internal/EntityFormParseProcessor.java | 8 ++- .../xform/parse/BindAttributeProcessor.java | 3 +- .../StandardBindAttributesProcessor.java | 7 +- .../EntityFormParseProcessorTest.java | 65 +++++++++++++++++++ 4 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index c8d4ef47f..0050fe637 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -14,12 +14,14 @@ public class EntityFormParseProcessor implements BindAttributeProcessor, FormDefProcessor { + private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; + private final List> saveTos = new ArrayList<>(); @Override - public Set getUsedAttributes() { - HashSet attributes = new HashSet<>(); - attributes.add("saveto"); + public Set> getUsedAttributes() { + HashSet> attributes = new HashSet<>(); + attributes.add(new Pair<>(ENTITIES_NAMESPACE, "saveto")); return attributes; } diff --git a/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java b/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java index bcd028a7f..df51b7cca 100644 --- a/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java @@ -1,12 +1,13 @@ package org.javarosa.xform.parse; +import kotlin.Pair; import org.javarosa.core.model.DataBinding; import java.util.Set; public interface BindAttributeProcessor { - Set getUsedAttributes(); + Set> getUsedAttributes(); void processBindingAttribute(String name, String value, DataBinding binding); } diff --git a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java index 3b4555949..07020ab2d 100644 --- a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java @@ -12,6 +12,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.IDataReference; @@ -118,8 +119,10 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef bindAttributeProcessors.stream().forEach(bindAttributeProcessor -> { for (int i = 0; i < element.getAttributeCount(); i++) { + String namespace = element.getAttributeNamespace(i); String name = element.getAttributeName(i); - if (bindAttributeProcessor.getUsedAttributes().contains(name)) { + + if (bindAttributeProcessor.getUsedAttributes().contains(new Pair<>(namespace, name))) { bindAttributeProcessor.processBindingAttribute(name, element.getAttributeValue(i), binding); } } @@ -127,7 +130,7 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef List processorAttributes = bindAttributeProcessors.stream() .flatMap((Function>) bindAttributeProcessor -> { - return bindAttributeProcessor.getUsedAttributes().stream(); + return bindAttributeProcessor.getUsedAttributes().stream().map(Pair::getSecond); }) .collect(Collectors.toList()); diff --git a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java new file mode 100644 index 000000000..3e26d49d4 --- /dev/null +++ b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java @@ -0,0 +1,65 @@ +package org.javarosa.entities.internal; + +import kotlin.Pair; +import org.javarosa.core.model.FormDef; +import org.javarosa.core.util.XFormsElement; +import org.javarosa.xform.parse.XFormParser; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.javarosa.core.util.BindBuilderXFormsElement.bind; +import static org.javarosa.core.util.XFormsElement.body; +import static org.javarosa.core.util.XFormsElement.head; +import static org.javarosa.core.util.XFormsElement.input; +import static org.javarosa.core.util.XFormsElement.mainInstance; +import static org.javarosa.core.util.XFormsElement.model; +import static org.javarosa.core.util.XFormsElement.t; +import static org.javarosa.core.util.XFormsElement.title; + +public class EntityFormParseProcessorTest { + + @Test + public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException { + XFormsElement form = XFormsElement.html( + asList( + new Pair<>("correct", "http://www.opendatakit.org/xforms/entities"), + new Pair<>("incorrect", "blah") + ), + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("correct:entity dataset=\"people\"", + t("correct:create") + ) + ) + ) + ), + bind("/data/name").type("string").withAttribute("incorrect", "saveto", "name") + ) + ), + body( + input("/data/name") + ) + ); + + EntityFormParseProcessor processor = new EntityFormParseProcessor(); + + XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); + parser.addBindAttributeProcessor(processor); + parser.addFormDefProcessor(processor); + + FormDef formDef = parser.parse(null); + assertThat(formDef.getExtras().get(EntityFormExtra.class).getSaveTos(), is(empty())); + } +} \ No newline at end of file From eb6568e74c9422c56f52b219ce5d7996ab5f38b6 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 16:05:01 +0100 Subject: [PATCH 24/36] Use simpler parsing code --- .../javarosa/entities/EntityDatasetParserTest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java b/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java index b3f047be4..23470360f 100644 --- a/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java +++ b/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java @@ -5,10 +5,13 @@ import org.javarosa.core.model.instance.FormInstance; import org.javarosa.core.util.XFormsElement; import org.javarosa.entities.internal.EntityDatasetParser; +import org.javarosa.xform.parse.XFormParser; import org.javarosa.xform.util.XFormUtils; import org.junit.Test; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; @@ -25,7 +28,7 @@ public class EntityDatasetParserTest { @Test - public void parseFirstDatasetToCreate_ignoresDatasetWithCreateActionWithIncorrectNamespace() { + public void parseFirstDatasetToCreate_ignoresDatasetWithCreateActionWithIncorrectNamespace() throws IOException { XFormsElement form = XFormsElement.html( asList( new Pair<>("correct", "http://www.opendatakit.org/xforms/entities"), @@ -52,10 +55,10 @@ public void parseFirstDatasetToCreate_ignoresDatasetWithCreateActionWithIncorrec ) ); - FormDef formDef = XFormUtils.getFormFromInputStream(new ByteArrayInputStream(form.asXml().getBytes())); - FormInstance mainInstance = formDef.getMainInstance(); + XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); + FormDef formDef = parser.parse(null); - String dataset = EntityDatasetParser.parseFirstDatasetToCreate(mainInstance); + String dataset = EntityDatasetParser.parseFirstDatasetToCreate(formDef.getMainInstance()); assertThat(dataset, equalTo(null)); } } \ No newline at end of file From 6946ecc2d98a435682088f84c630f6f692e0353d Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 16:11:26 +0100 Subject: [PATCH 25/36] Make it easier to add processor that deals with multiple stages of parse --- .../entities/EntityXFormParserFactory.java | 3 +-- .../internal/EntityFormParseProcessor.java | 5 ++-- .../xform/parse/BindAttributeProcessor.java | 13 ---------- .../xform/parse/FormDefProcessor.java | 7 ----- .../StandardBindAttributesProcessor.java | 4 +-- .../org/javarosa/xform/parse/XFormParser.java | 26 +++++++++++++++++++ .../entities/EntityDatasetParserTest.java | 2 -- .../EntityFormParseProcessorTest.java | 3 +-- 8 files changed, 32 insertions(+), 31 deletions(-) delete mode 100644 src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java delete mode 100644 src/main/java/org/javarosa/xform/parse/FormDefProcessor.java diff --git a/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java b/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java index e09421705..2a9ce7186 100644 --- a/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java +++ b/src/main/java/org/javarosa/entities/EntityXFormParserFactory.java @@ -37,8 +37,7 @@ public XFormParser getXFormParser(Document form, Document instance) { private XFormParser configureEntityParsing(XFormParser xFormParser) { EntityFormParseProcessor processor = new EntityFormParseProcessor(); - xFormParser.addBindAttributeProcessor(processor); - xFormParser.addFormDefProcessor(processor); + xFormParser.addProcessor(processor); return xFormParser; } diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 0050fe637..39742fd74 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -4,15 +4,14 @@ import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.model.xform.XPathReference; -import org.javarosa.xform.parse.BindAttributeProcessor; -import org.javarosa.xform.parse.FormDefProcessor; +import org.javarosa.xform.parse.XFormParser; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -public class EntityFormParseProcessor implements BindAttributeProcessor, FormDefProcessor { +public class EntityFormParseProcessor implements XFormParser.BindAttributeProcessor, XFormParser.FormDefProcessor { private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; diff --git a/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java b/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java deleted file mode 100644 index df51b7cca..000000000 --- a/src/main/java/org/javarosa/xform/parse/BindAttributeProcessor.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.javarosa.xform.parse; - -import kotlin.Pair; -import org.javarosa.core.model.DataBinding; - -import java.util.Set; - -public interface BindAttributeProcessor { - - Set> getUsedAttributes(); - - void processBindingAttribute(String name, String value, DataBinding binding); -} diff --git a/src/main/java/org/javarosa/xform/parse/FormDefProcessor.java b/src/main/java/org/javarosa/xform/parse/FormDefProcessor.java deleted file mode 100644 index dd402f3c6..000000000 --- a/src/main/java/org/javarosa/xform/parse/FormDefProcessor.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.javarosa.xform.parse; - -import org.javarosa.core.model.FormDef; - -public interface FormDefProcessor { - void processFormDef(FormDef formDef); -} diff --git a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java index 07020ab2d..6b83540dc 100644 --- a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java @@ -38,7 +38,7 @@ class StandardBindAttributesProcessor { DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef, Collection usedAttributes, Collection passedThroughAttributes, - Element element, List bindAttributeProcessors) { + Element element, List bindAttributeProcessors) { final DataBinding binding = new DataBinding(); binding.setId(element.getAttributeValue("", ID_ATTR)); @@ -129,7 +129,7 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef }); List processorAttributes = bindAttributeProcessors.stream() - .flatMap((Function>) bindAttributeProcessor -> { + .flatMap((Function>) bindAttributeProcessor -> { return bindAttributeProcessor.getUsedAttributes().stream().map(Pair::getSecond); }) .collect(Collectors.toList()); diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 986c0cfa8..699510786 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -16,6 +16,7 @@ package org.javarosa.xform.parse; +import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.GroupDef; @@ -404,6 +405,16 @@ public FormDef parse(String formXmlSrc, String lastSavedSrc) throws IOException return _f; } + public void addProcessor(Processor processor) { + if (processor instanceof BindAttributeProcessor) { + addBindAttributeProcessor((BindAttributeProcessor) processor); + } + + if (processor instanceof FormDefProcessor) { + addFormDefProcessor((FormDefProcessor) processor); + } + } + public void addBindAttributeProcessor(BindAttributeProcessor bindAttributeProcessor) { bindAttributeProcessors.add(bindAttributeProcessor); } @@ -2383,4 +2394,19 @@ public interface WarningCallback { public interface ErrorCallback { void accept(String message); } + + public interface Processor { + + } + + public interface FormDefProcessor extends Processor { + void processFormDef(FormDef formDef); + } + + public interface BindAttributeProcessor extends Processor { + + Set> getUsedAttributes(); + + void processBindingAttribute(String name, String value, DataBinding binding); + } } diff --git a/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java b/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java index 23470360f..2605485b5 100644 --- a/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java +++ b/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java @@ -2,11 +2,9 @@ import kotlin.Pair; import org.javarosa.core.model.FormDef; -import org.javarosa.core.model.instance.FormInstance; import org.javarosa.core.util.XFormsElement; import org.javarosa.entities.internal.EntityDatasetParser; import org.javarosa.xform.parse.XFormParser; -import org.javarosa.xform.util.XFormUtils; import org.junit.Test; import java.io.ByteArrayInputStream; diff --git a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java index 3e26d49d4..968ce6f13 100644 --- a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java +++ b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java @@ -56,8 +56,7 @@ public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException { EntityFormParseProcessor processor = new EntityFormParseProcessor(); XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); - parser.addBindAttributeProcessor(processor); - parser.addFormDefProcessor(processor); + parser.addProcessor(processor); FormDef formDef = parser.parse(null); assertThat(formDef.getExtras().get(EntityFormExtra.class).getSaveTos(), is(empty())); From ced91b0ac3949f44738c362da064e1733113aa05 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 30 Sep 2022 18:16:02 +0100 Subject: [PATCH 26/36] Validate entity version if defined in model --- .../UnrecognizedEntityVersionException.java | 6 ++ .../internal/EntityFormParseProcessor.java | 31 +++++++- .../StandardBindAttributesProcessor.java | 6 +- .../org/javarosa/xform/parse/XFormParser.java | 34 ++++++++- .../EntityFormParseProcessorTest.java | 71 +++++++++++++++++++ 5 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/javarosa/entities/UnrecognizedEntityVersionException.java diff --git a/src/main/java/org/javarosa/entities/UnrecognizedEntityVersionException.java b/src/main/java/org/javarosa/entities/UnrecognizedEntityVersionException.java new file mode 100644 index 000000000..974fa97e8 --- /dev/null +++ b/src/main/java/org/javarosa/entities/UnrecognizedEntityVersionException.java @@ -0,0 +1,6 @@ +package org.javarosa.entities; + +import org.javarosa.xform.parse.XFormParseException; + +public class UnrecognizedEntityVersionException extends XFormParseException { +} diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 39742fd74..bfae738ae 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -3,7 +3,11 @@ import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; +import org.javarosa.core.model.instance.FormInstance; +import org.javarosa.core.model.instance.TreeElement; +import org.javarosa.entities.UnrecognizedEntityVersionException; import org.javarosa.model.xform.XPathReference; +import org.javarosa.xform.parse.XFormParseException; import org.javarosa.xform.parse.XFormParser; import java.util.ArrayList; @@ -11,14 +15,35 @@ import java.util.List; import java.util.Set; -public class EntityFormParseProcessor implements XFormParser.BindAttributeProcessor, XFormParser.FormDefProcessor { +public class EntityFormParseProcessor implements XFormParser.BindAttributeProcessor, XFormParser.FormDefProcessor, XFormParser.ModelAttributeProcessor { private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; + public static final String SUPPORTED_VERSION = "v2022.1"; private final List> saveTos = new ArrayList<>(); @Override - public Set> getUsedAttributes() { + public Set> getUsedModelAttributes() { + HashSet> attributes = new HashSet<>(); + attributes.add(new Pair<>(ENTITIES_NAMESPACE, "entities-version")); + + return attributes; + } + + @Override + public void processModelAttribute(String name, String value) throws XFormParseException { + try { + String[] versionParts = value.split("\\."); + if (!SUPPORTED_VERSION.equals(versionParts[0] + "." + versionParts[1])) { + throw new UnrecognizedEntityVersionException(); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new UnrecognizedEntityVersionException(); + } + } + + @Override + public Set> getUsedBindAttributes() { HashSet> attributes = new HashSet<>(); attributes.add(new Pair<>(ENTITIES_NAMESPACE, "saveto")); @@ -26,7 +51,7 @@ public Set> getUsedAttributes() { } @Override - public void processBindingAttribute(String name, String value, DataBinding binding) { + public void processBindAttribute(String name, String value, DataBinding binding) { saveTos.add(new Pair<>((XPathReference) binding.getReference(), value)); } diff --git a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java index 6b83540dc..e61f499ac 100644 --- a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java @@ -122,15 +122,15 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef String namespace = element.getAttributeNamespace(i); String name = element.getAttributeName(i); - if (bindAttributeProcessor.getUsedAttributes().contains(new Pair<>(namespace, name))) { - bindAttributeProcessor.processBindingAttribute(name, element.getAttributeValue(i), binding); + if (bindAttributeProcessor.getUsedBindAttributes().contains(new Pair<>(namespace, name))) { + bindAttributeProcessor.processBindAttribute(name, element.getAttributeValue(i), binding); } } }); List processorAttributes = bindAttributeProcessors.stream() .flatMap((Function>) bindAttributeProcessor -> { - return bindAttributeProcessor.getUsedAttributes().stream().map(Pair::getSecond); + return bindAttributeProcessor.getUsedBindAttributes().stream().map(Pair::getSecond); }) .collect(Collectors.toList()); diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 699510786..843a62188 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -177,6 +177,7 @@ public class XFormParser implements IXFormParserFunctions { private final List bindAttributeProcessors = new ArrayList<>(); private final List formDefProcessors = new ArrayList<>(); + private final List modelAttributeProcessors = new ArrayList<>(); /** * The string IDs of all instances that are referenced in a instance() function call in the primary instance @@ -413,6 +414,10 @@ public void addProcessor(Processor processor) { if (processor instanceof FormDefProcessor) { addFormDefProcessor((FormDefProcessor) processor); } + + if (processor instanceof ModelAttributeProcessor) { + addModelAttributeProcessor((ModelAttributeProcessor) processor); + } } public void addBindAttributeProcessor(BindAttributeProcessor bindAttributeProcessor) { @@ -423,6 +428,10 @@ public void addFormDefProcessor(FormDefProcessor formDefProcessor) { formDefProcessors.add(formDefProcessor); } + public void addModelAttributeProcessor(ModelAttributeProcessor modelAttributeProcessor) { + modelAttributeProcessors.add(modelAttributeProcessor); + } + /** * Extracts the namespaces from the given element and creates a map of URI to prefix */ @@ -673,7 +682,19 @@ private void parseMeta(Element e) { } //for ease of parsing, we assume a model comes before the controls, which isn't necessarily mandated by the xforms spec - private void parseModel(Element e) { + private void parseModel(Element e) throws XFormParseException { + modelAttributeProcessors.stream().forEach(processor -> { + for (int i = 0; i < e.getAttributeCount(); i++) { + String namespace = e.getAttributeNamespace(i); + String name = e.getAttributeName(i); + String value = e.getAttributeValue(i); + + if (processor.getUsedModelAttributes().contains(new Pair<>(namespace, name))) { + processor.processModelAttribute(name, value); + } + } + }); + List usedAtts = new ArrayList<>(); //no attributes parsed in title. List delayedParseElements = new ArrayList<>(); @@ -2405,8 +2426,15 @@ public interface FormDefProcessor extends Processor { public interface BindAttributeProcessor extends Processor { - Set> getUsedAttributes(); + Set> getUsedBindAttributes(); + + void processBindAttribute(String name, String value, DataBinding binding); + } + + public interface ModelAttributeProcessor extends Processor { + + Set> getUsedModelAttributes(); - void processBindingAttribute(String name, String value, DataBinding binding); + void processModelAttribute(String name, String value) throws XFormParseException; } } diff --git a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java index 968ce6f13..16fdde949 100644 --- a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java +++ b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java @@ -3,6 +3,7 @@ import kotlin.Pair; import org.javarosa.core.model.FormDef; import org.javarosa.core.util.XFormsElement; +import org.javarosa.entities.UnrecognizedEntityVersionException; import org.javarosa.xform.parse.XFormParser; import org.junit.Test; @@ -25,6 +26,76 @@ public class EntityFormParseProcessorTest { + @Test(expected = UnrecognizedEntityVersionException.class) + public void whenVersionIsNotRecognized_throwsException() throws IOException { + XFormsElement form = XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), + head( + title("Create entity form"), + t("model entities:entities-version=\"somethingElse\"", + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("entities:entity dataset=\"people\"", + t("entities:create") + ) + ) + ) + ), + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") + ) + ), + body( + input("/data/name") + ) + ); + + EntityFormParseProcessor processor = new EntityFormParseProcessor(); + + XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); + parser.addProcessor(processor); + parser.parse(null); + } + + @Test + public void whenVersionIsNewPatch_doesNotThrowException() throws IOException { + String newPatchVersion = EntityFormParseProcessor.SUPPORTED_VERSION + ".12"; + + XFormsElement form = XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), + head( + title("Create entity form"), + t("model entities:entities-version=\"" + newPatchVersion + "\"", + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("entities:entity dataset=\"people\"", + t("entities:create") + ) + ) + ) + ), + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") + ) + ), + body( + input("/data/name") + ) + ); + + EntityFormParseProcessor processor = new EntityFormParseProcessor(); + + XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); + parser.addProcessor(processor); + parser.parse(null); + } + @Test public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException { XFormsElement form = XFormsElement.html( From 71b75afcd2d68aca07c7ef0f0f7849e9c7ce9c47 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 10:44:33 +0100 Subject: [PATCH 27/36] Keep 'processor' consistent in naming --- ...zer.java => EntityFinalizationProcessor.java} | 4 ++-- .../javarosa/form/api/FormEntryController.java | 10 +++++----- ....java => FormEntryFinalizationProcessor.java} | 2 +- .../java/org/javarosa/entities/EntitiesTest.java | 16 ++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) rename src/main/java/org/javarosa/entities/{EntityFinalizer.java => EntityFinalizationProcessor.java} (91%) rename src/main/java/org/javarosa/form/api/{FormEntryFinalizer.java => FormEntryFinalizationProcessor.java} (63%) diff --git a/src/main/java/org/javarosa/entities/EntityFinalizer.java b/src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java similarity index 91% rename from src/main/java/org/javarosa/entities/EntityFinalizer.java rename to src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java index 27af0a209..f5005fe74 100644 --- a/src/main/java/org/javarosa/entities/EntityFinalizer.java +++ b/src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java @@ -8,7 +8,7 @@ import org.javarosa.entities.internal.EntityDatasetParser; import org.javarosa.entities.internal.EntityFormExtra; import org.javarosa.form.api.FormEntryModel; -import org.javarosa.form.api.FormEntryFinalizer; +import org.javarosa.form.api.FormEntryFinalizationProcessor; import org.javarosa.model.xform.XPathReference; import java.util.List; @@ -17,7 +17,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -public class EntityFinalizer implements FormEntryFinalizer { +public class EntityFinalizationProcessor implements FormEntryFinalizationProcessor { @Override public void processForm(FormEntryModel formEntryModel) { diff --git a/src/main/java/org/javarosa/form/api/FormEntryController.java b/src/main/java/org/javarosa/form/api/FormEntryController.java index e2b49cbd1..486b0ef32 100644 --- a/src/main/java/org/javarosa/form/api/FormEntryController.java +++ b/src/main/java/org/javarosa/form/api/FormEntryController.java @@ -51,7 +51,7 @@ public class FormEntryController { FormEntryModel model; - private final List formEntryFinalizers = new ArrayList<>(); + private final List formEntryFinalizationProcessors = new ArrayList<>(); /** * Creates a new form entry controller for the model provided @@ -204,13 +204,13 @@ public int stepToPreviousEvent() { public void finalizeFormEntry() { model.getForm().postProcessInstance(); - formEntryFinalizers.stream().forEach(formEntryFinalizer -> { - formEntryFinalizer.processForm(model); + formEntryFinalizationProcessors.stream().forEach(formEntryFinalizationProcessor -> { + formEntryFinalizationProcessor.processForm(model); }); } - public void addPostProcessor(FormEntryFinalizer formEntryFinalizer) { - formEntryFinalizers.add(formEntryFinalizer); + public void addPostProcessor(FormEntryFinalizationProcessor formEntryFinalizationProcessor) { + formEntryFinalizationProcessors.add(formEntryFinalizationProcessor); } /** diff --git a/src/main/java/org/javarosa/form/api/FormEntryFinalizer.java b/src/main/java/org/javarosa/form/api/FormEntryFinalizationProcessor.java similarity index 63% rename from src/main/java/org/javarosa/form/api/FormEntryFinalizer.java rename to src/main/java/org/javarosa/form/api/FormEntryFinalizationProcessor.java index 553ea3548..179ceba0c 100644 --- a/src/main/java/org/javarosa/form/api/FormEntryFinalizer.java +++ b/src/main/java/org/javarosa/form/api/FormEntryFinalizationProcessor.java @@ -1,6 +1,6 @@ package org.javarosa.form.api; -public interface FormEntryFinalizer { +public interface FormEntryFinalizationProcessor { void processForm(FormEntryModel formEntryModel); } diff --git a/src/test/java/org/javarosa/entities/EntitiesTest.java b/src/test/java/org/javarosa/entities/EntitiesTest.java index 95ae2ea1c..69ce4c2b9 100644 --- a/src/test/java/org/javarosa/entities/EntitiesTest.java +++ b/src/test/java/org/javarosa/entities/EntitiesTest.java @@ -69,7 +69,7 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizer()); + scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); scenario.next(); scenario.answer("Tom Wambsgans"); @@ -106,7 +106,7 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizer()); + scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); scenario.next(); scenario.answer("Tom Wambsgans"); @@ -148,7 +148,7 @@ public void fillingFormWithNonRelevantCreate_doesNotCreateAnyEntities() throws I ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizer()); + scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); scenario.next(); scenario.answer("Roman Roy"); @@ -186,10 +186,10 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizer()); + scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); Scenario deserializedScenario = scenario.serializeAndDeserializeForm(); - deserializedScenario.getFormEntryController().addPostProcessor(new EntityFinalizer()); + deserializedScenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); deserializedScenario.next(); deserializedScenario.answer("Shiv Roy"); @@ -228,7 +228,7 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizer()); + scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); scenario.next(); scenario.answer("Tom Wambsgans"); @@ -266,7 +266,7 @@ public void mustUseCorrectNamespace() throws IOException { ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizer()); + scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); scenario.next(); scenario.answer("Tom Wambsgans"); @@ -303,7 +303,7 @@ public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEnti ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizer()); + scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); scenario.next(); scenario.answer(scenario.choicesOf("/data/team").get(0)); From c3f86f2a04aa943b95adc85ae895001b07b7fb3e Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 11:24:51 +0100 Subject: [PATCH 28/36] Make sure forms without entities can be parsed --- .../entities/EntityFinalizationProcessor.java | 4 +- .../internal/EntityDatasetParser.java | 35 -------- .../internal/EntityFormParseProcessor.java | 9 +- .../entities/internal/EntityFormParser.java | 43 ++++++++++ .../org/javarosa/xform/parse/XFormParser.java | 19 +++++ .../org/javarosa/core/util/XFormsElement.java | 9 ++ .../org/javarosa/entities/EntitiesTest.java | 17 ++-- ...serTest.java => EntityFormParserTest.java} | 6 +- .../EntityFormParseProcessorTest.java | 82 +++++++++++++++++-- 9 files changed, 168 insertions(+), 56 deletions(-) delete mode 100644 src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java create mode 100644 src/main/java/org/javarosa/entities/internal/EntityFormParser.java rename src/test/java/org/javarosa/entities/{EntityDatasetParserTest.java => EntityFormParserTest.java} (91%) diff --git a/src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java b/src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java index f5005fe74..a9e50dd6f 100644 --- a/src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java +++ b/src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java @@ -5,7 +5,7 @@ import org.javarosa.core.model.IDataReference; import org.javarosa.core.model.instance.FormInstance; import org.javarosa.entities.internal.Entities; -import org.javarosa.entities.internal.EntityDatasetParser; +import org.javarosa.entities.internal.EntityFormParser; import org.javarosa.entities.internal.EntityFormExtra; import org.javarosa.form.api.FormEntryModel; import org.javarosa.form.api.FormEntryFinalizationProcessor; @@ -27,7 +27,7 @@ public void processForm(FormEntryModel formEntryModel) { EntityFormExtra entityFormExtra = formDef.getExtras().get(EntityFormExtra.class); List> saveTos = entityFormExtra.getSaveTos(); - String dataset = EntityDatasetParser.parseFirstDatasetToCreate(mainInstance); + String dataset = EntityFormParser.parseFirstDatasetToCreate(mainInstance); if (dataset != null) { List> fields = saveTos.stream().map(saveTo -> { IDataReference reference = saveTo.getFirst(); diff --git a/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java b/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java deleted file mode 100644 index 89a9e0d3e..000000000 --- a/src/main/java/org/javarosa/entities/internal/EntityDatasetParser.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.javarosa.entities.internal; - -import org.javarosa.core.model.instance.FormInstance; -import org.javarosa.core.model.instance.TreeElement; -import org.jetbrains.annotations.Nullable; - -public class EntityDatasetParser { - - private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; - - private EntityDatasetParser() { - - } - - @Nullable - public static String parseFirstDatasetToCreate(FormInstance mainInstance) { - TreeElement root = mainInstance.getRoot(); - TreeElement meta = root.getFirstChild("meta"); - if (meta != null) { - TreeElement entity = meta.getFirstChild(ENTITIES_NAMESPACE, "entity"); - - if (entity != null) { - TreeElement create = entity.getFirstChild(ENTITIES_NAMESPACE, "create"); - - if (create != null) { - if (create.isRelevant()) { - return entity.getAttributeValue(null, "dataset"); - } - } - } - } - - return null; - } -} diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index bfae738ae..888aaa887 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -3,8 +3,6 @@ import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; -import org.javarosa.core.model.instance.FormInstance; -import org.javarosa.core.model.instance.TreeElement; import org.javarosa.entities.UnrecognizedEntityVersionException; import org.javarosa.model.xform.XPathReference; import org.javarosa.xform.parse.XFormParseException; @@ -21,6 +19,7 @@ public class EntityFormParseProcessor implements XFormParser.BindAttributeProces public static final String SUPPORTED_VERSION = "v2022.1"; private final List> saveTos = new ArrayList<>(); + private boolean versionPresent; @Override public Set> getUsedModelAttributes() { @@ -32,6 +31,8 @@ public Set> getUsedModelAttributes() { @Override public void processModelAttribute(String name, String value) throws XFormParseException { + versionPresent = true; + try { String[] versionParts = value.split("\\."); if (!SUPPORTED_VERSION.equals(versionParts[0] + "." + versionParts[1])) { @@ -57,6 +58,10 @@ public void processBindAttribute(String name, String value, DataBinding binding) @Override public void processFormDef(FormDef formDef) { + if (!versionPresent && EntityFormParser.getEntityElement(formDef.getMainInstance()) != null) { + throw new XFormParser.MissingModelAttributeException(ENTITIES_NAMESPACE, "entities-version"); + } + EntityFormExtra entityFormExtra = new EntityFormExtra(saveTos); formDef.getExtras().put(entityFormExtra); } diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParser.java b/src/main/java/org/javarosa/entities/internal/EntityFormParser.java new file mode 100644 index 000000000..e8e2af199 --- /dev/null +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParser.java @@ -0,0 +1,43 @@ +package org.javarosa.entities.internal; + +import org.javarosa.core.model.instance.FormInstance; +import org.javarosa.core.model.instance.TreeElement; +import org.jetbrains.annotations.Nullable; + +public class EntityFormParser { + + private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; + + private EntityFormParser() { + + } + + @Nullable + public static String parseFirstDatasetToCreate(FormInstance mainInstance) { + TreeElement entity = getEntityElement(mainInstance); + + if (entity != null) { + TreeElement create = entity.getFirstChild(ENTITIES_NAMESPACE, "create"); + + if (create != null) { + if (create.isRelevant()) { + return entity.getAttributeValue(null, "dataset"); + } + } + } + + return null; + } + + @Nullable + public static TreeElement getEntityElement(FormInstance mainInstance) { + TreeElement root = mainInstance.getRoot(); + TreeElement meta = root.getFirstChild("meta"); + + if (meta != null) { + return meta.getFirstChild(ENTITIES_NAMESPACE, "entity"); + } else { + return null; + } + } +} diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 843a62188..0b55c2603 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -2437,4 +2437,23 @@ public interface ModelAttributeProcessor extends Processor { void processModelAttribute(String name, String value) throws XFormParseException; } + + public static class MissingModelAttributeException extends XFormParseException { + + private final String namespace; + private final String name; + + public MissingModelAttributeException(String namespace, String name) { + this.namespace = namespace; + this.name = name; + } + + public String getNamespace() { + return namespace; + } + + public String getName() { + return name; + } + } } diff --git a/src/test/java/org/javarosa/core/util/XFormsElement.java b/src/test/java/org/javarosa/core/util/XFormsElement.java index c4af22a9a..19daa16b1 100644 --- a/src/test/java/org/javarosa/core/util/XFormsElement.java +++ b/src/test/java/org/javarosa/core/util/XFormsElement.java @@ -109,6 +109,15 @@ static XFormsElement model(XFormsElement... children) { return t("model", children); } + static XFormsElement model(List> attributes, XFormsElement... children) { + StringBuilder stringBuilder = new StringBuilder(); + attributes.stream().forEach(attribute -> { + stringBuilder.append(" " + attribute.getFirst() + "=\"" + attribute.getSecond() + "\""); + }); + + return t("model" + stringBuilder, children); + } + static XFormsElement mainInstance(XFormsElement... children) { return t("instance", children); } diff --git a/src/test/java/org/javarosa/entities/EntitiesTest.java b/src/test/java/org/javarosa/entities/EntitiesTest.java index 69ce4c2b9..39c0aa0f8 100644 --- a/src/test/java/org/javarosa/entities/EntitiesTest.java +++ b/src/test/java/org/javarosa/entities/EntitiesTest.java @@ -6,6 +6,7 @@ import org.javarosa.core.util.XFormsElement; import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.entities.internal.Entities; +import org.javarosa.entities.internal.EntityFormParseProcessor; import org.javarosa.xform.parse.XFormParserFactory; import org.javarosa.xform.util.XFormUtils; import org.junit.After; @@ -52,7 +53,7 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti ), head( title("Entity form"), - model( + model(asList(new Pair<>("entities:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"entity-form\"", t("name"), @@ -87,7 +88,7 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { ), head( title("Create entity form"), - model( + model(asList(new Pair<>("entities:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"create-entity-form\"", t("name"), @@ -126,7 +127,7 @@ public void fillingFormWithNonRelevantCreate_doesNotCreateAnyEntities() throws I ), head( title("Create entity form"), - model( + model(asList(new Pair<>("entities:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"create-entity-form\"", t("name"), @@ -167,7 +168,7 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep ), head( title("Create entity form"), - model( + model(asList(new Pair<>("entities:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"create-entity-form\"", t("name"), @@ -209,7 +210,7 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria ), head( title("Create entity form"), - model( + model(asList(new Pair<>("blah:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"create-entity-form\"", t("name"), @@ -247,7 +248,7 @@ public void mustUseCorrectNamespace() throws IOException { ), head( title("Create entity form"), - model( + model(asList(new Pair<>("entities:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"create-entity-form\"", t("name"), @@ -284,7 +285,7 @@ public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEnti ), head( title("Create entity form"), - model( + model(asList(new Pair<>("entities:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"create-entity-form\"", t("team"), @@ -322,7 +323,7 @@ public void savetoIsRemovedFromBindAttributesForClients() throws IOException { ), head( title("Create entity form"), - model( + model(asList(new Pair<>("entities:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"create-entity-form\"", t("name"), diff --git a/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java b/src/test/java/org/javarosa/entities/EntityFormParserTest.java similarity index 91% rename from src/test/java/org/javarosa/entities/EntityDatasetParserTest.java rename to src/test/java/org/javarosa/entities/EntityFormParserTest.java index 2605485b5..a03f2baac 100644 --- a/src/test/java/org/javarosa/entities/EntityDatasetParserTest.java +++ b/src/test/java/org/javarosa/entities/EntityFormParserTest.java @@ -3,7 +3,7 @@ import kotlin.Pair; import org.javarosa.core.model.FormDef; import org.javarosa.core.util.XFormsElement; -import org.javarosa.entities.internal.EntityDatasetParser; +import org.javarosa.entities.internal.EntityFormParser; import org.javarosa.xform.parse.XFormParser; import org.junit.Test; @@ -23,7 +23,7 @@ import static org.javarosa.core.util.XFormsElement.t; import static org.javarosa.core.util.XFormsElement.title; -public class EntityDatasetParserTest { +public class EntityFormParserTest { @Test public void parseFirstDatasetToCreate_ignoresDatasetWithCreateActionWithIncorrectNamespace() throws IOException { @@ -56,7 +56,7 @@ public void parseFirstDatasetToCreate_ignoresDatasetWithCreateActionWithIncorrec XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); FormDef formDef = parser.parse(null); - String dataset = EntityDatasetParser.parseFirstDatasetToCreate(formDef.getMainInstance()); + String dataset = EntityFormParser.parseFirstDatasetToCreate(formDef.getMainInstance()); assertThat(dataset, equalTo(null)); } } \ No newline at end of file diff --git a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java index 16fdde949..066b2d4d2 100644 --- a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java +++ b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java @@ -5,6 +5,7 @@ import org.javarosa.core.util.XFormsElement; import org.javarosa.entities.UnrecognizedEntityVersionException; import org.javarosa.xform.parse.XFormParser; +import org.javarosa.xform.parse.XFormParser.MissingModelAttributeException; import org.junit.Test; import java.io.ByteArrayInputStream; @@ -14,6 +15,8 @@ import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.javarosa.core.util.BindBuilderXFormsElement.bind; import static org.javarosa.core.util.XFormsElement.body; @@ -23,9 +26,79 @@ import static org.javarosa.core.util.XFormsElement.model; import static org.javarosa.core.util.XFormsElement.t; import static org.javarosa.core.util.XFormsElement.title; +import static org.junit.Assert.fail; public class EntityFormParseProcessorTest { + @Test + public void whenVersionIsMissing_parsesWithoutError() throws IOException { + XFormsElement form = XFormsElement.html( + head( + title("Non entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta") + ) + ), + bind("/data/name").type("string") + ) + ), + body( + input("/data/name") + ) + ); + + EntityFormParseProcessor processor = new EntityFormParseProcessor(); + XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); + parser.addProcessor(processor); + parser.parse(null); + } + + @Test + public void whenVersionIsMissing_andThereIsAnEntityElement_throwsException() { + XFormsElement form = XFormsElement.html( + asList( + new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") + ), + head( + title("Create entity form"), + model( + mainInstance( + t("data id=\"create-entity-form\"", + t("name"), + t("orx:meta", + t("entities:entity dataset=\"people\"", + t("entities:create") + ) + ) + ) + ), + bind("/data/name").type("string").withAttribute("entities", "saveto", "name") + ) + ), + body( + input("/data/name") + ) + ); + + EntityFormParseProcessor processor = new EntityFormParseProcessor(); + XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); + parser.addProcessor(processor); + + try { + parser.parse(null); + fail("Expected exception!"); + } catch (Exception e) { + assertThat(e, instanceOf(MissingModelAttributeException.class)); + + MissingModelAttributeException missingModelAttributeException = (MissingModelAttributeException) e; + assertThat(missingModelAttributeException.getNamespace(), equalTo("http://www.opendatakit.org/xforms/entities")); + assertThat(missingModelAttributeException.getName(), equalTo("entities-version")); + } + } + @Test(expected = UnrecognizedEntityVersionException.class) public void whenVersionIsNotRecognized_throwsException() throws IOException { XFormsElement form = XFormsElement.html( @@ -34,7 +107,7 @@ public void whenVersionIsNotRecognized_throwsException() throws IOException { ), head( title("Create entity form"), - t("model entities:entities-version=\"somethingElse\"", + model(asList(new Pair<>("entities:entities-version", "somethingElse")), mainInstance( t("data id=\"create-entity-form\"", t("name"), @@ -54,7 +127,6 @@ public void whenVersionIsNotRecognized_throwsException() throws IOException { ); EntityFormParseProcessor processor = new EntityFormParseProcessor(); - XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); parser.addProcessor(processor); parser.parse(null); @@ -70,7 +142,7 @@ public void whenVersionIsNewPatch_doesNotThrowException() throws IOException { ), head( title("Create entity form"), - t("model entities:entities-version=\"" + newPatchVersion + "\"", + model(asList(new Pair<>("entities:entities-version", newPatchVersion)), mainInstance( t("data id=\"create-entity-form\"", t("name"), @@ -90,7 +162,6 @@ public void whenVersionIsNewPatch_doesNotThrowException() throws IOException { ); EntityFormParseProcessor processor = new EntityFormParseProcessor(); - XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); parser.addProcessor(processor); parser.parse(null); @@ -105,7 +176,7 @@ public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException { ), head( title("Create entity form"), - model( + model(asList(new Pair<>("correct:entities-version", EntityFormParseProcessor.SUPPORTED_VERSION + ".1")), mainInstance( t("data id=\"create-entity-form\"", t("name"), @@ -125,7 +196,6 @@ public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException { ); EntityFormParseProcessor processor = new EntityFormParseProcessor(); - XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); parser.addProcessor(processor); From 331acd6fdcb1d1da11063e185c01f8c2464232d8 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 11:26:26 +0100 Subject: [PATCH 29/36] Naming improvements --- ...java => EntityFormFinalizationProcessor.java} | 2 +- .../internal/EntityFormParseProcessor.java | 4 ++-- .../parse/StandardBindAttributesProcessor.java | 4 ++-- .../org/javarosa/xform/parse/XFormParser.java | 6 +++--- .../java/org/javarosa/entities/EntitiesTest.java | 16 ++++++++-------- 5 files changed, 16 insertions(+), 16 deletions(-) rename src/main/java/org/javarosa/entities/{EntityFinalizationProcessor.java => EntityFormFinalizationProcessor.java} (94%) diff --git a/src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java b/src/main/java/org/javarosa/entities/EntityFormFinalizationProcessor.java similarity index 94% rename from src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java rename to src/main/java/org/javarosa/entities/EntityFormFinalizationProcessor.java index a9e50dd6f..8354bce2f 100644 --- a/src/main/java/org/javarosa/entities/EntityFinalizationProcessor.java +++ b/src/main/java/org/javarosa/entities/EntityFormFinalizationProcessor.java @@ -17,7 +17,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -public class EntityFinalizationProcessor implements FormEntryFinalizationProcessor { +public class EntityFormFinalizationProcessor implements FormEntryFinalizationProcessor { @Override public void processForm(FormEntryModel formEntryModel) { diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 888aaa887..87d2abada 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -22,7 +22,7 @@ public class EntityFormParseProcessor implements XFormParser.BindAttributeProces private boolean versionPresent; @Override - public Set> getUsedModelAttributes() { + public Set> getModelAttributes() { HashSet> attributes = new HashSet<>(); attributes.add(new Pair<>(ENTITIES_NAMESPACE, "entities-version")); @@ -44,7 +44,7 @@ public void processModelAttribute(String name, String value) throws XFormParseEx } @Override - public Set> getUsedBindAttributes() { + public Set> getBindAttributes() { HashSet> attributes = new HashSet<>(); attributes.add(new Pair<>(ENTITIES_NAMESPACE, "saveto")); diff --git a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java index e61f499ac..5fecc78eb 100644 --- a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java @@ -122,7 +122,7 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef String namespace = element.getAttributeNamespace(i); String name = element.getAttributeName(i); - if (bindAttributeProcessor.getUsedBindAttributes().contains(new Pair<>(namespace, name))) { + if (bindAttributeProcessor.getBindAttributes().contains(new Pair<>(namespace, name))) { bindAttributeProcessor.processBindAttribute(name, element.getAttributeValue(i), binding); } } @@ -130,7 +130,7 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef List processorAttributes = bindAttributeProcessors.stream() .flatMap((Function>) bindAttributeProcessor -> { - return bindAttributeProcessor.getUsedBindAttributes().stream().map(Pair::getSecond); + return bindAttributeProcessor.getBindAttributes().stream().map(Pair::getSecond); }) .collect(Collectors.toList()); diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 0b55c2603..991ff642e 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -689,7 +689,7 @@ private void parseModel(Element e) throws XFormParseException { String name = e.getAttributeName(i); String value = e.getAttributeValue(i); - if (processor.getUsedModelAttributes().contains(new Pair<>(namespace, name))) { + if (processor.getModelAttributes().contains(new Pair<>(namespace, name))) { processor.processModelAttribute(name, value); } } @@ -2426,14 +2426,14 @@ public interface FormDefProcessor extends Processor { public interface BindAttributeProcessor extends Processor { - Set> getUsedBindAttributes(); + Set> getBindAttributes(); void processBindAttribute(String name, String value, DataBinding binding); } public interface ModelAttributeProcessor extends Processor { - Set> getUsedModelAttributes(); + Set> getModelAttributes(); void processModelAttribute(String name, String value) throws XFormParseException; } diff --git a/src/test/java/org/javarosa/entities/EntitiesTest.java b/src/test/java/org/javarosa/entities/EntitiesTest.java index 39c0aa0f8..2caeb2cc7 100644 --- a/src/test/java/org/javarosa/entities/EntitiesTest.java +++ b/src/test/java/org/javarosa/entities/EntitiesTest.java @@ -70,7 +70,7 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); + scenario.getFormEntryController().addPostProcessor(new EntityFormFinalizationProcessor()); scenario.next(); scenario.answer("Tom Wambsgans"); @@ -107,7 +107,7 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); + scenario.getFormEntryController().addPostProcessor(new EntityFormFinalizationProcessor()); scenario.next(); scenario.answer("Tom Wambsgans"); @@ -149,7 +149,7 @@ public void fillingFormWithNonRelevantCreate_doesNotCreateAnyEntities() throws I ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); + scenario.getFormEntryController().addPostProcessor(new EntityFormFinalizationProcessor()); scenario.next(); scenario.answer("Roman Roy"); @@ -187,10 +187,10 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); + scenario.getFormEntryController().addPostProcessor(new EntityFormFinalizationProcessor()); Scenario deserializedScenario = scenario.serializeAndDeserializeForm(); - deserializedScenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); + deserializedScenario.getFormEntryController().addPostProcessor(new EntityFormFinalizationProcessor()); deserializedScenario.next(); deserializedScenario.answer("Shiv Roy"); @@ -229,7 +229,7 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); + scenario.getFormEntryController().addPostProcessor(new EntityFormFinalizationProcessor()); scenario.next(); scenario.answer("Tom Wambsgans"); @@ -267,7 +267,7 @@ public void mustUseCorrectNamespace() throws IOException { ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); + scenario.getFormEntryController().addPostProcessor(new EntityFormFinalizationProcessor()); scenario.next(); scenario.answer("Tom Wambsgans"); @@ -304,7 +304,7 @@ public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEnti ) )); - scenario.getFormEntryController().addPostProcessor(new EntityFinalizationProcessor()); + scenario.getFormEntryController().addPostProcessor(new EntityFormFinalizationProcessor()); scenario.next(); scenario.answer(scenario.choicesOf("/data/team").get(0)); From e978f642d711f375342869333c70821d5d407d22 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 11:37:38 +0100 Subject: [PATCH 30/36] Remove unsupported forEach --- .../core/util/externalizable/ExternalizableExtras.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java b/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java index aa2744019..49c2554a8 100644 --- a/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java +++ b/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java @@ -12,14 +12,14 @@ public class ExternalizableExtras extends Extras implements Exte @Override public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { HashMap extras = (HashMap) ExtUtil.read(in, new ExtWrapMap(String.class, new ExtWrapExternalizable()), pf); - extras.forEach((s, externalizable) -> put(externalizable)); + extras.entrySet().stream().forEach(entry -> put(entry.getValue())); } @Override public void writeExternal(DataOutputStream out) throws IOException { HashMap wrappedParseAttachments = new HashMap<>(); - map.forEach((key, value) -> { - wrappedParseAttachments.put(key, new ExtWrapExternalizable(value)); + map.entrySet().stream().forEach(entry -> { + wrappedParseAttachments.put(entry.getKey(), new ExtWrapExternalizable(entry.getValue())); }); ExtUtil.write(out, new ExtWrapMap(wrappedParseAttachments)); From d4b9100d9d059b5580413c569569643aef25d9cf Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 11:44:12 +0100 Subject: [PATCH 31/36] Simplify ExternalizableExtras --- .../externalizable/ExternalizableExtras.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java b/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java index 49c2554a8..d11377818 100644 --- a/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java +++ b/src/main/java/org/javarosa/core/util/externalizable/ExternalizableExtras.java @@ -5,23 +5,25 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; public class ExternalizableExtras extends Extras implements Externalizable { @Override public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { - HashMap extras = (HashMap) ExtUtil.read(in, new ExtWrapMap(String.class, new ExtWrapExternalizable()), pf); - extras.entrySet().stream().forEach(entry -> put(entry.getValue())); + ArrayList extras = (ArrayList) ExtUtil.read(in, new ExtWrapList(new ExtWrapExternalizable()), pf); + extras.stream().forEach(this::put); } @Override public void writeExternal(DataOutputStream out) throws IOException { - HashMap wrappedParseAttachments = new HashMap<>(); - map.entrySet().stream().forEach(entry -> { - wrappedParseAttachments.put(entry.getKey(), new ExtWrapExternalizable(entry.getValue())); - }); + List wrappedParseAttachments = map.values() + .stream() + .map(ExtWrapExternalizable::new) + .collect(Collectors.toList()); - ExtUtil.write(out, new ExtWrapMap(wrappedParseAttachments)); + ExtUtil.write(out, new ExtWrapList(wrappedParseAttachments)); } } From 35652f6747ee44ed534af1a453a3f7005eddb73a Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 13:57:05 +0100 Subject: [PATCH 32/36] Make sure only bind attributes with correct namespace are removed --- .../StandardBindAttributesProcessor.java | 51 ++++---- .../parse/BindAttributeProcessorTest.java | 117 ++++++++++++++++++ 2 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 src/test/java/org/javarosa/xform/parse/BindAttributeProcessorTest.java diff --git a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java index 5fecc78eb..bce4ef489 100644 --- a/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java +++ b/src/main/java/org/javarosa/xform/parse/StandardBindAttributesProcessor.java @@ -1,17 +1,5 @@ package org.javarosa.xform.parse; -import static org.javarosa.xform.parse.Constants.ID_ATTR; -import static org.javarosa.xform.parse.Constants.NODESET_ATTR; -import static org.javarosa.xform.parse.XFormParser.NAMESPACE_JAVAROSA; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import kotlin.Pair; import org.javarosa.core.model.DataBinding; import org.javarosa.core.model.FormDef; @@ -27,6 +15,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.javarosa.xform.parse.Constants.ID_ATTR; +import static org.javarosa.xform.parse.Constants.NODESET_ATTR; +import static org.javarosa.xform.parse.XFormParser.NAMESPACE_JAVAROSA; + class StandardBindAttributesProcessor { private static final Logger logger = LoggerFactory.getLogger(StandardBindAttributesProcessor.class); @@ -128,17 +127,21 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef } }); - List processorAttributes = bindAttributeProcessors.stream() - .flatMap((Function>) bindAttributeProcessor -> { - return bindAttributeProcessor.getBindAttributes().stream().map(Pair::getSecond); + List> processorAttributes = bindAttributeProcessors.stream() + .flatMap((Function>>) bindAttributeProcessor -> { + return bindAttributeProcessor.getBindAttributes().stream(); }) .collect(Collectors.toList()); - List allUsedAttributes = new ArrayList<>(); - allUsedAttributes.addAll(usedAttributes); - allUsedAttributes.addAll(processorAttributes); + for (int i = 0; i < element.getAttributeCount(); i++) { + String namespace = element.getAttributeNamespace(i); + String name = element.getAttributeName(i); - saveUnusedAttributes(binding, element, allUsedAttributes, passedThroughAttributes); + boolean usedAttribute = usedAttributes.contains(name) || processorAttributes.contains(new Pair<>(namespace, name)); + if (!usedAttribute || passedThroughAttributes.contains(name)) { + binding.setAdditionalAttribute(element.getAttributeNamespace(i), name, element.getAttributeValue(i)); + } + } return binding; } @@ -206,14 +209,4 @@ private int getDataType(String type) { return dataType; } - - private void saveUnusedAttributes(DataBinding binding, Element element, Collection usedAttributes, - Collection passedThroughAttributes) { - for (int i = 0; i < element.getAttributeCount(); i++) { - String name = element.getAttributeName(i); - if (!usedAttributes.contains(name) || passedThroughAttributes.contains(name)) { - binding.setAdditionalAttribute(element.getAttributeNamespace(i), name, element.getAttributeValue(i)); - } - } - } } diff --git a/src/test/java/org/javarosa/xform/parse/BindAttributeProcessorTest.java b/src/test/java/org/javarosa/xform/parse/BindAttributeProcessorTest.java new file mode 100644 index 000000000..2e40f6aa9 --- /dev/null +++ b/src/test/java/org/javarosa/xform/parse/BindAttributeProcessorTest.java @@ -0,0 +1,117 @@ +package org.javarosa.xform.parse; + +import kotlin.Pair; +import org.javarosa.core.model.DataBinding; +import org.javarosa.core.model.FormDef; +import org.javarosa.core.model.instance.TreeElement; +import org.javarosa.core.model.instance.TreeReference; +import org.javarosa.core.util.XFormsElement; +import org.javarosa.model.xform.XPathReference; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.javarosa.core.util.BindBuilderXFormsElement.bind; +import static org.javarosa.core.util.XFormsElement.body; +import static org.javarosa.core.util.XFormsElement.head; +import static org.javarosa.core.util.XFormsElement.input; +import static org.javarosa.core.util.XFormsElement.mainInstance; +import static org.javarosa.core.util.XFormsElement.model; +import static org.javarosa.core.util.XFormsElement.t; +import static org.javarosa.core.util.XFormsElement.title; + +public class BindAttributeProcessorTest { + + @Test + public void doesNotProcessAttributeWithIncorrectNamespace() throws IOException { + XFormsElement form = XFormsElement.html( + asList( + new Pair<>("blah", "blah"), + new Pair<>("notBlah", "notBlah") + ), + head( + title("Form"), + model( + mainInstance( + t("data id=\"form\"", + t("name") + ) + ), + bind("/data/name").type("string").withAttribute("notBlah", "name", "value") + ) + ), + body( + input("/data/name") + ) + ); + + XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); + + RecordingBindAttributeProcessor processor = new RecordingBindAttributeProcessor(new HashSet<>(asList(new Pair<>("blah", "name")))); + parser.addBindAttributeProcessor(processor); + + parser.parse(); + assertThat(processor.processCalled, equalTo(false)); + } + @Test + public void doesNotRemovettributeWithIncorrectNamespace() throws IOException { + XFormsElement form = XFormsElement.html( + asList( + new Pair<>("blah", "blah"), + new Pair<>("notBlah", "notBlah") + ), + head( + title("Form"), + model( + mainInstance( + t("data id=\"form\"", + t("name") + ) + ), + bind("/data/name").type("string").withAttribute("notBlah", "name", "value") + ) + ), + body( + input("/data/name") + ) + ); + + XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); + + RecordingBindAttributeProcessor processor = new RecordingBindAttributeProcessor(new HashSet<>(asList(new Pair<>("blah", "name")))); + parser.addBindAttributeProcessor(processor); + + FormDef formDef = parser.parse(); + + TreeReference questionRef = XPathReference.getPathExpr("/data/name").getReference(); + TreeElement questionElement = formDef.getMainInstance().resolveReference(questionRef); + assertThat(questionElement.getBindAttributes().size(), equalTo(1)); + } + + private static class RecordingBindAttributeProcessor implements XFormParser.BindAttributeProcessor { + + private final Set> attributes; + boolean processCalled; + + public RecordingBindAttributeProcessor(Set> attributes) { + this.attributes = attributes; + } + + @Override + public Set> getBindAttributes() { + return attributes; + } + + @Override + public void processBindAttribute(String name, String value, DataBinding binding) { + processCalled = true; + } + } +} From f69c9b2532b36b0e8f2854f2377f983d99281fcb Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 14:17:00 +0100 Subject: [PATCH 33/36] Add explicit parse exception --- .../internal/EntityFormParseProcessor.java | 2 +- .../xform/parse/XFormParseException.java | 8 +- .../org/javarosa/xform/parse/XFormParser.java | 19 ++-- .../org/javarosa/xform/util/XFormUtils.java | 10 +- .../core/form/api/test/TextFormTests.java | 3 +- .../javarosa/core/model/ChoiceNameTest.java | 13 +-- .../core/model/FormDefSerializationTest.java | 9 +- .../javarosa/core/model/SelectChoiceTest.java | 17 +-- .../model/SelectMultipleChoiceFilterTest.java | 3 +- .../core/model/SelectOneChoiceFilterTest.java | 3 +- .../core/model/TriggerableDagTest.java | 101 +++++++++--------- .../model/actions/InstanceLoadEventsTest.java | 5 +- .../model/actions/MultipleEventsTest.java | 15 +-- .../model/actions/OdkNewRepeatEventTest.java | 17 +-- .../model/actions/RecordAudioActionTest.java | 9 +- .../model/actions/SetGeopointActionTest.java | 7 +- .../model/actions/SetValueActionTest.java | 33 +++--- .../EvaluationContextExpandReferenceTest.java | 3 +- .../condition/ReadOnlyCalculateTest.java | 3 +- .../core/model/condition/RecalculateTest.java | 3 +- .../model/instance/test/TreeElementTests.java | 2 +- .../javarosa/core/model/test/FormDefTest.java | 19 ++-- .../test/FormIndexSerializationTest.java | 3 +- .../core/model/test/QuestionDefTest.java | 3 +- .../org/javarosa/core/test/FormParseInit.java | 7 +- .../java/org/javarosa/core/test/Scenario.java | 8 +- .../org/javarosa/core/util/GeoAreaTest.java | 9 +- .../javarosa/core/util/GeoDistanceTest.java | 9 +- .../org/javarosa/entities/EntitiesTest.java | 17 +-- .../entities/EntityFormParserTest.java | 2 +- .../EntityFormParseProcessorTest.java | 8 +- .../javarosa/form/api/ConstraintTextTest.java | 3 +- .../javarosa/form/api/FormEntryModelTest.java | 3 +- .../form/api/FormEntryPromptTest.java | 7 +- .../form/api/FormNavigationTestCase.java | 5 +- .../xform/CompactSerializingVisitorTest.java | 5 +- .../xform/XFormSerializingVisitorTest.java | 3 +- .../IndexedRepeatRelativeRefsTest.java | 3 +- ...SameRefDifferentInstancesIssue449Test.java | 5 +- .../TriggersForRelativeRefsTest.java | 11 +- .../smoketests/ChildVaccinationTest.java | 3 +- .../org/javarosa/smoketests/WhoVATest.java | 5 +- .../xform/parse/AttributesTestCase.java | 4 +- .../parse/BindAttributeProcessorTest.java | 4 +- .../ExternalSecondaryInstanceParseTest.java | 26 ++--- .../xform/parse/FormParserHelper.java | 4 +- .../javarosa/xform/parse/XFormParserTest.java | 50 ++++----- .../org/javarosa/xpath/expr/DigestTest.java | 3 +- .../expr/XPathFuncExprRandomizeTest.java | 3 +- .../XPathPathExprCurrentFieldRefTest.java | 3 +- ...XPathPathExprCurrentGroupCountRefTest.java | 3 +- .../xpath/expr/XPathPathExprCurrentTest.java | 3 +- 52 files changed, 288 insertions(+), 238 deletions(-) diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 87d2abada..e93ac551f 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -57,7 +57,7 @@ public void processBindAttribute(String name, String value, DataBinding binding) } @Override - public void processFormDef(FormDef formDef) { + public void processFormDef(FormDef formDef) throws XFormParser.ParseException { if (!versionPresent && EntityFormParser.getEntityElement(formDef.getMainInstance()) != null) { throw new XFormParser.MissingModelAttributeException(ENTITIES_NAMESPACE, "entities-version"); } diff --git a/src/main/java/org/javarosa/xform/parse/XFormParseException.java b/src/main/java/org/javarosa/xform/parse/XFormParseException.java index 1dce317a6..d26686679 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParseException.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParseException.java @@ -20,13 +20,15 @@ /** * Exception thrown when an XForms Parsing error occurs. + * Clayton Sims - Aug 18, 2008 : This doesn't actually seem + * to be a RuntimeException to me. Is there justification + * as to why it is? * * @author Drew Roos + * @deprecated Use non {@link RuntimeException} {@link XFormParser.ParseException} instead * */ -// Clayton Sims - Aug 18, 2008 : This doesn't actually seem -// to be a RuntimeException to me. Is there justification -// as to why it is? +@Deprecated public class XFormParseException extends RuntimeException { /** * diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 991ff642e..325b84131 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -370,12 +370,12 @@ public XFormParser(Document form, Document instance) { _instDoc = instance; } - public FormDef parse(String lastSavedSrc) throws IOException { + public FormDef parse(String lastSavedSrc) throws IOException, ParseException { return parse(null, lastSavedSrc); } - public FormDef parse() throws IOException { + public FormDef parse() throws IOException, ParseException { return parse(null, null); } @@ -384,7 +384,7 @@ public FormDef parse() throws IOException { * @param lastSavedSrc The src of the last-saved instance of this form (for auto-filling). If null, * no data will be loaded and the instance will be blank. */ - public FormDef parse(String formXmlSrc, String lastSavedSrc) throws IOException { + public FormDef parse(String formXmlSrc, String lastSavedSrc) throws IOException, ParseException { if (_f == null) { logger.info("Parsing form..."); @@ -402,7 +402,10 @@ public FormDef parse(String formXmlSrc, String lastSavedSrc) throws IOException } } - formDefProcessors.stream().forEach(formDefProcessor -> formDefProcessor.processFormDef(_f)); + for (FormDefProcessor formDefProcessor : formDefProcessors) { + formDefProcessor.processFormDef(_f); + } + return _f; } @@ -2421,7 +2424,7 @@ public interface Processor { } public interface FormDefProcessor extends Processor { - void processFormDef(FormDef formDef); + void processFormDef(FormDef formDef) throws ParseException; } public interface BindAttributeProcessor extends Processor { @@ -2438,7 +2441,11 @@ public interface ModelAttributeProcessor extends Processor { void processModelAttribute(String name, String value) throws XFormParseException; } - public static class MissingModelAttributeException extends XFormParseException { + public static class ParseException extends Exception { + + } + + public static class MissingModelAttributeException extends ParseException { private final String namespace; private final String name; diff --git a/src/main/java/org/javarosa/xform/util/XFormUtils.java b/src/main/java/org/javarosa/xform/util/XFormUtils.java index 55294dd87..a7ec97ff5 100644 --- a/src/main/java/org/javarosa/xform/util/XFormUtils.java +++ b/src/main/java/org/javarosa/xform/util/XFormUtils.java @@ -53,7 +53,7 @@ public static IXFormParserFactory setXFormParserFactory(IXFormParserFactory fact return oldFactory; } - public static FormDef getFormFromResource (String resource) { + public static FormDef getFormFromResource (String resource) throws XFormParser.ParseException { InputStream is = System.class.getResourceAsStream(resource); if (is == null) { logger.error("Can't find form resource {}. Is it in the JAR?", resource); @@ -64,7 +64,7 @@ public static FormDef getFormFromResource (String resource) { } - public static FormDef getFormRaw(InputStreamReader isr) throws XFormParseException, IOException{ + public static FormDef getFormRaw(InputStreamReader isr) throws XFormParseException, IOException, XFormParser.ParseException { return _factory.getXFormParser(isr).parse(); } @@ -75,7 +75,7 @@ public static FormDef getFormRaw(InputStreamReader isr) throws XFormParseExcepti * @return a FormDef for the parsed form * @throws XFormParseException if the form can’t be parsed */ - public static FormDef getFormFromInputStream(InputStream is) throws XFormParseException { + public static FormDef getFormFromInputStream(InputStream is) throws XFormParseException, XFormParser.ParseException { return getFormFromInputStream(is, null); } @@ -85,7 +85,7 @@ public static FormDef getFormFromInputStream(InputStream is) throws XFormParseEx * @param lastSavedSrc The src of the last-saved instance of this form (for auto-filling). If null, * no data will be loaded and the instance will be blank. */ - public static FormDef getFormFromInputStream(InputStream is, String lastSavedSrc) throws XFormParseException { + public static FormDef getFormFromInputStream(InputStream is, String lastSavedSrc) throws XFormParseException, XFormParser.ParseException { InputStreamReader isr = null; try { try { @@ -115,7 +115,7 @@ public static FormDef getFormFromInputStream(InputStream is, String lastSavedSrc * @param lastSavedSrc The src of the last-saved instance of this form (for auto-filling). If null, * no data will be loaded and the instance will be blank. */ - public static FormDef getFormFromFormXml(String formXmlSrc, String lastSavedSrc) throws XFormParseException { + public static FormDef getFormFromFormXml(String formXmlSrc, String lastSavedSrc) throws XFormParseException, XFormParser.ParseException { InputStreamReader isr = null; try { isr = new FileReader(formXmlSrc); diff --git a/src/test/java/org/javarosa/core/form/api/test/TextFormTests.java b/src/test/java/org/javarosa/core/form/api/test/TextFormTests.java index 2d3d2d9ce..8a75dd15b 100644 --- a/src/test/java/org/javarosa/core/form/api/test/TextFormTests.java +++ b/src/test/java/org/javarosa/core/form/api/test/TextFormTests.java @@ -19,6 +19,7 @@ import org.javarosa.form.api.FormEntryCaption; import org.javarosa.form.api.FormEntryController; import org.javarosa.form.api.FormEntryPrompt; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -39,7 +40,7 @@ public static void classSetup() { @Before - public void setUp(){ + public void setUp() throws XFormParser.ParseException { fpi = new FormParseInit(); q = fpi.getFirstQuestionDef(); fep = new FormEntryPrompt(fpi.getFormDef(), fpi.getFormEntryModel().getFormIndex()); diff --git a/src/test/java/org/javarosa/core/model/ChoiceNameTest.java b/src/test/java/org/javarosa/core/model/ChoiceNameTest.java index 8169d4a5f..4984c293f 100644 --- a/src/test/java/org/javarosa/core/model/ChoiceNameTest.java +++ b/src/test/java/org/javarosa/core/model/ChoiceNameTest.java @@ -17,21 +17,22 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class ChoiceNameTest { - @Test public void choiceNameCallOnLiteralChoiceValue_getsChoiceName() { + @Test public void choiceNameCallOnLiteralChoiceValue_getsChoiceName() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("jr-choice-name.xml")); assertThat(scenario.answerOf("/jr-choice-name/literal_choice_name"), is(stringAnswer("Choice 2"))); } - @Test public void choiceNameCallOutsideOfRepeatWithStaticChoices_getsChoiceName() { + @Test public void choiceNameCallOutsideOfRepeatWithStaticChoices_getsChoiceName() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("jr-choice-name.xml")); scenario.answer("/jr-choice-name/select_one_outside", "choice3"); assertThat(scenario.answerOf("/jr-choice-name/select_one_name_outside"), is(stringAnswer("Choice 3"))); } - @Test public void choiceNameCallInRepeatWithStaticChoices_getsChoiceName() { + @Test public void choiceNameCallInRepeatWithStaticChoices_getsChoiceName() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("jr-choice-name.xml")); scenario.answer("/jr-choice-name/my-repeat[1]/select_one", "choice4"); scenario.answer("/jr-choice-name/my-repeat[2]/select_one", "choice1"); @@ -42,7 +43,7 @@ public class ChoiceNameTest { assertThat(scenario.answerOf("/jr-choice-name/my-repeat[3]/select_one_name"), is(stringAnswer("Choice 5"))); } - @Test public void choiceNameCall_respectsLanguage() { + @Test public void choiceNameCall_respectsLanguage() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("jr-choice-name.xml")); scenario.setLanguage("French (fr)"); scenario.answer("/jr-choice-name/select_one_outside", "choice3"); @@ -62,7 +63,7 @@ public class ChoiceNameTest { // The choice list for question cocotero with dynamic itemset is populated on DAG initialization time triggered by the jr:choice-name // expression in the calculate. - @Test public void choiceNameCallWithDynamicChoicesAndNoPredicate_selectsName() { + @Test public void choiceNameCallWithDynamicChoicesAndNoPredicate_selectsName() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("jr-choice-name.xml")); scenario.answer("/jr-choice-name/cocotero_a", "a"); scenario.answer("/jr-choice-name/cocotero_b", "b"); @@ -72,7 +73,7 @@ public class ChoiceNameTest { // The choice list for question city with dynamic itemset is populated at DAG initialization time. Since country hasn't been // set yet, the choice list is empty. Setting the country does not automatically trigger re-computation of the choice list for the // city question. Instead, clients trigger a recomputation of the list when the list is displayed. - @Test public void choiceNameCallWithDynamicChoicesAndPredicate_requiresExplicitDynamicChoicesRecomputation() throws IOException { + @Test public void choiceNameCallWithDynamicChoicesAndPredicate_requiresExplicitDynamicChoicesRecomputation() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Dynamic Choices and Predicates", html( head( title("Dynamic Choices and Predicates"), diff --git a/src/test/java/org/javarosa/core/model/FormDefSerializationTest.java b/src/test/java/org/javarosa/core/model/FormDefSerializationTest.java index e47860656..ace7832ff 100644 --- a/src/test/java/org/javarosa/core/model/FormDefSerializationTest.java +++ b/src/test/java/org/javarosa/core/model/FormDefSerializationTest.java @@ -32,10 +32,11 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class FormDefSerializationTest { - @Test public void instanceName_forReferenceInMainInstance_isAlwaysNull() throws IOException, DeserializationException { + @Test public void instanceName_forReferenceInMainInstance_isAlwaysNull() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = getSimplestFormScenario(); scenario.next(); @@ -48,7 +49,7 @@ public class FormDefSerializationTest { } // During form evaluation, most relative references are contextualized directly or indirectly using the FormDef evaluation context. - @Test public void instanceName_forFormDefEvaluationContext_isAlwaysNull() throws IOException, DeserializationException { + @Test public void instanceName_forFormDefEvaluationContext_isAlwaysNull() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = getSimplestFormScenario(); scenario.next(); @@ -64,7 +65,7 @@ public class FormDefSerializationTest { // TreeReference. Then XPathPathExpr.getRefValue sees whether that reference is the same as the latest modified // question by using TreeRefence.equals. In the original JavaRosa implementation, the main instance name was null prior // to serialization and set after deserialization. - @Test public void instanceName_forFormDefMainInstance_isAlwaysNull() throws IOException, DeserializationException { + @Test public void instanceName_forFormDefMainInstance_isAlwaysNull() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = getSimplestFormScenario(); scenario.next(); @@ -76,7 +77,7 @@ public class FormDefSerializationTest { assertThat(deserialized.getFormDef().getMainInstance().getBase().getInstanceName(), is(nullValue())); } - private static Scenario getSimplestFormScenario() throws IOException { + private static Scenario getSimplestFormScenario() throws IOException, XFormParser.ParseException { return Scenario.init("Simplest", html( head( title("Simplest"), diff --git a/src/test/java/org/javarosa/core/model/SelectChoiceTest.java b/src/test/java/org/javarosa/core/model/SelectChoiceTest.java index 5dc982fa3..ce748349f 100644 --- a/src/test/java/org/javarosa/core/model/SelectChoiceTest.java +++ b/src/test/java/org/javarosa/core/model/SelectChoiceTest.java @@ -45,11 +45,12 @@ import org.hamcrest.CoreMatchers; import org.javarosa.core.test.Scenario; import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class SelectChoiceTest { @Test - public void value_should_continue_being_an_empty_string_after_deserialization() throws IOException, DeserializationException { + public void value_should_continue_being_an_empty_string_after_deserialization() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("SelectChoice.getValue() regression test form", html( head( title("SelectChoice.getValue() regression test form"), @@ -76,7 +77,7 @@ public void value_should_continue_being_an_empty_string_after_deserialization() } @Test - public void getChild_returnsNamedChild_whenChoicesAreFromSecondaryInstance() { + public void getChild_returnsNamedChild_whenChoicesAreFromSecondaryInstance() throws XFormParser.ParseException { setUpSimpleReferenceManager(r("external-select-geojson.xml").getParent(), "file"); Scenario scenario = Scenario.init("external-select-geojson.xml"); @@ -85,7 +86,7 @@ public void getChild_returnsNamedChild_whenChoicesAreFromSecondaryInstance() { } @Test - public void getChild_returnsNull_whenChoicesAreFromSecondaryInstance_andRequestedChildDoesNotExist() { + public void getChild_returnsNull_whenChoicesAreFromSecondaryInstance_andRequestedChildDoesNotExist() throws XFormParser.ParseException { setUpSimpleReferenceManager(r("external-select-geojson.xml").getParent(), "file"); Scenario scenario = Scenario.init("external-select-geojson.xml"); @@ -93,7 +94,7 @@ public void getChild_returnsNull_whenChoicesAreFromSecondaryInstance_andRequeste } @Test - public void getChild_updates_whenChoicesAreFromRepeat() throws IOException { + public void getChild_updates_whenChoicesAreFromRepeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Select from repeat", html( head( title("Select from repeat"), @@ -126,7 +127,7 @@ public void getChild_updates_whenChoicesAreFromRepeat() throws IOException { } @Test - public void getChild_returnsNull_whenCalledOnAChoiceFromInlineSelect() throws IOException { + public void getChild_returnsNull_whenCalledOnAChoiceFromInlineSelect() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Static select", html( head( title("Static select"), @@ -142,7 +143,7 @@ public void getChild_returnsNull_whenCalledOnAChoiceFromInlineSelect() throws IO } @Test - public void getAdditionalChildren_returnsChildrenInOrder_whenChoicesAreFromSecondaryInstance() { + public void getAdditionalChildren_returnsChildrenInOrder_whenChoicesAreFromSecondaryInstance() throws XFormParser.ParseException { setUpSimpleReferenceManager(r("external-select-geojson.xml").getParent(), "file"); Scenario scenario = Scenario.init("external-select-geojson.xml"); @@ -162,7 +163,7 @@ public void getAdditionalChildren_returnsChildrenInOrder_whenChoicesAreFromSecon } @Test - public void getChildren_updates_whenChoicesAreFromRepeat() throws IOException { + public void getChildren_updates_whenChoicesAreFromRepeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Select from repeat", html( head( title("Select from repeat"), @@ -199,7 +200,7 @@ public void getChildren_updates_whenChoicesAreFromRepeat() throws IOException { } @Test - public void getAdditionalChildren_returnsEmpty_whenCalledOnAChoiceFromInlineSelect() throws IOException { + public void getAdditionalChildren_returnsEmpty_whenCalledOnAChoiceFromInlineSelect() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Static select", html( head( title("Static select"), diff --git a/src/test/java/org/javarosa/core/model/SelectMultipleChoiceFilterTest.java b/src/test/java/org/javarosa/core/model/SelectMultipleChoiceFilterTest.java index 10e596070..62f686c87 100644 --- a/src/test/java/org/javarosa/core/model/SelectMultipleChoiceFilterTest.java +++ b/src/test/java/org/javarosa/core/model/SelectMultipleChoiceFilterTest.java @@ -8,6 +8,7 @@ import static org.javarosa.core.test.SelectChoiceMatchers.choice; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.Test; @@ -24,7 +25,7 @@ public class SelectMultipleChoiceFilterTest { private Scenario scenario; @Before - public void setUp() { + public void setUp() throws XFormParser.ParseException { scenario = Scenario.init("three-level-cascading-multi-select.xml"); } diff --git a/src/test/java/org/javarosa/core/model/SelectOneChoiceFilterTest.java b/src/test/java/org/javarosa/core/model/SelectOneChoiceFilterTest.java index f6556d72c..db5a7572e 100644 --- a/src/test/java/org/javarosa/core/model/SelectOneChoiceFilterTest.java +++ b/src/test/java/org/javarosa/core/model/SelectOneChoiceFilterTest.java @@ -25,6 +25,7 @@ import static org.javarosa.form.api.FormEntryController.ANSWER_REQUIRED_BUT_EMPTY; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -40,7 +41,7 @@ public class SelectOneChoiceFilterTest { private Scenario scenario; @Before - public void setUp() { + public void setUp() throws XFormParser.ParseException { scenario = Scenario.init("three-level-cascading-select.xml"); } diff --git a/src/test/java/org/javarosa/core/model/TriggerableDagTest.java b/src/test/java/org/javarosa/core/model/TriggerableDagTest.java index 46aa502c8..2d1361148 100644 --- a/src/test/java/org/javarosa/core/model/TriggerableDagTest.java +++ b/src/test/java/org/javarosa/core/model/TriggerableDagTest.java @@ -47,6 +47,7 @@ import org.javarosa.debug.Event; import org.javarosa.form.api.FormEntryController; import org.javarosa.xform.parse.XFormParseException; +import org.javarosa.xform.parse.XFormParser; import org.javarosa.xpath.expr.XPathPathExpr; import org.javarosa.xpath.expr.XPathPathExprEval; import org.junit.Before; @@ -71,7 +72,7 @@ public void setUp() { public ExpectedException exceptionRule = ExpectedException.none(); @Test - public void order_of_the_DAG_is_ensured() throws IOException { + public void order_of_the_DAG_is_ensured() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -103,7 +104,7 @@ public void order_of_the_DAG_is_ensured() throws IOException { //region Cycles @Test - public void parsing_forms_with_cycles_by_self_reference_in_calculate_should_fail() throws IOException { + public void parsing_forms_with_cycles_by_self_reference_in_calculate_should_fail() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Cycle detected in form's relevant and calculation logic!"); @@ -113,7 +114,7 @@ public void parsing_forms_with_cycles_by_self_reference_in_calculate_should_fail } @Test - public void parsing_forms_with_cycles_in_calculate_should_fail() throws IOException { + public void parsing_forms_with_cycles_in_calculate_should_fail() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Cycle detected in form's relevant and calculation logic!"); @@ -125,7 +126,7 @@ public void parsing_forms_with_cycles_in_calculate_should_fail() throws IOExcept } @Test - public void parsing_forms_with_cycles_by_self_reference_in_relevance_should_fail() throws IOException { + public void parsing_forms_with_cycles_by_self_reference_in_relevance_should_fail() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Cycle detected in form's relevant and calculation logic!"); @@ -135,7 +136,7 @@ public void parsing_forms_with_cycles_by_self_reference_in_relevance_should_fail } @Test - public void parsing_forms_with_cycles_by_self_reference_in_read_only_condition_should_fail() throws IOException { + public void parsing_forms_with_cycles_by_self_reference_in_read_only_condition_should_fail() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Cycle detected in form's relevant and calculation logic!"); @@ -145,7 +146,7 @@ public void parsing_forms_with_cycles_by_self_reference_in_read_only_condition_s } @Test - public void parsing_forms_with_cycles_by_self_reference_in_required_condition_should_fail() throws IOException { + public void parsing_forms_with_cycles_by_self_reference_in_required_condition_should_fail() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Cycle detected in form's relevant and calculation logic!"); @@ -155,7 +156,7 @@ public void parsing_forms_with_cycles_by_self_reference_in_required_condition_sh } @Test - public void supports_self_references_in_constraints() throws IOException { + public void supports_self_references_in_constraints() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", buildFormForDagCyclesCheck( bind("/data/count").type("int").constraint(". > 10") )); @@ -192,7 +193,7 @@ public void supports_self_references_in_constraints() throws IOException { */ @Test @Ignore - public void supports_codependant_relevant_expressions() throws IOException { + public void supports_codependant_relevant_expressions() throws IOException, XFormParser.ParseException { Scenario.init("Some form", buildFormForDagCyclesCheck( bind("/data/a").type("int").relevant("/data/b > 0"), bind("/data/b").type("int").relevant("/data/a > 0"))); @@ -223,7 +224,7 @@ public void supports_codependant_relevant_expressions() throws IOException { */ @Test @Ignore - public void supports_codependant_required_conditions() throws IOException { + public void supports_codependant_required_conditions() throws IOException, XFormParser.ParseException { Scenario.init("Some form", buildFormForDagCyclesCheck( bind("/data/a").type("int").required("/data/b > 0"), bind("/data/b").type("int").required("/data/a > 0"))); @@ -254,7 +255,7 @@ public void supports_codependant_required_conditions() throws IOException { */ @Test @Ignore - public void supports_codependant_readonly_conditions() throws IOException { + public void supports_codependant_readonly_conditions() throws IOException, XFormParser.ParseException { Scenario.init("Some form", buildFormForDagCyclesCheck( bind("/data/a").type("int").readonly("/data/b > 0"), bind("/data/b").type("int").readonly("/data/a > 0"))); @@ -262,7 +263,7 @@ public void supports_codependant_readonly_conditions() throws IOException { } @Test - public void parsing_forms_with_cycles_involving_fields_inside_and_outside_of_repeat_groups_should_fail() throws IOException { + public void parsing_forms_with_cycles_involving_fields_inside_and_outside_of_repeat_groups_should_fail() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Cycle detected in form's relevant and calculation logic!"); @@ -290,7 +291,7 @@ public void parsing_forms_with_cycles_involving_fields_inside_and_outside_of_rep } @Test - public void parsing_forms_with_self_reference_cycles_in_fields_of_repeat_groups_should_fail() throws IOException { + public void parsing_forms_with_self_reference_cycles_in_fields_of_repeat_groups_should_fail() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Cycle detected in form's relevant and calculation logic!"); @@ -322,7 +323,7 @@ public void parsing_forms_with_self_reference_cycles_in_fields_of_repeat_groups_ */ @Test @Ignore - public void supports_self_reference_dependency_when_targeting_different_repeat_instance_siblings() throws IOException { + public void supports_self_reference_dependency_when_targeting_different_repeat_instance_siblings() throws IOException, XFormParser.ParseException { Scenario.init("Some form", html( head( title("Some form"), @@ -343,7 +344,7 @@ public void supports_self_reference_dependency_when_targeting_different_repeat_i @Test - public void parsing_forms_with_cycles_between_fields_of_the_same_repeat_instance_should_fail() throws IOException { + public void parsing_forms_with_cycles_between_fields_of_the_same_repeat_instance_should_fail() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Cycle detected in form's relevant and calculation logic!"); @@ -376,7 +377,7 @@ public void parsing_forms_with_cycles_between_fields_of_the_same_repeat_instance * - https://www.w3.org/community/xformsusers/wiki/XForms_2.0#The_relevant_Property */ @Test - public void non_relevance_is_inherited_from_ancestors() throws IOException { + public void non_relevance_is_inherited_from_ancestors() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -422,7 +423,7 @@ public void non_relevance_is_inherited_from_ancestors() throws IOException { * to determine relevance inheritance. */ @Test - public void relevanceIsDeterminedByModelNesting() throws IOException { + public void relevanceIsDeterminedByModelNesting() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -447,7 +448,7 @@ public void relevanceIsDeterminedByModelNesting() throws IOException { } @Test - public void non_relevant_nodes_are_excluded_from_nodeset_evaluation() throws IOException { + public void non_relevant_nodes_are_excluded_from_nodeset_evaluation() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -496,7 +497,7 @@ public void non_relevant_nodes_are_excluded_from_nodeset_evaluation() throws IOE } @Test - public void non_relevant_node_values_are_always_null_regardless_of_their_actual_value() throws IOException { + public void non_relevant_node_values_are_always_null_regardless_of_their_actual_value() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -537,7 +538,7 @@ public void non_relevant_node_values_are_always_null_regardless_of_their_actual_ * case of expression evaluation in our DAG. */ @Test - public void verify_relation_between_calculate_expressions_and_relevancy_conditions() throws IOException { + public void verify_relation_between_calculate_expressions_and_relevancy_conditions() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -624,7 +625,7 @@ public void whenRepeatAndTopLevelNodeHaveSameRelevanceExpression_andExpressionEv * - https://www.w3.org/TR/xforms11/#model-prop-relevant */ @Test - public void readonly_is_inherited_from_ancestors() throws IOException { + public void readonly_is_inherited_from_ancestors() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -681,7 +682,7 @@ public void readonly_is_inherited_from_ancestors() throws IOException { //region Required and constraint @Test - public void constraints_of_fields_that_are_empty_are_always_satisfied() throws IOException { + public void constraints_of_fields_that_are_empty_are_always_satisfied() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -708,7 +709,7 @@ public void constraints_of_fields_that_are_empty_are_always_satisfied() throws I } @Test - public void empty_required_fields_make_form_validation_fail() throws IOException { + public void empty_required_fields_make_form_validation_fail() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -733,7 +734,7 @@ public void empty_required_fields_make_form_validation_fail() throws IOException @Test - public void constraint_violations_and_form_finalization() throws IOException { + public void constraint_violations_and_form_finalization() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -782,7 +783,7 @@ public void constraint_violations_and_form_finalization() throws IOException { //region Adding or deleting repeats @Test - public void addingRepeatInstance_updatesCalculationCascade() throws IOException { + public void addingRepeatInstance_updatesCalculationCascade() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Add repeat instance", html( head( title("Add repeat instance"), @@ -819,7 +820,7 @@ public void addingRepeatInstance_updatesCalculationCascade() throws IOException } @Test - public void addingRepeat_updatesInnerCalculations_withMultipleDependencies() throws IOException { + public void addingRepeat_updatesInnerCalculations_withMultipleDependencies() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Repeat cascading calc", html( head( title("Repeat cascading calc"), @@ -855,7 +856,7 @@ public void addingRepeat_updatesInnerCalculations_withMultipleDependencies() thr // Illustrates the second case in TriggerableDAG.getTriggerablesAffectingAllInstances @Test - public void addingOrRemovingRepeatInstance_withCalculatedCountOutsideRepeat_updatesReferenceToCountInside() throws IOException { + public void addingOrRemovingRepeatInstance_withCalculatedCountOutsideRepeat_updatesReferenceToCountInside() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Count outside repeat used inside", html( head( title("Count outside repeat used inside"), @@ -896,7 +897,7 @@ public void addingOrRemovingRepeatInstance_withCalculatedCountOutsideRepeat_upda // evaluated once and it's the expandReference call in Triggerable.apply which ensures the result is updated for // every repeat instance. @Test - public void addingOrRemovingRepeatInstance_updatesRepeatCount_insideAndOutsideRepeat() throws IOException { + public void addingOrRemovingRepeatInstance_updatesRepeatCount_insideAndOutsideRepeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Count outside repeat used inside", html( head( title("Count outside repeat used inside"), @@ -935,7 +936,7 @@ public void addingOrRemovingRepeatInstance_updatesRepeatCount_insideAndOutsideRe // count always evaluates to 1. See contrast with addingOrRemovingRepeatInstance_updatesRepeatCount_insideAndOutsideRepeat. @Ignore("Highlights issue with de-duplicating refs and different contexts") @Test - public void addingOrRemovingRepeatInstance_updatesRepeatCount_insideRepeat() throws IOException { + public void addingOrRemovingRepeatInstance_updatesRepeatCount_insideRepeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Count outside repeat used inside", html( head( title("Count outside repeat used inside"), @@ -968,7 +969,7 @@ public void addingOrRemovingRepeatInstance_updatesRepeatCount_insideRepeat() thr } @Test - public void addingOrRemovingRepeatInstance_updatesRelativeRepeatCount_insideRepeat() throws IOException { + public void addingOrRemovingRepeatInstance_updatesRelativeRepeatCount_insideRepeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Count outside repeat used inside", html( head( title("Count outside repeat used inside"), @@ -1001,7 +1002,7 @@ public void addingOrRemovingRepeatInstance_updatesRelativeRepeatCount_insideRepe } @Test - public void addingOrRemovingRepeatInstance_withReferenceToRepeatInRepeat_andOuterSum_updates() throws IOException { + public void addingOrRemovingRepeatInstance_withReferenceToRepeatInRepeat_andOuterSum_updates() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Count outside repeat used inside", html( head( title("Count outside repeat used inside"), @@ -1040,7 +1041,7 @@ public void addingOrRemovingRepeatInstance_withReferenceToRepeatInRepeat_andOute @Test - public void addingOrRemovingRepeatInstance_withReferenceToPreviousInstance_updatesThatReference() throws IOException { + public void addingOrRemovingRepeatInstance_withReferenceToPreviousInstance_updatesThatReference() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1094,7 +1095,7 @@ public void addingOrRemovingRepeatInstance_withReferenceToPreviousInstance_updat } @Test - public void addingOrDeletingRepeatInstance_withRelevanceInsideRepeatDependingOnCount_updatesRelevanceForAllInstances() throws IOException { + public void addingOrDeletingRepeatInstance_withRelevanceInsideRepeatDependingOnCount_updatesRelevanceForAllInstances() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1138,7 +1139,7 @@ public void addingOrDeletingRepeatInstance_withRelevanceInsideRepeatDependingOnC //region Deleting repeats @Test - public void deleteSecondRepeatGroup_evaluatesTriggerables_dependentOnPrecedingRepeatGroupSiblings() throws IOException { + public void deleteSecondRepeatGroup_evaluatesTriggerables_dependentOnPrecedingRepeatGroupSiblings() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1179,7 +1180,7 @@ public void deleteSecondRepeatGroup_evaluatesTriggerables_dependentOnPrecedingRe } @Test - public void deleteSecondRepeatGroup_evaluatesTriggerables_dependentOnTheParentPosition() throws IOException { + public void deleteSecondRepeatGroup_evaluatesTriggerables_dependentOnTheParentPosition() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1230,7 +1231,7 @@ public void deleteSecondRepeatGroup_evaluatesTriggerables_dependentOnTheParentPo } @Test - public void deleteSecondRepeatGroup_doesNotEvaluateTriggerables_notDependentOnTheParentPosition() throws IOException { + public void deleteSecondRepeatGroup_doesNotEvaluateTriggerables_notDependentOnTheParentPosition() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1281,7 +1282,7 @@ public void deleteSecondRepeatGroup_doesNotEvaluateTriggerables_notDependentOnTh } @Test - public void deleteThirdRepeatGroup_evaluatesTriggerables_dependentOnTheRepeatGroupsNumber() throws IOException { + public void deleteThirdRepeatGroup_evaluatesTriggerables_dependentOnTheRepeatGroupsNumber() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1311,7 +1312,7 @@ public void deleteThirdRepeatGroup_evaluatesTriggerables_dependentOnTheRepeatGro // Verifies that the list of recalculations triggered by the repeat instance deletion is minimal. In particular, // calculations outside the repeat should only be re-computed once. @Test - public void repeatInstanceDeletion_triggersCalculationsOutsideTheRepeat_exactlyOnce() throws IOException { + public void repeatInstanceDeletion_triggersCalculationsOutsideTheRepeat_exactlyOnce() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1346,7 +1347,7 @@ public void repeatInstanceDeletion_triggersCalculationsOutsideTheRepeat_exactlyO } @Test - public void repeatInstanceDeletion_withoutReferencesToRepeat_evaluatesNoTriggersInInstances() throws IOException { + public void repeatInstanceDeletion_withoutReferencesToRepeat_evaluatesNoTriggersInInstances() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1394,7 +1395,7 @@ public void repeatInstanceDeletion_withoutReferencesToRepeat_evaluatesNoTriggers * has been deleted along with its parent (the repeat group instance). */ @Test - public void deleteThirdRepeatGroup_evaluatesTriggerables_indirectlyDependentOnTheRepeatGroupsNumber() throws IOException { + public void deleteThirdRepeatGroup_evaluatesTriggerables_indirectlyDependentOnTheRepeatGroupsNumber() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1430,7 +1431,7 @@ public void deleteThirdRepeatGroup_evaluatesTriggerables_indirectlyDependentOnTh } @Test - public void deleteLastRepeat_evaluatesTriggerables() throws IOException { + public void deleteLastRepeat_evaluatesTriggerables() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Delete last repeat instance", html( head( title("Delete last repeat instance"), @@ -1459,7 +1460,7 @@ public void deleteLastRepeat_evaluatesTriggerables() throws IOException { } @Test - public void deleteLastRepeat_evaluatesTriggerables_indirectlyDependentOnTheDeletedRepeat() throws IOException { + public void deleteLastRepeat_evaluatesTriggerables_indirectlyDependentOnTheDeletedRepeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Delete last repeat instance", html( head( title("Delete last repeat instance"), @@ -1493,7 +1494,7 @@ public void deleteLastRepeat_evaluatesTriggerables_indirectlyDependentOnTheDelet * Excercises the triggerTriggerables call in createRepeatInstance. */ @Test - public void adding_repeat_instance_triggers_triggerables_outside_repeat_that_reference_repeat_nodeset() throws IOException { + public void adding_repeat_instance_triggers_triggerables_outside_repeat_that_reference_repeat_nodeset() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Form", html( head( title("Form"), @@ -1525,7 +1526,7 @@ public void adding_repeat_instance_triggers_triggerables_outside_repeat_that_ref * Excercises the initializeTriggerables call in createRepeatInstance. */ @Test - public void adding_repeat_instance_triggers_descendant_node_triggerables() throws IOException { + public void adding_repeat_instance_triggers_descendant_node_triggerables() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Form", html( head( title("Form"), @@ -1574,7 +1575,7 @@ public void adding_repeat_instance_triggers_descendant_node_triggerables() throw // This should cause every repeat instance to be updated. We could handle this by using a strategy similar to // getTriggerablesAffectingAllInstances but for initializeTriggerables. @Test - public void addingRepeatInstance_withInnerSumOfQuestionInRepeat_updatesInnerSumForAllInstances() throws IOException { + public void addingRepeatInstance_withInnerSumOfQuestionInRepeat_updatesInnerSumForAllInstances() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Count outside repeat used inside", html( head( title("Count outside repeat used inside"), @@ -1613,7 +1614,7 @@ public void addingRepeatInstance_withInnerSumOfQuestionInRepeat_updatesInnerSumF // every repeat instance should be updated. We could handle this by using a strategy similar to // getTriggerablesAffectingAllInstances but for initializeTriggerables. @Test - public void addingRepeatInstance_withInnerCalculateDependentOnOuterSum_updatesInnerSumForAllInstances() throws IOException { + public void addingRepeatInstance_withInnerCalculateDependentOnOuterSum_updatesInnerSumForAllInstances() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Count outside repeat used inside", html( head( title("Count outside repeat used inside"), @@ -1653,7 +1654,7 @@ public void addingRepeatInstance_withInnerCalculateDependentOnOuterSum_updatesIn // the setting of the number value in a specific instance. There's currently no mechanism to do that. When a repeat // is added, it will trigger recomputation for previous instances. @Test - public void changingValueInRepeat_withReferenceToNextInstance_updatesPreviousInstance() throws IOException { + public void changingValueInRepeat_withReferenceToNextInstance_updatesPreviousInstance() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1705,7 +1706,7 @@ public void changingValueInRepeat_withReferenceToNextInstance_updatesPreviousIns @Ignore("Fails on v2.17.0 (before DAG simplification)") @Test - public void issue_119_target_question_should_be_relevant() throws IOException { + public void issue_119_target_question_should_be_relevant() throws IOException, XFormParser.ParseException { // This is a translation of the XML form in the issue to our DSL with some adaptations: // - Explicit binds for all fields // - Migrated the condition field to boolean, which should be easier to understand @@ -1766,7 +1767,7 @@ public void issue_119_target_question_should_be_relevant() throws IOException { //region Repeat misc @Test - public void issue_135_verify_that_counts_in_inner_repeats_work_as_expected() throws IOException { + public void issue_135_verify_that_counts_in_inner_repeats_work_as_expected() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1827,7 +1828,7 @@ public void issue_135_verify_that_counts_in_inner_repeats_work_as_expected() thr } @Test - public void addingNestedRepeatInstance_updatesExpressionTriggeredByGenericRef_forAllRepeatInstances() throws IOException { + public void addingNestedRepeatInstance_updatesExpressionTriggeredByGenericRef_forAllRepeatInstances() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), @@ -1868,7 +1869,7 @@ public void addingNestedRepeatInstance_updatesExpressionTriggeredByGenericRef_fo } @Test - public void addingRepeatInstance_updatesReferenceToLastInstance_usingPositionPredicate() throws IOException { + public void addingRepeatInstance_updatesReferenceToLastInstance_usingPositionPredicate() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), diff --git a/src/test/java/org/javarosa/core/model/actions/InstanceLoadEventsTest.java b/src/test/java/org/javarosa/core/model/actions/InstanceLoadEventsTest.java index 7200b2396..84597b04e 100644 --- a/src/test/java/org/javarosa/core/model/actions/InstanceLoadEventsTest.java +++ b/src/test/java/org/javarosa/core/model/actions/InstanceLoadEventsTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import org.hamcrest.CoreMatchers; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class InstanceLoadEventsTest { @@ -92,7 +93,7 @@ public void instanceFirstLoadEvent_doesNotfireOnSecondLoad() throws Exception { } @Test - public void instanceLoadEvent_triggersNestedActions() throws IOException { + public void instanceLoadEvent_triggersNestedActions() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested instance load", html( head( title("Nested instance load"), @@ -117,7 +118,7 @@ public void instanceLoadEvent_triggersNestedActions() throws IOException { } @Test - public void instanceLoadEvent_triggeredForAllPreExistingRepeatInstances() throws IOException { + public void instanceLoadEvent_triggeredForAllPreExistingRepeatInstances() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested instance load", html( head( title("Nested instance load"), diff --git a/src/test/java/org/javarosa/core/model/actions/MultipleEventsTest.java b/src/test/java/org/javarosa/core/model/actions/MultipleEventsTest.java index b4b38445c..92895f4be 100644 --- a/src/test/java/org/javarosa/core/model/actions/MultipleEventsTest.java +++ b/src/test/java/org/javarosa/core/model/actions/MultipleEventsTest.java @@ -3,6 +3,7 @@ import org.javarosa.core.test.Scenario; import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.xform.parse.XFormParseException; +import org.javarosa.xform.parse.XFormParser; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -17,21 +18,21 @@ public class MultipleEventsTest { public ExpectedException expectedException = ExpectedException.none(); @Test - public void nestedFirstLoadEvent_setsValue() { + public void nestedFirstLoadEvent_setsValue() throws XFormParser.ParseException { Scenario scenario = Scenario.init("multiple-events.xml"); assertThat(scenario.answerOf("/data/nested-first-load").getDisplayText(), is("cheese")); } @Test - public void nestedFirstLoadEventInGroup_setsValue() { + public void nestedFirstLoadEventInGroup_setsValue() throws XFormParser.ParseException { Scenario scenario = Scenario.init("multiple-events.xml"); assertThat(scenario.answerOf("/data/my-group/nested-first-load-in-group").getDisplayText(), is("more cheese")); } @Test - public void serializedAndDeserializedNestedFirstLoadEvent_setsValue() throws IOException, DeserializationException { + public void serializedAndDeserializedNestedFirstLoadEvent_setsValue() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("multiple-events.xml"); Scenario deserializedScenario = scenario.serializeAndDeserializeForm(); @@ -40,7 +41,7 @@ public void serializedAndDeserializedNestedFirstLoadEvent_setsValue() throws IOE } @Test - public void serializedAndDeserializedNestedFirstLoadEventInGroup_setsValue() throws IOException, DeserializationException { + public void serializedAndDeserializedNestedFirstLoadEventInGroup_setsValue() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("multiple-events.xml"); Scenario deserializedScenario = scenario.serializeAndDeserializeForm(); @@ -49,7 +50,7 @@ public void serializedAndDeserializedNestedFirstLoadEventInGroup_setsValue() thr } @Test - public void nestedFirstLoadAndValueChangedEvents_setValue() { + public void nestedFirstLoadAndValueChangedEvents_setValue() throws XFormParser.ParseException { Scenario scenario = Scenario.init("multiple-events.xml"); assertThat(scenario.answerOf("/data/my-calculated-value").getDisplayText(), is("10")); @@ -58,7 +59,7 @@ public void nestedFirstLoadAndValueChangedEvents_setValue() { } @Test - public void serializedAndDeserializedNestedFirstLoadAndValueChangedEvents_setValue() throws IOException, DeserializationException { + public void serializedAndDeserializedNestedFirstLoadAndValueChangedEvents_setValue() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("multiple-events.xml"); Scenario deserializedScenario = scenario.serializeAndDeserializeForm(); @@ -69,7 +70,7 @@ public void serializedAndDeserializedNestedFirstLoadAndValueChangedEvents_setVal } @Test - public void invalidEventNames_throwException() { + public void invalidEventNames_throwException() throws XFormParser.ParseException { expectedException.expect(XFormParseException.class); expectedException.expectMessage("An action was registered for unsupported events: odk-inftance-first-load, my-fake-event"); diff --git a/src/test/java/org/javarosa/core/model/actions/OdkNewRepeatEventTest.java b/src/test/java/org/javarosa/core/model/actions/OdkNewRepeatEventTest.java index 172a407e6..d035e2219 100644 --- a/src/test/java/org/javarosa/core/model/actions/OdkNewRepeatEventTest.java +++ b/src/test/java/org/javarosa/core/model/actions/OdkNewRepeatEventTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; import org.javarosa.xform.parse.XFormParseException; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; /** @@ -27,7 +28,7 @@ */ public class OdkNewRepeatEventTest { @Test - public void setValueOnRepeatInsertInBody_setsValueInRepeat() { + public void setValueOnRepeatInsertInBody_setsValueInRepeat() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("event-odk-new-repeat.xml")); assertThat(scenario.countRepeatInstancesOf("/data/my-repeat"), is(0)); @@ -37,7 +38,7 @@ public void setValueOnRepeatInsertInBody_setsValueInRepeat() { } @Test - public void addingRepeat_doesNotChangeValueSetForPreviousRepeat() { + public void addingRepeat_doesNotChangeValueSetForPreviousRepeat() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("event-odk-new-repeat.xml")); scenario.createNewRepeat("/data/my-repeat"); @@ -50,7 +51,7 @@ public void addingRepeat_doesNotChangeValueSetForPreviousRepeat() { } @Test - public void setValueOnRepeatInBody_usesCurrentContextForRelativeReferences() { + public void setValueOnRepeatInBody_usesCurrentContextForRelativeReferences() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("event-odk-new-repeat.xml")); scenario.answer("/data/my-toplevel-value", "12"); @@ -60,7 +61,7 @@ public void setValueOnRepeatInBody_usesCurrentContextForRelativeReferences() { } @Test - public void setValueOnRepeatWithCount_setsValueForEachRepeat() { + public void setValueOnRepeatWithCount_setsValueForEachRepeat() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("event-odk-new-repeat.xml")); scenario.answer("/data/repeat-count", 4); @@ -89,7 +90,7 @@ public void setValueOnRepeatWithCount_setsValueForEachRepeat() { } @Test - public void repeatInFormDefInstance_neverFiresNewRepeatEvent() { + public void repeatInFormDefInstance_neverFiresNewRepeatEvent() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("event-odk-new-repeat.xml")); assertThat(scenario.answerOf("/data/my-repeat-without-template[0]/my-value"), is(nullValue())); @@ -100,7 +101,7 @@ public void repeatInFormDefInstance_neverFiresNewRepeatEvent() { } @Test - public void newRepeatInstance_doesNotTriggerActionOnUnrelatedRepeat() throws IOException { + public void newRepeatInstance_doesNotTriggerActionOnUnrelatedRepeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Parallel repeats", html( head( title("Parallel repeats"), @@ -139,7 +140,7 @@ public void newRepeatInstance_doesNotTriggerActionOnUnrelatedRepeat() throws IOE } @Test - public void newRepeatInstance_canUsePreviousInstanceAsDefault() throws IOException { + public void newRepeatInstance_canUsePreviousInstanceAsDefault() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Default from prior instance", html( head( title("Default from prior instance"), @@ -177,7 +178,7 @@ public void newRepeatInstance_canUsePreviousInstanceAsDefault() throws IOExcepti // Not part of ODK XForms so throws parse exception. @Test(expected = XFormParseException.class) - public void setValueOnRepeatInsertInModel_notAllowed() { + public void setValueOnRepeatInsertInModel_notAllowed() throws XFormParser.ParseException { Scenario.init(r("event-odk-new-repeat-model.xml")); } } diff --git a/src/test/java/org/javarosa/core/model/actions/RecordAudioActionTest.java b/src/test/java/org/javarosa/core/model/actions/RecordAudioActionTest.java index 221feee2e..7763f2481 100644 --- a/src/test/java/org/javarosa/core/model/actions/RecordAudioActionTest.java +++ b/src/test/java/org/javarosa/core/model/actions/RecordAudioActionTest.java @@ -33,11 +33,12 @@ import org.javarosa.core.model.actions.recordaudio.RecordAudioActions; import org.javarosa.core.test.Scenario; import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class RecordAudioActionTest { @Test - public void recordAudioAction_isProcessedOnFormParse() throws IOException { + public void recordAudioAction_isProcessedOnFormParse() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Record audio form", html( head( title("Record audio form"), @@ -57,7 +58,7 @@ public void recordAudioAction_isProcessedOnFormParse() throws IOException { } @Test - public void recordAudioAction_callsListenerActionTriggeredWhenTriggered() throws IOException { + public void recordAudioAction_callsListenerActionTriggeredWhenTriggered() throws IOException, XFormParser.ParseException { CapturingRecordAudioActionListener listener = new CapturingRecordAudioActionListener(); RecordAudioActions.setRecordAudioListener(listener); @@ -81,7 +82,7 @@ public void recordAudioAction_callsListenerActionTriggeredWhenTriggered() throws } @Test - public void targetReferenceInRepeat_isContextualized() throws IOException { + public void targetReferenceInRepeat_isContextualized() throws IOException, XFormParser.ParseException { CapturingRecordAudioActionListener listener = new CapturingRecordAudioActionListener(); RecordAudioActions.setRecordAudioListener(listener); @@ -106,7 +107,7 @@ public void targetReferenceInRepeat_isContextualized() throws IOException { } @Test - public void serializationAndDeserialization_maintainsFields() throws IOException, DeserializationException { + public void serializationAndDeserialization_maintainsFields() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("Record audio form", html( head( title("Record audio form"), diff --git a/src/test/java/org/javarosa/core/model/actions/SetGeopointActionTest.java b/src/test/java/org/javarosa/core/model/actions/SetGeopointActionTest.java index 15c86322d..97f5c708e 100644 --- a/src/test/java/org/javarosa/core/model/actions/SetGeopointActionTest.java +++ b/src/test/java/org/javarosa/core/model/actions/SetGeopointActionTest.java @@ -36,6 +36,7 @@ import org.javarosa.core.test.Scenario; import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.xform.parse.XFormParseException; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class SetGeopointActionTest { @@ -43,19 +44,19 @@ public class SetGeopointActionTest { stringAnswer("no client implementation"); @Test(expected = XFormParseException.class) - public void when_namespaceIsNotOdk_exceptionIsThrown() throws IOException { + public void when_namespaceIsNotOdk_exceptionIsThrown() throws IOException, XFormParser.ParseException { Scenario.init(r("setgeopoint-action-bad-namespace.xml")); } @Test - public void when_instanceIsLoaded_locationIsSetAtTarget() throws IOException { + public void when_instanceIsLoaded_locationIsSetAtTarget() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init(r("setgeopoint-action-instance-load.xml")); assertThat(scenario.answerOf("/data/location"), is(EXPECTED_STUB_ANSWER)); } @Test - public void when_triggerNodeIsUpdated_locationIsSetAtTarget() throws IOException { + public void when_triggerNodeIsUpdated_locationIsSetAtTarget() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init(r("setgeopoint-action-value-changed.xml")); // The test form has no default value at /data/location, and diff --git a/src/test/java/org/javarosa/core/model/actions/SetValueActionTest.java b/src/test/java/org/javarosa/core/model/actions/SetValueActionTest.java index bd28b9fa2..603dda278 100644 --- a/src/test/java/org/javarosa/core/model/actions/SetValueActionTest.java +++ b/src/test/java/org/javarosa/core/model/actions/SetValueActionTest.java @@ -40,13 +40,14 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.xform.parse.XFormParser; import org.javarosa.xpath.XPathTypeMismatchException; import org.junit.Ignore; import org.junit.Test; public class SetValueActionTest { @Test - public void when_triggerNodeIsUpdated_targetNodeCalculation_isEvaluated() throws IOException { + public void when_triggerNodeIsUpdated_targetNodeCalculation_isEvaluated() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested setvalue action", html( head( title("Nested setvalue action"), @@ -73,7 +74,7 @@ public void when_triggerNodeIsUpdated_targetNodeCalculation_isEvaluated() throws } @Test - public void when_triggerNodeIsUpdatedWithTheSameValue_targetNodeCalculation_isNotEvaluated() throws IOException { + public void when_triggerNodeIsUpdatedWithTheSameValue_targetNodeCalculation_isNotEvaluated() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested setvalue action", html( head( title("Nested setvalue action"), @@ -109,7 +110,7 @@ public void when_triggerNodeIsUpdatedWithTheSameValue_targetNodeCalculation_isNo } @Test - public void setvalue_isSerializedAndDeserialized() throws IOException, DeserializationException { + public void setvalue_isSerializedAndDeserialized() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested setvalue action", html( head( title("Nested setvalue action"), @@ -138,7 +139,7 @@ public void setvalue_isSerializedAndDeserialized() throws IOException, Deseriali //region groups @Test - public void setvalueInGroup_setsValueOutsideOfGroup() throws IOException { + public void setvalueInGroup_setsValueOutsideOfGroup() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue", html( head( title("Setvalue"), @@ -164,7 +165,7 @@ public void setvalueInGroup_setsValueOutsideOfGroup() throws IOException { } @Test - public void setvalueOutsideGroup_setsValueInGroup() throws IOException { + public void setvalueOutsideGroup_setsValueInGroup() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue", html( head( title("Setvalue"), @@ -191,7 +192,7 @@ public void setvalueOutsideGroup_setsValueInGroup() throws IOException { //region repeats @Test - public void sourceInRepeat_updatesDestInSameRepeatInstance() throws IOException { + public void sourceInRepeat_updatesDestInSameRepeatInstance() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested setvalue action with repeats", html( head( title("Nested setvalue action with repeats"), @@ -229,7 +230,7 @@ public void sourceInRepeat_updatesDestInSameRepeatInstance() throws IOException } @Test - public void setvalueAtRoot_setsValueOfNodeInFirstRepeatInstance() throws IOException { + public void setvalueAtRoot_setsValueOfNodeInFirstRepeatInstance() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue into repeat", html( head( title("Setvalue into repeat"), @@ -259,7 +260,7 @@ public void setvalueAtRoot_setsValueOfNodeInFirstRepeatInstance() throws IOExcep @Ignore("TODO: verifyActions seems like it may be overzealous") @Test - public void setvalueAtRoot_setsValueOfNodeInRepeatInstanceAddedAfterFormLoad() throws IOException { + public void setvalueAtRoot_setsValueOfNodeInRepeatInstanceAddedAfterFormLoad() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue into repeat", html( head( title("Setvalue into repeat"), @@ -288,7 +289,7 @@ public void setvalueAtRoot_setsValueOfNodeInRepeatInstanceAddedAfterFormLoad() t } @Test - public void setValueAtRoot_throwsExpression_whenTargetIsUnboundReference() throws IOException { + public void setValueAtRoot_throwsExpression_whenTargetIsUnboundReference() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue into repeat", html( head( title("Setvalue into repeat"), @@ -321,7 +322,7 @@ public void setValueAtRoot_throwsExpression_whenTargetIsUnboundReference() throw } @Test - public void setValueInRepeat_setsValueOutsideOfRepeat() throws IOException { + public void setValueInRepeat_setsValueOutsideOfRepeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested setvalue action with repeats", html( head( title("Nested setvalue action with repeats"), @@ -356,7 +357,7 @@ public void setValueInRepeat_setsValueOutsideOfRepeat() throws IOException { } @Test - public void setvalueInOuterRepeat_setsInnerRepeatValue() throws IOException { + public void setvalueInOuterRepeat_setsInnerRepeatValue() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested repeats", html( head( title("Nested repeats"), @@ -388,7 +389,7 @@ public void setvalueInOuterRepeat_setsInnerRepeatValue() throws IOException { * field. */ @Test - public void setvalue_setsValueOfReadOnlyField() throws IOException { + public void setvalue_setsValueOfReadOnlyField() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue readonly", html( head( title("Setvalue readonly"), @@ -408,7 +409,7 @@ public void setvalue_setsValueOfReadOnlyField() throws IOException { } @Test - public void setvalue_withInnerEmptyString_clearsTarget() throws IOException { + public void setvalue_withInnerEmptyString_clearsTarget() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue empty string", html( head( title("Setvalue empty string"), @@ -428,7 +429,7 @@ public void setvalue_withInnerEmptyString_clearsTarget() throws IOException { } @Test - public void setvalue_withEmptyStringValue_clearsTarget() throws IOException { + public void setvalue_withEmptyStringValue_clearsTarget() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue empty string", html( head( title("Setvalue empty string"), @@ -448,7 +449,7 @@ public void setvalue_withEmptyStringValue_clearsTarget() throws IOException { } @Test - public void setvalue_setsValueOfMultipleFields() throws IOException { + public void setvalue_setsValueOfMultipleFields() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Setvalue multiple destinations", html( head( title("Setvalue multiple destinations"), @@ -474,7 +475,7 @@ public void setvalue_setsValueOfMultipleFields() throws IOException { } @Test - public void xformsValueChanged_triggeredAfterRecomputation() throws IOException { + public void xformsValueChanged_triggeredAfterRecomputation() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("xforms-value-changed-event", html( head( title("Value changed event"), diff --git a/src/test/java/org/javarosa/core/model/condition/EvaluationContextExpandReferenceTest.java b/src/test/java/org/javarosa/core/model/condition/EvaluationContextExpandReferenceTest.java index c702654bf..6924ac34d 100644 --- a/src/test/java/org/javarosa/core/model/condition/EvaluationContextExpandReferenceTest.java +++ b/src/test/java/org/javarosa/core/model/condition/EvaluationContextExpandReferenceTest.java @@ -38,6 +38,7 @@ import java.io.IOException; import org.javarosa.core.model.instance.TreeReference; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.BeforeClass; import org.junit.Test; @@ -46,7 +47,7 @@ public class EvaluationContextExpandReferenceTest { private static EvaluationContext ec; @BeforeClass - public static void setUp() throws IOException { + public static void setUp() throws IOException, XFormParser.ParseException { scenario = Scenario.init("Some form", html( head( title("Some form"), diff --git a/src/test/java/org/javarosa/core/model/condition/ReadOnlyCalculateTest.java b/src/test/java/org/javarosa/core/model/condition/ReadOnlyCalculateTest.java index e17ad20fb..c2b9e8d06 100644 --- a/src/test/java/org/javarosa/core/model/condition/ReadOnlyCalculateTest.java +++ b/src/test/java/org/javarosa/core/model/condition/ReadOnlyCalculateTest.java @@ -31,6 +31,7 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class ReadOnlyCalculateTest { @@ -38,7 +39,7 @@ public class ReadOnlyCalculateTest { * Read-only is only a UI concern so calculates should be evaluated on read-only fields. */ @Test - public void calculate_evaluatedOnReadonlyFieldWithUi() throws IOException { + public void calculate_evaluatedOnReadonlyFieldWithUi() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Calculate readonly", html( head( title("Calculate readonly"), diff --git a/src/test/java/org/javarosa/core/model/condition/RecalculateTest.java b/src/test/java/org/javarosa/core/model/condition/RecalculateTest.java index 96e9108cd..96317453d 100644 --- a/src/test/java/org/javarosa/core/model/condition/RecalculateTest.java +++ b/src/test/java/org/javarosa/core/model/condition/RecalculateTest.java @@ -8,13 +8,14 @@ import org.javarosa.core.model.instance.InstanceInitializationFactory; import org.javarosa.core.test.FormParseInit; import org.javarosa.form.api.FormEntryPrompt; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.Test; public class RecalculateTest { private FormDef formDef; @Before - public void setUp() { + public void setUp() throws XFormParser.ParseException { FormParseInit fpi = new FormParseInit(r("calculate-now.xml")); formDef = fpi.getFormDef(); formDef.initialize(true, new InstanceInitializationFactory()); diff --git a/src/test/java/org/javarosa/core/model/instance/test/TreeElementTests.java b/src/test/java/org/javarosa/core/model/instance/test/TreeElementTests.java index b76e082da..e98f40283 100644 --- a/src/test/java/org/javarosa/core/model/instance/test/TreeElementTests.java +++ b/src/test/java/org/javarosa/core/model/instance/test/TreeElementTests.java @@ -18,7 +18,7 @@ public class TreeElementTests { @Test - public void testPopulate_withNodesAttributes() throws IOException { + public void testPopulate_withNodesAttributes() throws IOException, XFormParser.ParseException { // Given FormParseInit formParseInit = new FormParseInit(r("populate-nodes-attributes.xml")); diff --git a/src/test/java/org/javarosa/core/model/test/FormDefTest.java b/src/test/java/org/javarosa/core/model/test/FormDefTest.java index ac991bc34..36ba11c26 100644 --- a/src/test/java/org/javarosa/core/model/test/FormDefTest.java +++ b/src/test/java/org/javarosa/core/model/test/FormDefTest.java @@ -44,6 +44,7 @@ import org.javarosa.core.test.Scenario; import org.javarosa.core.util.XFormsElement; import org.javarosa.form.api.FormEntryCaption; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; /** * See testAnswerConstraint() for an example of how to write the @@ -51,7 +52,7 @@ */ public class FormDefTest { @Test - public void enforces_constraints_defined_in_a_field() { + public void enforces_constraints_defined_in_a_field() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("ImageSelectTester.xhtml")); scenario.next(); scenario.next(); @@ -63,7 +64,7 @@ public void enforces_constraints_defined_in_a_field() { } @Test - public void enforcesConstraints_whenInstanceIsDeserialized() throws IOException { + public void enforcesConstraints_whenInstanceIsDeserialized() throws IOException, XFormParser.ParseException { XFormsElement formDef = html( head( title("Some form"), @@ -96,7 +97,7 @@ public void enforcesConstraints_whenInstanceIsDeserialized() throws IOException //region Repeat relevance @Test - public void repeatRelevanceChanges_whenDependentValuesOfRelevanceExpressionChange() throws IOException { + public void repeatRelevanceChanges_whenDependentValuesOfRelevanceExpressionChange() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Repeat relevance - dynamic expression", html( head( title("Repeat relevance - dynamic expression"), @@ -125,7 +126,7 @@ public void repeatRelevanceChanges_whenDependentValuesOfRelevanceExpressionChang } @Test - public void repeatIsIrrelevant_whenRelevanceSetToFalse() throws IOException { + public void repeatIsIrrelevant_whenRelevanceSetToFalse() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Repeat relevance - false()", html( head( title("Repeat relevance - false()"), @@ -147,7 +148,7 @@ public void repeatIsIrrelevant_whenRelevanceSetToFalse() throws IOException { } @Test - public void repeatRelevanceChanges_whenDependentValuesOfGrandparentRelevanceExpressionChange() throws IOException { + public void repeatRelevanceChanges_whenDependentValuesOfGrandparentRelevanceExpressionChange() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Repeat relevance - dynamic expression", html( head( title("Repeat relevance - dynamic expression"), @@ -180,7 +181,7 @@ public void repeatRelevanceChanges_whenDependentValuesOfGrandparentRelevanceExpr } @Test - public void repeatIsIrrelevant_whenGrandparentRelevanceSetToFalse() throws IOException { + public void repeatIsIrrelevant_whenGrandparentRelevanceSetToFalse() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Repeat relevance - false()", html( head( title("Repeat relevance - false()"), @@ -207,7 +208,7 @@ public void repeatIsIrrelevant_whenGrandparentRelevanceSetToFalse() throws IOExc } @Test - public void nestedRepeatRelevance_updatesBasedOnParentPosition() throws IOException { + public void nestedRepeatRelevance_updatesBasedOnParentPosition() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested repeat relevance", html( head( title("Nested repeat relevance"), @@ -268,7 +269,7 @@ public void nestedRepeatRelevance_updatesBasedOnParentPosition() throws IOExcept //endregion @Test - public void fillTemplateString_resolvesRelativeReferences() throws IOException { + public void fillTemplateString_resolvesRelativeReferences() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init(" with relative ref", html( head( title("output with relative ref"), @@ -304,7 +305,7 @@ public void fillTemplateString_resolvesRelativeReferences() throws IOException { } @Test - public void fillTemplateString_resolvesRelativeReferences_inItext() throws IOException { + public void fillTemplateString_resolvesRelativeReferences_inItext() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init(" with relative ref in translation", html( head( title("output with relative ref in translation"), diff --git a/src/test/java/org/javarosa/core/model/test/FormIndexSerializationTest.java b/src/test/java/org/javarosa/core/model/test/FormIndexSerializationTest.java index e83f40035..95c7f50d1 100644 --- a/src/test/java/org/javarosa/core/model/test/FormIndexSerializationTest.java +++ b/src/test/java/org/javarosa/core/model/test/FormIndexSerializationTest.java @@ -14,6 +14,7 @@ import org.javarosa.core.model.instance.TreeReference; import org.javarosa.core.test.FormParseInit; import org.javarosa.form.api.FormEntryController; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class FormIndexSerializationTest { @@ -52,7 +53,7 @@ public void testLocalAndInstanceNonNullReference() throws IOException, ClassNotF } @Test - public void testOnFormController() throws IOException, ClassNotFoundException { + public void testOnFormController() throws IOException, ClassNotFoundException, XFormParser.ParseException { FormParseInit formParseInit = new FormParseInit(r("formindex-serialization.xml")); FormEntryController formEntryController = formParseInit.getFormEntryController(); diff --git a/src/test/java/org/javarosa/core/model/test/QuestionDefTest.java b/src/test/java/org/javarosa/core/model/test/QuestionDefTest.java index 9427e9421..8dbec4ee6 100644 --- a/src/test/java/org/javarosa/core/model/test/QuestionDefTest.java +++ b/src/test/java/org/javarosa/core/model/test/QuestionDefTest.java @@ -35,6 +35,7 @@ import org.javarosa.core.util.externalizable.ExtUtil; import org.javarosa.core.util.externalizable.PrototypeFactory; import org.javarosa.form.api.FormEntryPrompt; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.Test; @@ -44,7 +45,7 @@ public class QuestionDefTest { private FormParseInit fpi = null; @Before - public void setUp() { + public void setUp() throws XFormParser.ParseException { fpi = new FormParseInit(); fpi.getFirstQuestionDef(); } diff --git a/src/test/java/org/javarosa/core/test/FormParseInit.java b/src/test/java/org/javarosa/core/test/FormParseInit.java index a71e7dea6..b87604bb0 100644 --- a/src/test/java/org/javarosa/core/test/FormParseInit.java +++ b/src/test/java/org/javarosa/core/test/FormParseInit.java @@ -16,6 +16,7 @@ import org.javarosa.form.api.FormEntryCaption; import org.javarosa.form.api.FormEntryController; import org.javarosa.form.api.FormEntryModel; +import org.javarosa.xform.parse.XFormParser; import org.javarosa.xform.util.XFormUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,17 +37,17 @@ public class FormParseInit { private FormEntryController fec; private FormEntryModel femodel; - public FormParseInit() { + public FormParseInit() throws XFormParser.ParseException { FORM_NAME = r("ImageSelectTester.xhtml").toString(); this.init(); } - public FormParseInit(Path form) { + public FormParseInit(Path form) throws XFormParser.ParseException { FORM_NAME = form.toString(); this.init(); } - private void init() { + private void init() throws XFormParser.ParseException { String xf_name = FORM_NAME; try (FileInputStream is = new FileInputStream(xf_name)) { xform = XFormUtils.getFormFromInputStream(is); diff --git a/src/test/java/org/javarosa/core/test/Scenario.java b/src/test/java/org/javarosa/core/test/Scenario.java index e750e1fa8..86e77f617 100644 --- a/src/test/java/org/javarosa/core/test/Scenario.java +++ b/src/test/java/org/javarosa/core/test/Scenario.java @@ -283,7 +283,7 @@ public Scenario serializeAndDeserializeForm() throws IOException, Deserializatio // that XFormParser.loadXmlInstance(FormDef f, Reader xmlReader) should probably be public. This is also the method that Collect // copies because the FormDef may be built from cache meaning there won't be a Reader/Document available and because it makes // some extra calls for search(). We pass in an XFormsElement for now until we decide on an interface that Collect can use. - public Scenario serializeAndDeserializeInstance(XFormsElement form) throws IOException { + public Scenario serializeAndDeserializeInstance(XFormsElement form) throws IOException, XFormParser.ParseException { FormInstance originalInstance = getFormDef().getMainInstance(); XFormSerializingVisitor serializer = new XFormSerializingVisitor(); byte[] formInstanceBytes = serializer.serializeInstance(originalInstance); @@ -429,7 +429,7 @@ private FormIndex getIndexOf(TreeReference ref) { * Initializes the Scenario using a form defined using the DSL in XFormsElement */ // TODO Extract the form's name from the provided XFormsElement object to simplify args - public static Scenario init(String formName, XFormsElement form) throws IOException { + public static Scenario init(String formName, XFormsElement form) throws IOException, XFormParser.ParseException { Path formFile = createTempDirectory("javarosa").resolve(formName + ".xml"); String xml = form.asXml(); System.out.println(xml); @@ -442,14 +442,14 @@ public static Scenario init(String formName, XFormsElement form) throws IOExcept *

* A form with the provided filename must exist in the classpath */ - public static Scenario init(String formFileName) { + public static Scenario init(String formFileName) throws XFormParser.ParseException { return init(r(formFileName)); } /** * Initializes the Scenario with the form at the provided path */ - public static Scenario init(Path formFile) { + public static Scenario init(Path formFile) throws XFormParser.ParseException { // TODO explain why this sequence of calls StorageManager.setStorageFactory((name, type) -> new DummyIndexedStorageUtility<>()); new XFormsModule().registerModule(); diff --git a/src/test/java/org/javarosa/core/util/GeoAreaTest.java b/src/test/java/org/javarosa/core/util/GeoAreaTest.java index 1939409b5..22ee67518 100644 --- a/src/test/java/org/javarosa/core/util/GeoAreaTest.java +++ b/src/test/java/org/javarosa/core/util/GeoAreaTest.java @@ -32,11 +32,12 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class GeoAreaTest { @Test - public void area_isComputedForGeoshape() throws IOException { + public void area_isComputedForGeoshape() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("geoshape area", html( head( title("Geoshape area"), @@ -60,7 +61,7 @@ public void area_isComputedForGeoshape() throws IOException { } @Test - public void area_isComputedForGeopointNodeset() throws IOException { + public void area_isComputedForGeopointNodeset() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("geopoint nodeset area", html( head( title("Geopoint nodeset area"), @@ -100,7 +101,7 @@ public void area_isComputedForGeopointNodeset() throws IOException { } @Test - public void area_isComputedForString() throws IOException { + public void area_isComputedForString() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("string area", html( head( title("String area"), @@ -134,7 +135,7 @@ public void area_isComputedForString() throws IOException { } @Test - public void area_whenShapeHasFewerThanThreePoints_isZero() throws IOException { + public void area_whenShapeHasFewerThanThreePoints_isZero() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("geoshape area", html( head( title("Geoshape area"), diff --git a/src/test/java/org/javarosa/core/util/GeoDistanceTest.java b/src/test/java/org/javarosa/core/util/GeoDistanceTest.java index a29c8493f..28ef33e6d 100644 --- a/src/test/java/org/javarosa/core/util/GeoDistanceTest.java +++ b/src/test/java/org/javarosa/core/util/GeoDistanceTest.java @@ -34,13 +34,14 @@ import java.io.IOException; import org.hamcrest.number.IsCloseTo; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class GeoDistanceTest { private static final double NINETY_DEGREES_ON_EQUATOR_KM = EARTH_EQUATORIAL_CIRCUMFERENCE_METERS / 4; @Test - public void distance_isComputedForGeopointNodeset() throws IOException { + public void distance_isComputedForGeopointNodeset() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("geopoint nodeset distance", html( head( title("Geopoint nodeset distance"), @@ -70,7 +71,7 @@ public void distance_isComputedForGeopointNodeset() throws IOException { } @Test - public void distance_isComputedForGeotrace() throws IOException { + public void distance_isComputedForGeotrace() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("geotrace distance", html( head( title("Geotrace distance"), @@ -93,7 +94,7 @@ public void distance_isComputedForGeotrace() throws IOException { } @Test - public void distance_isComputedForGeoshape() throws IOException { + public void distance_isComputedForGeoshape() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("geoshape distance", html( head( title("Geoshape distance"), @@ -116,7 +117,7 @@ public void distance_isComputedForGeoshape() throws IOException { } @Test - public void distance_isComputedForString() throws IOException { + public void distance_isComputedForString() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("string distance", html( head( title("String distance"), diff --git a/src/test/java/org/javarosa/entities/EntitiesTest.java b/src/test/java/org/javarosa/entities/EntitiesTest.java index 2caeb2cc7..2c8d6415e 100644 --- a/src/test/java/org/javarosa/entities/EntitiesTest.java +++ b/src/test/java/org/javarosa/entities/EntitiesTest.java @@ -7,6 +7,7 @@ import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.entities.internal.Entities; import org.javarosa.entities.internal.EntityFormParseProcessor; +import org.javarosa.xform.parse.XFormParser; import org.javarosa.xform.parse.XFormParserFactory; import org.javarosa.xform.util.XFormUtils; import org.junit.After; @@ -46,7 +47,7 @@ public void teardown() { } @Test - public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOException { + public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Entity form", XFormsElement.html( asList( new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") @@ -81,7 +82,7 @@ public void fillingFormWithoutCreate_doesNotCreateAnyEntities() throws IOExcepti } @Test - public void fillingFormWithCreate_makesEntityAvailable() throws IOException { + public void fillingFormWithCreate_makesEntityAvailable() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( asList( new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") @@ -120,7 +121,7 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException { } @Test - public void fillingFormWithNonRelevantCreate_doesNotCreateAnyEntities() throws IOException { + public void fillingFormWithNonRelevantCreate_doesNotCreateAnyEntities() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( asList( new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") @@ -161,7 +162,7 @@ public void fillingFormWithNonRelevantCreate_doesNotCreateAnyEntities() throws I } @Test - public void entityFormCanBeSerialized() throws IOException, DeserializationException { + public void entityFormCanBeSerialized() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( asList( new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") @@ -203,7 +204,7 @@ public void entityFormCanBeSerialized() throws IOException, DeserializationExcep } @Test - public void entitiesNamespaceWorksRegardlessOfName() throws IOException, DeserializationException { + public void entitiesNamespaceWorksRegardlessOfName() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( asList( new Pair<>("blah", "http://www.opendatakit.org/xforms/entities") @@ -241,7 +242,7 @@ public void entitiesNamespaceWorksRegardlessOfName() throws IOException, Deseria } @Test - public void mustUseCorrectNamespace() throws IOException { + public void mustUseCorrectNamespace() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( asList( new Pair<>("entities", "http://www.example.com/xforms/entities") @@ -278,7 +279,7 @@ public void mustUseCorrectNamespace() throws IOException { } @Test - public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEntity() throws IOException { + public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEntity() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( asList( new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") @@ -316,7 +317,7 @@ public void fillingFormWithSelectSaveTo_andWithCreate_savesValuesCorrectlyToEnti } @Test - public void savetoIsRemovedFromBindAttributesForClients() throws IOException { + public void savetoIsRemovedFromBindAttributesForClients() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Create entity form", XFormsElement.html( asList( new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") diff --git a/src/test/java/org/javarosa/entities/EntityFormParserTest.java b/src/test/java/org/javarosa/entities/EntityFormParserTest.java index a03f2baac..a26f058c6 100644 --- a/src/test/java/org/javarosa/entities/EntityFormParserTest.java +++ b/src/test/java/org/javarosa/entities/EntityFormParserTest.java @@ -26,7 +26,7 @@ public class EntityFormParserTest { @Test - public void parseFirstDatasetToCreate_ignoresDatasetWithCreateActionWithIncorrectNamespace() throws IOException { + public void parseFirstDatasetToCreate_ignoresDatasetWithCreateActionWithIncorrectNamespace() throws IOException, XFormParser.ParseException { XFormsElement form = XFormsElement.html( asList( new Pair<>("correct", "http://www.opendatakit.org/xforms/entities"), diff --git a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java index 066b2d4d2..93451c626 100644 --- a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java +++ b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java @@ -31,7 +31,7 @@ public class EntityFormParseProcessorTest { @Test - public void whenVersionIsMissing_parsesWithoutError() throws IOException { + public void whenVersionIsMissing_parsesWithoutError() throws IOException, XFormParser.ParseException { XFormsElement form = XFormsElement.html( head( title("Non entity form"), @@ -100,7 +100,7 @@ public void whenVersionIsMissing_andThereIsAnEntityElement_throwsException() { } @Test(expected = UnrecognizedEntityVersionException.class) - public void whenVersionIsNotRecognized_throwsException() throws IOException { + public void whenVersionIsNotRecognized_throwsException() throws IOException, XFormParser.ParseException { XFormsElement form = XFormsElement.html( asList( new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") @@ -133,7 +133,7 @@ public void whenVersionIsNotRecognized_throwsException() throws IOException { } @Test - public void whenVersionIsNewPatch_doesNotThrowException() throws IOException { + public void whenVersionIsNewPatch_doesNotThrowException() throws IOException, XFormParser.ParseException { String newPatchVersion = EntityFormParseProcessor.SUPPORTED_VERSION + ".12"; XFormsElement form = XFormsElement.html( @@ -168,7 +168,7 @@ public void whenVersionIsNewPatch_doesNotThrowException() throws IOException { } @Test - public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException { + public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException, XFormParser.ParseException { XFormsElement form = XFormsElement.html( asList( new Pair<>("correct", "http://www.opendatakit.org/xforms/entities"), diff --git a/src/test/java/org/javarosa/form/api/ConstraintTextTest.java b/src/test/java/org/javarosa/form/api/ConstraintTextTest.java index 683cb5cc8..1fcbe7b2e 100644 --- a/src/test/java/org/javarosa/form/api/ConstraintTextTest.java +++ b/src/test/java/org/javarosa/form/api/ConstraintTextTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import org.javarosa.core.model.FormDef; import org.javarosa.core.test.FormParseInit; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; import java.util.Arrays; @@ -27,7 +28,7 @@ public class ConstraintTextTest { @Test - public void checkConstraintTexts() { + public void checkConstraintTexts() throws XFormParser.ParseException { FormParseInit fpi = new FormParseInit(r("constraint-message-error.xml")); FormEntryModel formEntryModel = fpi.getFormEntryModel(); FormDef formDef = fpi.getFormDef(); diff --git a/src/test/java/org/javarosa/form/api/FormEntryModelTest.java b/src/test/java/org/javarosa/form/api/FormEntryModelTest.java index 8d0534e5c..6f2967478 100644 --- a/src/test/java/org/javarosa/form/api/FormEntryModelTest.java +++ b/src/test/java/org/javarosa/form/api/FormEntryModelTest.java @@ -33,11 +33,12 @@ import org.javarosa.core.model.FormDef; import org.javarosa.core.model.FormIndex; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class FormEntryModelTest { @Test - public void isIndexRelevant_respectsRelevanceOfOutermostGroup() throws IOException { + public void isIndexRelevant_respectsRelevanceOfOutermostGroup() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Nested relevance", html( head( title("Nested relevance"), diff --git a/src/test/java/org/javarosa/form/api/FormEntryPromptTest.java b/src/test/java/org/javarosa/form/api/FormEntryPromptTest.java index 57cc8692f..a13e77290 100644 --- a/src/test/java/org/javarosa/form/api/FormEntryPromptTest.java +++ b/src/test/java/org/javarosa/form/api/FormEntryPromptTest.java @@ -33,12 +33,13 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class FormEntryPromptTest { //region Binding of select choice values to labels @Test - public void getSelectItemText_onSelectionFromDynamicSelect_withoutTranslations_returnsLabelInnerText() throws IOException { + public void getSelectItemText_onSelectionFromDynamicSelect_withoutTranslations_returnsLabelInnerText() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Select", html( head( title("Select"), @@ -67,7 +68,7 @@ public void getSelectItemText_onSelectionFromDynamicSelect_withoutTranslations_r } @Test - public void getSelectItemText_onSelectionFromDynamicSelect_withTranslations_returnsCorrectTranslation() throws IOException { + public void getSelectItemText_onSelectionFromDynamicSelect_withTranslations_returnsCorrectTranslation() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Multilingual dynamic select", html( head( title("Multilingual dynamic select"), @@ -112,7 +113,7 @@ public void getSelectItemText_onSelectionFromDynamicSelect_withTranslations_retu } @Test - public void getSelectItemText_onSelectionsInRepeatInstances_returnsLabelInnerText() throws IOException { + public void getSelectItemText_onSelectionsInRepeatInstances_returnsLabelInnerText() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Select", html( head( title("Select"), diff --git a/src/test/java/org/javarosa/form/api/FormNavigationTestCase.java b/src/test/java/org/javarosa/form/api/FormNavigationTestCase.java index 91b2ac6a0..910b6028d 100644 --- a/src/test/java/org/javarosa/form/api/FormNavigationTestCase.java +++ b/src/test/java/org/javarosa/form/api/FormNavigationTestCase.java @@ -1,6 +1,7 @@ package org.javarosa.form.api; import org.javarosa.core.test.FormParseInit; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -54,7 +55,7 @@ public FormNavigationTestCase(String formName, String[] expectedIndices) { } @Test - public void formNavigationTestCase() { + public void formNavigationTestCase() throws XFormParser.ParseException { testIndices(); } @@ -62,7 +63,7 @@ public void formNavigationTestCase() { // For each form, simulate increasing the index until the end of the // form and then decreasing until the beginning of the form. // Verify the expected indices before and after each operation. - public void testIndices() { + public void testIndices() throws XFormParser.ParseException { FormParseInit fpi = new FormParseInit(r("navigation/" + formName)); FormEntryController formEntryController = fpi.getFormEntryController(); FormEntryModel formEntryModel = fpi.getFormEntryModel(); diff --git a/src/test/java/org/javarosa/model/xform/CompactSerializingVisitorTest.java b/src/test/java/org/javarosa/model/xform/CompactSerializingVisitorTest.java index e22723272..1cb951e01 100644 --- a/src/test/java/org/javarosa/model/xform/CompactSerializingVisitorTest.java +++ b/src/test/java/org/javarosa/model/xform/CompactSerializingVisitorTest.java @@ -5,6 +5,7 @@ import org.javarosa.core.services.transport.payload.ByteArrayPayload; import org.javarosa.core.test.FormParseInit; import org.javarosa.form.api.FormEntryController; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.Test; @@ -26,7 +27,7 @@ public class CompactSerializingVisitorTest { private FormInstance formInstance; @Before - public void setUp() throws IOException { + public void setUp() throws IOException, XFormParser.ParseException { FormParseInit formParser = new FormParseInit(r("sms_form.xml")); FormEntryController formEntryController = formParser.getFormEntryController(); formInstance = formEntryController.getModel().getForm().getInstance(); @@ -72,7 +73,7 @@ public void ensureTagWithNoAnswerNotPresent() { } @Test - public void ensureThatFormWithNoSmsTagsIsEmpty() throws IOException { + public void ensureThatFormWithNoSmsTagsIsEmpty() throws IOException, XFormParser.ParseException { FormParseInit formParser = new FormParseInit(r("simple-form.xml")); FormEntryController formEntryController = formParser.getFormEntryController(); formInstance = formEntryController.getModel().getForm().getInstance(); diff --git a/src/test/java/org/javarosa/model/xform/XFormSerializingVisitorTest.java b/src/test/java/org/javarosa/model/xform/XFormSerializingVisitorTest.java index 03c606047..06013619b 100644 --- a/src/test/java/org/javarosa/model/xform/XFormSerializingVisitorTest.java +++ b/src/test/java/org/javarosa/model/xform/XFormSerializingVisitorTest.java @@ -2,6 +2,7 @@ import org.javarosa.core.test.Scenario; import org.javarosa.core.util.XFormsElement; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; import java.io.IOException; @@ -21,7 +22,7 @@ public class XFormSerializingVisitorTest { @Test - public void serializeInstance_preservesUnicodeCharacters() throws IOException { + public void serializeInstance_preservesUnicodeCharacters() throws IOException, XFormParser.ParseException { XFormsElement formDef = html( head( title("Some form"), diff --git a/src/test/java/org/javarosa/regression/IndexedRepeatRelativeRefsTest.java b/src/test/java/org/javarosa/regression/IndexedRepeatRelativeRefsTest.java index 038597d8e..b3213f6ac 100644 --- a/src/test/java/org/javarosa/regression/IndexedRepeatRelativeRefsTest.java +++ b/src/test/java/org/javarosa/regression/IndexedRepeatRelativeRefsTest.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.util.Arrays; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -74,7 +75,7 @@ public static Iterable data() { } @Test - public void indexed_repeat() throws IOException { + public void indexed_repeat() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Some form"), diff --git a/src/test/java/org/javarosa/regression/SameRefDifferentInstancesIssue449Test.java b/src/test/java/org/javarosa/regression/SameRefDifferentInstancesIssue449Test.java index 2f5b76c45..66e02c360 100644 --- a/src/test/java/org/javarosa/regression/SameRefDifferentInstancesIssue449Test.java +++ b/src/test/java/org/javarosa/regression/SameRefDifferentInstancesIssue449Test.java @@ -37,11 +37,12 @@ import org.javarosa.core.reference.ReferenceManagerTestUtils; import org.javarosa.core.test.Scenario; import org.javarosa.core.util.externalizable.DeserializationException; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class SameRefDifferentInstancesIssue449Test { @Test - public void formWithSameRefInDifferentInstances_isSuccessfullyDeserialized() throws IOException, DeserializationException { + public void formWithSameRefInDifferentInstances_isSuccessfullyDeserialized() throws IOException, DeserializationException, XFormParser.ParseException { Path formFile = r("issue_449.xml"); ReferenceManagerTestUtils.setUpSimpleReferenceManager(formFile.getParent(), "file"); Scenario scenario = Scenario.init(formFile); @@ -58,7 +59,7 @@ public void formWithSameRefInDifferentInstances_isSuccessfullyDeserialized() thr } @Test - public void constraintsAreCorrectlyApplied_afterDeserialization() throws IOException, DeserializationException { + public void constraintsAreCorrectlyApplied_afterDeserialization() throws IOException, DeserializationException, XFormParser.ParseException { Scenario scenario = Scenario.init("Tree reference deserialization", html( head( title("Tree reference deserialization"), diff --git a/src/test/java/org/javarosa/regression/TriggersForRelativeRefsTest.java b/src/test/java/org/javarosa/regression/TriggersForRelativeRefsTest.java index 1edc78bf4..d8b061581 100644 --- a/src/test/java/org/javarosa/regression/TriggersForRelativeRefsTest.java +++ b/src/test/java/org/javarosa/regression/TriggersForRelativeRefsTest.java @@ -1,6 +1,7 @@ package org.javarosa.regression; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; import java.io.IOException; @@ -23,7 +24,7 @@ public class TriggersForRelativeRefsTest { @Test - public void indefiniteRepeatJrCountExpression_inSingleRepeat_addsRepeatsUntilConditionMet() throws IOException { + public void indefiniteRepeatJrCountExpression_inSingleRepeat_addsRepeatsUntilConditionMet() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("indefinite repeat", html( head( title("Indefinite repeat"), @@ -60,7 +61,7 @@ public void indefiniteRepeatJrCountExpression_inSingleRepeat_addsRepeatsUntilCon } @Test - public void indefiniteRepeatJrCountExpression_inNestedRepeat_addsRepeatsUntilConditionMet() throws IOException { + public void indefiniteRepeatJrCountExpression_inNestedRepeat_addsRepeatsUntilConditionMet() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("nested indefinite repeat", html( head( title("Indefinite repeat in nested repeat"), @@ -111,7 +112,7 @@ public void indefiniteRepeatJrCountExpression_inNestedRepeat_addsRepeatsUntilCon } @Test - public void indefiniteRepeatJrCountExpression_inNestedRepeat_withRelativePaths_addsRepeatsUntilConditionMet() throws IOException { + public void indefiniteRepeatJrCountExpression_inNestedRepeat_withRelativePaths_addsRepeatsUntilConditionMet() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("nested indefinite repeat", html( head( title("Indefinite repeat in nested repeat"), @@ -162,7 +163,7 @@ public void indefiniteRepeatJrCountExpression_inNestedRepeat_withRelativePaths_a } @Test - public void predicateWithRelativePathExpression_reevaluated_whenTriggerChanges() throws IOException { + public void predicateWithRelativePathExpression_reevaluated_whenTriggerChanges() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Predicate trigger", html( head( title("Predicate trigger"), @@ -215,7 +216,7 @@ public void predicateWithRelativePathExpression_reevaluated_whenTriggerChanges() } @Test - public void predicateWithCurrentPathExpression_reevaluated_whenTriggerChanges() throws IOException { + public void predicateWithCurrentPathExpression_reevaluated_whenTriggerChanges() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Predicate trigger", html( head( title("Predicate trigger"), diff --git a/src/test/java/org/javarosa/smoketests/ChildVaccinationTest.java b/src/test/java/org/javarosa/smoketests/ChildVaccinationTest.java index 1163412ac..42a344377 100644 --- a/src/test/java/org/javarosa/smoketests/ChildVaccinationTest.java +++ b/src/test/java/org/javarosa/smoketests/ChildVaccinationTest.java @@ -38,6 +38,7 @@ import java.util.function.Consumer; import org.javarosa.core.model.instance.TreeReference; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class ChildVaccinationTest { @@ -60,7 +61,7 @@ public class ChildVaccinationTest { public static final LocalDate TODAY = LocalDate.now(); @Test - public void smoke_test() { + public void smoke_test() throws XFormParser.ParseException { Scenario scenario = Scenario.init("child_vaccination_VOL_tool_v12.xml"); // region Answer questions about the building diff --git a/src/test/java/org/javarosa/smoketests/WhoVATest.java b/src/test/java/org/javarosa/smoketests/WhoVATest.java index a70124024..3fc746001 100644 --- a/src/test/java/org/javarosa/smoketests/WhoVATest.java +++ b/src/test/java/org/javarosa/smoketests/WhoVATest.java @@ -28,11 +28,12 @@ import java.time.LocalDate; import java.util.stream.IntStream; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; public class WhoVATest { @Test - public void regression_after_2_17_0_relevance_updates() { + public void regression_after_2_17_0_relevance_updates() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("whova_form.xml")); // region Give consent to unblock the rest of the form @@ -76,7 +77,7 @@ public void regression_after_2_17_0_relevance_updates() { } @Test - public void smoke_test_route_fever_and_lumps() { + public void smoke_test_route_fever_and_lumps() throws XFormParser.ParseException { Scenario scenario = Scenario.init(r("whova_form.xml")); // region Give consent to unblock the rest of the form diff --git a/src/test/java/org/javarosa/xform/parse/AttributesTestCase.java b/src/test/java/org/javarosa/xform/parse/AttributesTestCase.java index 73b67e234..d225168f4 100644 --- a/src/test/java/org/javarosa/xform/parse/AttributesTestCase.java +++ b/src/test/java/org/javarosa/xform/parse/AttributesTestCase.java @@ -16,7 +16,7 @@ public class AttributesTestCase { @Test - public void testBindAttributes() { + public void testBindAttributes() throws XFormParser.ParseException { FormParseInit fpi = new FormParseInit(r("form_with_bind_attributes.xml")); FormEntryModel formEntryModel = fpi.getFormEntryModel(); FormDef formDef = fpi.getFormDef(); @@ -38,7 +38,7 @@ public void testBindAttributes() { } @Test - public void testAdditionalAttributes() { + public void testAdditionalAttributes() throws XFormParser.ParseException { FormParseInit fpi = new FormParseInit(r("form_with_additional_attributes.xml")); FormEntryModel formEntryModel = fpi.getFormEntryModel(); FormDef formDef = fpi.getFormDef(); diff --git a/src/test/java/org/javarosa/xform/parse/BindAttributeProcessorTest.java b/src/test/java/org/javarosa/xform/parse/BindAttributeProcessorTest.java index 2e40f6aa9..5ee6e3cd7 100644 --- a/src/test/java/org/javarosa/xform/parse/BindAttributeProcessorTest.java +++ b/src/test/java/org/javarosa/xform/parse/BindAttributeProcessorTest.java @@ -30,7 +30,7 @@ public class BindAttributeProcessorTest { @Test - public void doesNotProcessAttributeWithIncorrectNamespace() throws IOException { + public void doesNotProcessAttributeWithIncorrectNamespace() throws IOException, XFormParser.ParseException { XFormsElement form = XFormsElement.html( asList( new Pair<>("blah", "blah"), @@ -61,7 +61,7 @@ public void doesNotProcessAttributeWithIncorrectNamespace() throws IOException { assertThat(processor.processCalled, equalTo(false)); } @Test - public void doesNotRemovettributeWithIncorrectNamespace() throws IOException { + public void doesNotRemovettributeWithIncorrectNamespace() throws IOException, XFormParser.ParseException { XFormsElement form = XFormsElement.html( asList( new Pair<>("blah", "blah"), diff --git a/src/test/java/org/javarosa/xform/parse/ExternalSecondaryInstanceParseTest.java b/src/test/java/org/javarosa/xform/parse/ExternalSecondaryInstanceParseTest.java index 30c503bc9..723f9c0c4 100644 --- a/src/test/java/org/javarosa/xform/parse/ExternalSecondaryInstanceParseTest.java +++ b/src/test/java/org/javarosa/xform/parse/ExternalSecondaryInstanceParseTest.java @@ -42,7 +42,7 @@ public class ExternalSecondaryInstanceParseTest { //region Parsing of different file types into external secondary instances @Test - public void itemsFromExternalSecondaryXMLInstance_ShouldBeAvailableToXPathParser() throws IOException, XPathSyntaxException { + public void itemsFromExternalSecondaryXMLInstance_ShouldBeAvailableToXPathParser() throws IOException, XPathSyntaxException, XFormParser.ParseException { configureReferenceManagerCorrectly(); FormDef formDef = parse(r("external-select-xml.xml")); @@ -58,7 +58,7 @@ public void itemsFromExternalSecondaryXMLInstance_ShouldBeAvailableToXPathParser } @Test - public void itemsFromExternalSecondaryGeoJsonInstance_ShouldBeAvailableToXPathParser() throws IOException, XPathSyntaxException { + public void itemsFromExternalSecondaryGeoJsonInstance_ShouldBeAvailableToXPathParser() throws IOException, XPathSyntaxException, XFormParser.ParseException { configureReferenceManagerCorrectly(); FormDef formDef = parse(r("external-select-geojson.xml")); @@ -74,7 +74,7 @@ public void itemsFromExternalSecondaryGeoJsonInstance_ShouldBeAvailableToXPathPa } @Test - public void itemsFromExternalSecondaryCSVInstance_ShouldBeAvailableToXPathParser() throws IOException, XPathSyntaxException { + public void itemsFromExternalSecondaryCSVInstance_ShouldBeAvailableToXPathParser() throws IOException, XPathSyntaxException, XFormParser.ParseException { configureReferenceManagerCorrectly(); FormDef formDef = parse(r("external-select-csv.xml")); @@ -115,11 +115,13 @@ public void xformParseException_whenItemsetConfiguresValueOrLabelNotInExternalIn fail("Expected XFormParseException because itemset references don't exist in external instance"); } catch (XFormParseException e) { // pass + } catch (XFormParser.ParseException e) { + throw new RuntimeException(e); } } @Test - public void formWithExternalSecondaryXMLInstance_ShouldSerializeAndDeserialize() throws IOException, DeserializationException { + public void formWithExternalSecondaryXMLInstance_ShouldSerializeAndDeserialize() throws IOException, DeserializationException, XFormParser.ParseException { configureReferenceManagerCorrectly(); FormDef originalFormDef = parse(r("external-select-xml.xml")); @@ -131,7 +133,7 @@ public void formWithExternalSecondaryXMLInstance_ShouldSerializeAndDeserialize() } @Test - public void deserializedFormDefCreatedFromAFormWithExternalSecondaryXMLInstance_ShouldContainThatExternalInstance() throws IOException, DeserializationException { + public void deserializedFormDefCreatedFromAFormWithExternalSecondaryXMLInstance_ShouldContainThatExternalInstance() throws IOException, DeserializationException, XFormParser.ParseException { configureReferenceManagerCorrectly(); Path formPath = r("external-select-xml.xml"); @@ -147,7 +149,7 @@ public void deserializedFormDefCreatedFromAFormWithExternalSecondaryXMLInstance_ // ODK Collect has CSV-parsing features that bypass XPath and use databases. This test verifies that if a // secondary instance is declared but not referenced in an instance() call, it is ignored by JavaRosa. @Test - public void externalInstanceDeclaration_ShouldBeIgnored_WhenNotReferenced() { + public void externalInstanceDeclaration_ShouldBeIgnored_WhenNotReferenced() throws XFormParser.ParseException { configureReferenceManagerCorrectly(); FormParseInit fpi = new FormParseInit(r("unused-secondary-instance.xml")); @@ -157,7 +159,7 @@ public void externalInstanceDeclaration_ShouldBeIgnored_WhenNotReferenced() { } @Test - public void externalInstanceDeclaration_ShouldBeIgnored_WhenNotReferenced_AfterParsingFormWithReference() { + public void externalInstanceDeclaration_ShouldBeIgnored_WhenNotReferenced_AfterParsingFormWithReference() throws XFormParser.ParseException { configureReferenceManagerCorrectly(); FormParseInit fpi = new FormParseInit(r("external-select-csv.xml")); @@ -172,7 +174,7 @@ public void externalInstanceDeclaration_ShouldBeIgnored_WhenNotReferenced_AfterP // See https://github.com/getodk/javarosa/issues/451 @Test - public void dummyNodesInExternalInstanceDeclaration_ShouldBeIgnored() throws IOException, XPathSyntaxException { + public void dummyNodesInExternalInstanceDeclaration_ShouldBeIgnored() throws IOException, XPathSyntaxException, XFormParser.ParseException { configureReferenceManagerCorrectly(); FormDef formDef = parse(r("external-select-xml-dummy-nodes.xml")); @@ -184,7 +186,7 @@ public void dummyNodesInExternalInstanceDeclaration_ShouldBeIgnored() throws IOE //region Missing external file @Test - public void emptyPlaceholderInstanceIsUsed_whenExternalInstanceNotFound() { + public void emptyPlaceholderInstanceIsUsed_whenExternalInstanceNotFound() throws XFormParser.ParseException { configureReferenceManagerIncorrectly(); Scenario scenario = Scenario.init("external-select-csv.xml"); @@ -192,7 +194,7 @@ public void emptyPlaceholderInstanceIsUsed_whenExternalInstanceNotFound() { } @Test - public void realInstanceIsResolved_whenFormIsDeserialized_afterPlaceholderInstanceUsed_andFileNowExists() throws IOException, DeserializationException { + public void realInstanceIsResolved_whenFormIsDeserialized_afterPlaceholderInstanceUsed_andFileNowExists() throws IOException, DeserializationException, XFormParser.ParseException { configureReferenceManagerIncorrectly(); Scenario scenario = Scenario.init("external-select-csv.xml"); @@ -206,7 +208,7 @@ public void realInstanceIsResolved_whenFormIsDeserialized_afterPlaceholderInstan @Test // Clients would typically catch this exception and try parsing the form again which would succeed by using the placeholder. - public void fileNotFoundException_whenFormIsDeserialized_afterPlaceholderInstanceUsed_andFileStillMissing() throws IOException, DeserializationException { + public void fileNotFoundException_whenFormIsDeserialized_afterPlaceholderInstanceUsed_andFileStillMissing() throws IOException, DeserializationException, XFormParser.ParseException { configureReferenceManagerIncorrectly(); Scenario scenario = Scenario.init("external-select-csv.xml"); @@ -222,7 +224,7 @@ public void fileNotFoundException_whenFormIsDeserialized_afterPlaceholderInstanc // It would be possible for a formdef to be serialized without access to the external secondary instance and then // deserialized with access. In that case, there's nothing to validate that the value and label references for a // dynamic select correspond to real nodes in the secondary instance so there's a runtime exception when making a choice. - public void exceptionFromChoiceSelection_whenFormIsDeserialized_afterPlaceholderInstanceUsed_andFileMissingColumns() throws IOException, DeserializationException { + public void exceptionFromChoiceSelection_whenFormIsDeserialized_afterPlaceholderInstanceUsed_andFileMissingColumns() throws IOException, DeserializationException, XFormParser.ParseException { configureReferenceManagerIncorrectly(); Scenario scenario = Scenario.init("Some form", html( diff --git a/src/test/java/org/javarosa/xform/parse/FormParserHelper.java b/src/test/java/org/javarosa/xform/parse/FormParserHelper.java index 8bd8fe01f..b33beeb1d 100644 --- a/src/test/java/org/javarosa/xform/parse/FormParserHelper.java +++ b/src/test/java/org/javarosa/xform/parse/FormParserHelper.java @@ -22,11 +22,11 @@ public final class FormParserHelper { private FormParserHelper() { } - public static FormDef parse(Path formName) throws IOException { + public static FormDef parse(Path formName) throws IOException, XFormParser.ParseException { return XFormUtils.getFormFromInputStream(new FileInputStream(formName.toString())); } - public static FormDef parse(Path formName, String lastSavedSrc) throws IOException { + public static FormDef parse(Path formName, String lastSavedSrc) throws IOException, XFormParser.ParseException { return XFormUtils.getFormFromInputStream(new FileInputStream(formName.toString()), lastSavedSrc); } diff --git a/src/test/java/org/javarosa/xform/parse/XFormParserTest.java b/src/test/java/org/javarosa/xform/parse/XFormParserTest.java index 41af924a9..b637c46d6 100644 --- a/src/test/java/org/javarosa/xform/parse/XFormParserTest.java +++ b/src/test/java/org/javarosa/xform/parse/XFormParserTest.java @@ -97,13 +97,13 @@ public void tearDown() throws Exception { } @Test - public void parsesSimpleForm() throws IOException { + public void parsesSimpleForm() throws IOException, XFormParser.ParseException { FormDef formDef = parse(r("simple-form.xml")); assertEquals(formDef.getTitle(), "Simple Form"); } @Test - public void parsesForm2() throws IOException { + public void parsesForm2() throws IOException, XFormParser.ParseException { FormDef formDef = parse(r("form2.xml")); assertEquals("My Survey", formDef.getTitle()); assertEquals(3, formDef.getChildren().size()); @@ -111,7 +111,7 @@ public void parsesForm2() throws IOException { } @Test - public void spacesBetweenOutputs_areRespected() throws IOException { + public void spacesBetweenOutputs_areRespected() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("spaces-outputs", html( head( model( @@ -136,20 +136,20 @@ public void spacesBetweenOutputs_areRespected() throws IOException { } @Test - public void parsesSecondaryInstanceForm() throws IOException { + public void parsesSecondaryInstanceForm() throws IOException, XFormParser.ParseException { FormDef formDef = parse(SECONDARY_INSTANCE_XML); assertEquals("Form with secondary instance", formDef.getTitle()); } @Test - public void parsesSecondaryInstanceForm2() throws IOException { + public void parsesSecondaryInstanceForm2() throws IOException, XFormParser.ParseException { Path formName = r("internal_select_10.xml"); FormDef formDef = parse(formName); assertEquals("internal select 10", formDef.getTitle()); } @Test - public void parsesLastSavedInstanceWithNullSrc() throws IOException { + public void parsesLastSavedInstanceWithNullSrc() throws IOException, XFormParser.ParseException { Path formName = r("last-saved-blank.xml"); FormDef formDef = parse(formName, null); assertEquals("Form with last-saved instance (blank)", formDef.getTitle()); @@ -160,7 +160,7 @@ public void parsesLastSavedInstanceWithNullSrc() throws IOException { } @Test - public void parsesLastSavedInstanceWithFilledForm() throws IOException { + public void parsesLastSavedInstanceWithFilledForm() throws IOException, XFormParser.ParseException { Path formName = r("last-saved-blank.xml"); Path lastSavedSubmissionDirectory = r("last-saved-filled.xml").toAbsolutePath().getParent(); ReferenceManagerTestUtils.setUpSimpleReferenceManager(lastSavedSubmissionDirectory, "file"); @@ -179,7 +179,7 @@ public void parsesLastSavedInstanceWithFilledForm() throws IOException { } @Test - public void multipleInstancesFormSavesAndRestores() throws IOException, DeserializationException { + public void multipleInstancesFormSavesAndRestores() throws IOException, DeserializationException, XFormParser.ParseException { FormDef originalFormDef = parse(r("Simpler_Cascading_Select_Form.xml")); Path serializedForm = getSerializedFormPath(originalFormDef); @@ -193,7 +193,7 @@ public void multipleInstancesFormSavesAndRestores() throws IOException, Deserial * see https://github.com/getodk/javarosa/issues/245 why this is needed */ @Test - public void rangeFormSavesAndRestores() throws IOException, DeserializationException { + public void rangeFormSavesAndRestores() throws IOException, DeserializationException, XFormParser.ParseException { FormDef originalFormDef = parse(r("range-form.xml")); Path serializedForm = getSerializedFormPath(originalFormDef); @@ -203,7 +203,7 @@ public void rangeFormSavesAndRestores() throws IOException, DeserializationExcep } @Test - public void parsesRankForm() throws IOException { + public void parsesRankForm() throws IOException, XFormParser.ParseException { FormDef formDef = parse(r("rank-form.xml")); assertEquals(formDef.getTitle(), "Rank Form"); assertEquals(1, formDef.getChildren().size()); @@ -212,7 +212,7 @@ public void parsesRankForm() throws IOException { } @Test - public void parsesRangeForm() throws IOException { + public void parsesRangeForm() throws IOException, XFormParser.ParseException { FormDef formDef = parse(r("range-form.xml")); RangeQuestion question = (RangeQuestion) formDef.getChild(0); assertEquals(CONTROL_RANGE, question.getControlType()); @@ -222,14 +222,14 @@ public void parsesRangeForm() throws IOException { } @Test(expected = XFormParseException.class) - public void throwsParseExceptionOnBadRangeForm() throws IOException { + public void throwsParseExceptionOnBadRangeForm() throws IOException, XFormParser.ParseException { parse(r("bad-range-form.xml")); } @Rule public ExpectedException exceptionRule = ExpectedException.none(); @Test - public void throwsExceptionOnEmptySelect() throws IOException { + public void throwsExceptionOnEmptySelect() throws IOException, XFormParser.ParseException { exceptionRule.expect(XFormParseException.class); exceptionRule.expectMessage("Select question 'First' has no choices"); @@ -238,21 +238,21 @@ public void throwsExceptionOnEmptySelect() throws IOException { } @Test - public void formWithCountNonEmptyFunc_ShouldNotThrowException() throws IOException { + public void formWithCountNonEmptyFunc_ShouldNotThrowException() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("countNonEmptyForm.xml"); assertThat(scenario.answerOf("/test/count_value"), is(intAnswer(4))); assertThat(scenario.answerOf("/test/count_non_empty_value"), is(intAnswer(2))); } @Test - public void parsesMetaNamespaceForm() throws IOException { + public void parsesMetaNamespaceForm() throws IOException, XFormParser.ParseException { FormDef formDef = parse(r("meta-namespace-form.xml")); assertEquals(formDef.getTitle(), "Namespace for Metadata"); assertNoParseErrors(formDef); } @Test - public void serializeAndRestoreMetaNamespaceFormInstance() throws IOException { + public void serializeAndRestoreMetaNamespaceFormInstance() throws IOException, XFormParser.ParseException { // Given FormDef formDef = parse(r("meta-namespace-form.xml")); assertEquals(formDef.getTitle(), "Namespace for Metadata"); @@ -311,7 +311,7 @@ public void serializeAndRestoreMetaNamespaceFormInstance() throws IOException { } @Test - public void parseFormWithTemplateRepeat() throws IOException { + public void parseFormWithTemplateRepeat() throws IOException, XFormParser.ParseException { // Given & When FormDef formDef = parse(r("template-repeat.xml")); @@ -321,7 +321,7 @@ public void parseFormWithTemplateRepeat() throws IOException { } @Test - public void parseIMCIbyDTreeForm() throws IOException { + public void parseIMCIbyDTreeForm() throws IOException, XFormParser.ParseException { // Given & When FormDef formDef = parse(r("eIMCI-by-D-Tree.xml")); @@ -331,7 +331,7 @@ public void parseIMCIbyDTreeForm() throws IOException { } @Test - public void parseFormWithSubmissionElement() throws IOException { + public void parseFormWithSubmissionElement() throws IOException, XFormParser.ParseException { // Given & When FormDef formDef = parse(r("submission-element.xml")); @@ -352,12 +352,12 @@ public void parseFormWithSubmissionElement() throws IOException { * this is not mandated by the specs but has been implemented this way to keep parsing simpler. */ @Test(expected = RuntimeException.class) - public void parseFormWithBodyBeforeModel() throws IOException { + public void parseFormWithBodyBeforeModel() throws IOException, XFormParser.ParseException { parse(r("body-before-model.xml")); } @Test - public void parseFormWithTwoModels() throws IOException { + public void parseFormWithTwoModels() throws IOException, XFormParser.ParseException { // Given & When FormDef formDef = parse(r("two-models.xml")); @@ -380,7 +380,7 @@ public void parseFormWithTwoModels() throws IOException { } @Test - public void parseFormWithSetValueAction() throws IOException { + public void parseFormWithSetValueAction() throws IOException, XFormParser.ParseException { // Given & When FormDef formDef = parse(r("form-with-setvalue-action.xml")); @@ -399,7 +399,7 @@ public void parseFormWithSetValueAction() throws IOException { } @Test - public void parseGroupWithNodesetAttrForm() throws IOException { + public void parseGroupWithNodesetAttrForm() throws IOException, XFormParser.ParseException { // Given & When FormDef formDef = parse(r("group-with-nodeset-attr.xml")); @@ -422,7 +422,7 @@ public void parseGroupWithNodesetAttrForm() throws IOException { } @Test - public void parseGroupWithRefAttrForm() throws IOException, XPathSyntaxException { + public void parseGroupWithRefAttrForm() throws IOException, XPathSyntaxException, XFormParser.ParseException { // Given & When FormDef formDef = parse(r("group-with-ref-attr.xml")); @@ -458,7 +458,7 @@ public void parseGroupWithRefAttrForm() throws IOException, XPathSyntaxException } @Test - public void testSetValueWithStrings() throws IOException { + public void testSetValueWithStrings() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("default_test.xml"); assertEquals("string-value", scenario.getAnswerNode("/data/string_val").getValue().getValue().toString()); diff --git a/src/test/java/org/javarosa/xpath/expr/DigestTest.java b/src/test/java/org/javarosa/xpath/expr/DigestTest.java index d7ed952bc..556aed119 100644 --- a/src/test/java/org/javarosa/xpath/expr/DigestTest.java +++ b/src/test/java/org/javarosa/xpath/expr/DigestTest.java @@ -39,6 +39,7 @@ import java.io.IOException; import java.util.Arrays; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -85,7 +86,7 @@ public void generates_a_digest() { } @Test - public void digestFunction_acceptsDynamicParameters() throws IOException { + public void digestFunction_acceptsDynamicParameters() throws IOException, XFormParser.ParseException { Scenario scenario = Scenario.init("Some form", html( head( title("Digest form"), diff --git a/src/test/java/org/javarosa/xpath/expr/XPathFuncExprRandomizeTest.java b/src/test/java/org/javarosa/xpath/expr/XPathFuncExprRandomizeTest.java index 2be018f27..2a6832093 100644 --- a/src/test/java/org/javarosa/xpath/expr/XPathFuncExprRandomizeTest.java +++ b/src/test/java/org/javarosa/xpath/expr/XPathFuncExprRandomizeTest.java @@ -45,6 +45,7 @@ import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.form.api.FormEntryPrompt; import org.javarosa.model.xform.XFormsModule; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.Test; @@ -53,7 +54,7 @@ public class XPathFuncExprRandomizeTest { private FormDef formDef; @Before - public void setUp() { + public void setUp() throws XFormParser.ParseException { // Take a look to the form file: // - The first pair of fields use randomize without a seed. // - The second pair of fields use randomize with a seed. diff --git a/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentFieldRefTest.java b/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentFieldRefTest.java index c84766b4c..5514b8ebf 100644 --- a/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentFieldRefTest.java +++ b/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentFieldRefTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertThat; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.Test; @@ -27,7 +28,7 @@ public class XPathPathExprCurrentFieldRefTest { private Scenario scenario; @Before - public void setUp() { + public void setUp() throws XFormParser.ParseException { scenario = Scenario.init("relative-current-ref-field-ref.xml"); } diff --git a/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentGroupCountRefTest.java b/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentGroupCountRefTest.java index 473b653c5..9ebfd16e8 100644 --- a/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentGroupCountRefTest.java +++ b/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentGroupCountRefTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertThat; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.Test; @@ -27,7 +28,7 @@ public class XPathPathExprCurrentGroupCountRefTest { private Scenario scenario; @Before - public void setUp() { + public void setUp() throws XFormParser.ParseException { scenario = Scenario.init("relative-current-ref-group-count-ref.xml"); } diff --git a/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentTest.java b/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentTest.java index 1b68a74df..138873359 100644 --- a/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentTest.java +++ b/src/test/java/org/javarosa/xpath/expr/XPathPathExprCurrentTest.java @@ -24,6 +24,7 @@ import java.util.List; import org.javarosa.core.model.SelectChoice; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.junit.Before; import org.junit.Test; @@ -31,7 +32,7 @@ public class XPathPathExprCurrentTest { private Scenario scenario; @Before - public void setUp() { + public void setUp() throws XFormParser.ParseException { scenario = Scenario.init("relative-current-ref.xml"); } From 9b34f4c5552debc38dd8bef0be0ad185ae91d7c9 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 14:28:48 +0100 Subject: [PATCH 34/36] Remove IOException from parse() method --- .../benchmarks/ChildVaccinationBenchmark.java | 3 +- ...efCacheExternal2ndryInstanceBenchmark.java | 3 +- ...efCacheInternal2ndryInstanceBenchmark.java | 3 +- .../benchmarks/FormDefValidateBenchmark.java | 3 +- .../FormEntryControllerAnswerQuestion.java | 3 +- ...rHelperParseExternalInstanceBenchmark.java | 3 +- ...rHelperParseInternalInstanceBenchmark.java | 3 +- ...arseInternalInstanceMinifiedBenchmark.java | 3 +- .../benchmarks/PopulateTreeNodeBenchmark.java | 2 +- .../javarosa/benchmarks/WhoVaBenchmark.java | 3 +- .../core/model/CreateRepeatDagBenchmark.java | 11 +-- .../core/model/DeleteRepeatDagBenchmark.java | 3 +- .../UnrecognizedEntityVersionException.java | 4 +- .../internal/EntityFormParseProcessor.java | 3 +- .../javarosa/xform/parse/IElementHandler.java | 2 +- .../org/javarosa/xform/parse/XFormParser.java | 77 +++++++++++-------- .../org/javarosa/xform/util/XFormUtils.java | 4 +- 17 files changed, 78 insertions(+), 55 deletions(-) diff --git a/src/jmh/java/org/javarosa/benchmarks/ChildVaccinationBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/ChildVaccinationBenchmark.java index 7f9a2c763..bc2b6b7fe 100644 --- a/src/jmh/java/org/javarosa/benchmarks/ChildVaccinationBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/ChildVaccinationBenchmark.java @@ -38,6 +38,7 @@ import java.util.function.Consumer; import org.javarosa.core.model.instance.TreeReference; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -65,7 +66,7 @@ public static class ChildVaccinationState { private List endOfVisitRefs; @Setup(Level.Trial) - public void initialize() { + public void initialize() throws XFormParser.ParseException { Path formFile = prepareAssets("child_vaccination_VOL_tool_v12.xml").resolve("child_vaccination_VOL_tool_v12.xml"); vaccinationPenta1Ref = getRef("/data/household/child_repeat/penta1"); vaccinationPenta3Ref = getRef("/data/household/child_repeat/penta3"); diff --git a/src/jmh/java/org/javarosa/benchmarks/FormDefCacheExternal2ndryInstanceBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/FormDefCacheExternal2ndryInstanceBenchmark.java index f1984ac10..00fd0df19 100644 --- a/src/jmh/java/org/javarosa/benchmarks/FormDefCacheExternal2ndryInstanceBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/FormDefCacheExternal2ndryInstanceBenchmark.java @@ -7,6 +7,7 @@ import org.javarosa.core.util.JavaRosaCoreModule; import org.javarosa.model.xform.XFormsModule; import org.javarosa.xform.parse.FormParserHelper; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -33,7 +34,7 @@ public static class FormDefCacheState { @Setup(Level.Trial) public void - initialize() throws IOException { + initialize() throws IOException, XFormParser.ParseException { resourcePath = BenchmarkUtils.getNigeriaWardsXMLWithExternal2ndryInstance(); formDef = FormParserHelper.parse(resourcePath); cachePath = getCachePath().toString(); diff --git a/src/jmh/java/org/javarosa/benchmarks/FormDefCacheInternal2ndryInstanceBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/FormDefCacheInternal2ndryInstanceBenchmark.java index d83097e2c..b603f7295 100644 --- a/src/jmh/java/org/javarosa/benchmarks/FormDefCacheInternal2ndryInstanceBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/FormDefCacheInternal2ndryInstanceBenchmark.java @@ -7,6 +7,7 @@ import org.javarosa.core.util.JavaRosaCoreModule; import org.javarosa.model.xform.XFormsModule; import org.javarosa.xform.parse.FormParserHelper; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -32,7 +33,7 @@ public static class FormDefCacheState { FormDef formDef; @Setup(Level.Trial) public void - initialize() throws IOException { + initialize() throws IOException, XFormParser.ParseException { resourcePath = BenchmarkUtils.getNigeriaWardsXMLWithExternal2ndryInstance(); formDef = FormParserHelper.parse(resourcePath); cachePath = getCachePath().toString(); diff --git a/src/jmh/java/org/javarosa/benchmarks/FormDefValidateBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/FormDefValidateBenchmark.java index f5a01455c..30af11ecb 100644 --- a/src/jmh/java/org/javarosa/benchmarks/FormDefValidateBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/FormDefValidateBenchmark.java @@ -15,6 +15,7 @@ import org.javarosa.form.api.FormEntryModel; import org.javarosa.form.api.FormEntryPrompt; import org.javarosa.xform.parse.FormParserHelper; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -32,7 +33,7 @@ public static class FormDefValidateState { FormDef formDef; @Setup(Level.Trial) - public void initialize() throws IOException { + public void initialize() throws IOException, XFormParser.ParseException { Path resourcePath = BenchmarkUtils.getNigeriaWardsXMLWithExternal2ndryInstance(); formDef = FormParserHelper.parse(resourcePath); FormEntryModel formEntryModel = new FormEntryModel(formDef); diff --git a/src/jmh/java/org/javarosa/benchmarks/FormEntryControllerAnswerQuestion.java b/src/jmh/java/org/javarosa/benchmarks/FormEntryControllerAnswerQuestion.java index 325ac8cab..24dfdf91b 100644 --- a/src/jmh/java/org/javarosa/benchmarks/FormEntryControllerAnswerQuestion.java +++ b/src/jmh/java/org/javarosa/benchmarks/FormEntryControllerAnswerQuestion.java @@ -16,6 +16,7 @@ import org.javarosa.form.api.FormEntryModel; import org.javarosa.form.api.FormEntryPrompt; import org.javarosa.xform.parse.FormParserHelper; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -33,7 +34,7 @@ public static class FormControllerAnswerQuestionState { FormEntryModel formEntryModel; @Setup(Level.Trial) - public void initialize() throws IOException { + public void initialize() throws IOException, XFormParser.ParseException { Path formFile = BenchmarkUtils.getNigeriaWardsXMLWithExternal2ndryInstance(); FormDef formDef = FormParserHelper.parse(formFile); formEntryModel = new FormEntryModel(formDef); diff --git a/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseExternalInstanceBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseExternalInstanceBenchmark.java index 4450e2c39..800282415 100644 --- a/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseExternalInstanceBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseExternalInstanceBenchmark.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.nio.file.Path; import org.javarosa.xform.parse.FormParserHelper; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -30,7 +31,7 @@ public void initialize() { @Benchmark public void - benchmarkParseExternalSecondaryInstance(FormParserHelperParseExternalInstanceBenchmarkState state, Blackhole bh) throws IOException { + benchmarkParseExternalSecondaryInstance(FormParserHelperParseExternalInstanceBenchmarkState state, Blackhole bh) throws IOException, XFormParser.ParseException { bh.consume(FormParserHelper.parse(state.xFormFilePath)); } diff --git a/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseInternalInstanceBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseInternalInstanceBenchmark.java index a0c242f10..f71caf4e8 100644 --- a/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseInternalInstanceBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseInternalInstanceBenchmark.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.nio.file.Path; import org.javarosa.xform.parse.FormParserHelper; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -30,7 +31,7 @@ public void initialize() { @Benchmark public void - benchmarkParseInternalSecondaryInstanceForm(FormParserHelperParseInternalInstanceBenchmarkState state, Blackhole bh) throws IOException { + benchmarkParseInternalSecondaryInstanceForm(FormParserHelperParseInternalInstanceBenchmarkState state, Blackhole bh) throws IOException, XFormParser.ParseException { bh.consume(FormParserHelper.parse(state.xFormFilePath)); } } diff --git a/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseInternalInstanceMinifiedBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseInternalInstanceMinifiedBenchmark.java index a4b749d32..9cf6802f3 100644 --- a/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseInternalInstanceMinifiedBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/FormParserHelperParseInternalInstanceMinifiedBenchmark.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.nio.file.Path; import org.javarosa.xform.parse.FormParserHelper; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -30,7 +31,7 @@ public void initialize() { @Benchmark public void - benchmarkParseInternalInstanceFormMinified(FormParserHelperParseInternalInstanceMinifiedBenchmarkState state, Blackhole bh) throws IOException { + benchmarkParseInternalInstanceFormMinified(FormParserHelperParseInternalInstanceMinifiedBenchmarkState state, Blackhole bh) throws IOException, XFormParser.ParseException { bh.consume(FormParserHelper.parse(state.xFormFilePath)); } diff --git a/src/jmh/java/org/javarosa/benchmarks/PopulateTreeNodeBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/PopulateTreeNodeBenchmark.java index 615f338df..1fdcb83fc 100644 --- a/src/jmh/java/org/javarosa/benchmarks/PopulateTreeNodeBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/PopulateTreeNodeBenchmark.java @@ -34,7 +34,7 @@ public static class TreeElementPopulateState { private FormDef formDef; @Setup(Level.Trial) - public void initialize() throws IOException { + public void initialize() throws IOException, XFormParser.ParseException { Path assetsDir = prepareAssets("nigeria_wards_external_combined.xml", "wards.xml", "lgas.xml", "populate-nodes-attributes-instance.xml"); Path formFile = assetsDir.resolve("nigeria_wards_external_combined.xml"); Path submissionFile = assetsDir.resolve("populate-nodes-attributes-instance.xml"); diff --git a/src/jmh/java/org/javarosa/benchmarks/WhoVaBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/WhoVaBenchmark.java index 43310c72f..1d2bcc942 100644 --- a/src/jmh/java/org/javarosa/benchmarks/WhoVaBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/WhoVaBenchmark.java @@ -24,6 +24,7 @@ import java.time.LocalDate; import java.util.stream.IntStream; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Scope; @@ -42,7 +43,7 @@ public static class WhoVaState { public Scenario scenario; @Setup(Level.Trial) - public void initialize() { + public void initialize() throws XFormParser.ParseException { scenario = init(prepareAssets("whova_form.xml").resolve("whova_form.xml")); } } diff --git a/src/jmh/java/org/javarosa/benchmarks/core/model/CreateRepeatDagBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/core/model/CreateRepeatDagBenchmark.java index 160ec62a4..ea166bd4e 100644 --- a/src/jmh/java/org/javarosa/benchmarks/core/model/CreateRepeatDagBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/core/model/CreateRepeatDagBenchmark.java @@ -15,6 +15,7 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; @@ -46,7 +47,7 @@ public static class ExecutionPlan { Scenario expressionInsideWithPositionCallScenario; @Setup(Level.Invocation) - public void setUp() throws IOException { + public void setUp() throws IOException, XFormParser.ParseException { expressionInsideScenario = getExpressionInsideScenario(); expressionInsideWithRefOutsideScenario = getExpressionInsideWithRefOutsideScenario(); sumExpressionOutsideScenario = getSumExpressionOutsideScenario(); @@ -95,7 +96,7 @@ public void createRepeat_withPositionExpression(ExecutionPlan plan, Blackhole bh }); } - static Scenario getExpressionInsideScenario() throws IOException { + static Scenario getExpressionInsideScenario() throws IOException, XFormParser.ParseException { return Scenario.init("Repeat with expression inside", html( head( title("Repeat with expression inside"), @@ -117,7 +118,7 @@ static Scenario getExpressionInsideScenario() throws IOException { ))); } - static Scenario getExpressionInsideWithRefOutsideScenario() throws IOException { + static Scenario getExpressionInsideWithRefOutsideScenario() throws IOException, XFormParser.ParseException { return Scenario.init("Repeat with expression inside referencing outside", html( head( title("Repeat with expression inside referencing outside"), @@ -139,7 +140,7 @@ static Scenario getExpressionInsideWithRefOutsideScenario() throws IOException { ))); } - static Scenario getSumExpressionOutsideScenario() throws IOException { + static Scenario getSumExpressionOutsideScenario() throws IOException, XFormParser.ParseException { return Scenario.init("Repeat with sum expression outside", html( head( title("Repeat with sum expression outside"), @@ -161,7 +162,7 @@ static Scenario getSumExpressionOutsideScenario() throws IOException { ))); } - static Scenario getExpressionInsideWithPositionCallScenario() throws IOException { + static Scenario getExpressionInsideWithPositionCallScenario() throws IOException, XFormParser.ParseException { return Scenario.init("Repeat with expression inside referencing outside", html( head( title("Repeat with expression inside referencing outside"), diff --git a/src/jmh/java/org/javarosa/benchmarks/core/model/DeleteRepeatDagBenchmark.java b/src/jmh/java/org/javarosa/benchmarks/core/model/DeleteRepeatDagBenchmark.java index 9cb8f9a1e..3e7f212d6 100644 --- a/src/jmh/java/org/javarosa/benchmarks/core/model/DeleteRepeatDagBenchmark.java +++ b/src/jmh/java/org/javarosa/benchmarks/core/model/DeleteRepeatDagBenchmark.java @@ -9,6 +9,7 @@ import java.io.IOException; import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; @@ -40,7 +41,7 @@ public static class ExecutionPlan { Scenario expressionInsideWithPositionCallScenario; @Setup(Level.Invocation) - public void setUp() throws IOException { + public void setUp() throws IOException, XFormParser.ParseException { expressionInsideScenario = getExpressionInsideScenario(); range(0, repeatCount).forEach(n -> { expressionInsideScenario.next(); diff --git a/src/main/java/org/javarosa/entities/UnrecognizedEntityVersionException.java b/src/main/java/org/javarosa/entities/UnrecognizedEntityVersionException.java index 974fa97e8..e5f9b74ff 100644 --- a/src/main/java/org/javarosa/entities/UnrecognizedEntityVersionException.java +++ b/src/main/java/org/javarosa/entities/UnrecognizedEntityVersionException.java @@ -1,6 +1,6 @@ package org.javarosa.entities; -import org.javarosa.xform.parse.XFormParseException; +import org.javarosa.xform.parse.XFormParser; -public class UnrecognizedEntityVersionException extends XFormParseException { +public class UnrecognizedEntityVersionException extends XFormParser.ParseException { } diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index e93ac551f..4cefb8c4f 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -5,7 +5,6 @@ import org.javarosa.core.model.FormDef; import org.javarosa.entities.UnrecognizedEntityVersionException; import org.javarosa.model.xform.XPathReference; -import org.javarosa.xform.parse.XFormParseException; import org.javarosa.xform.parse.XFormParser; import java.util.ArrayList; @@ -30,7 +29,7 @@ public Set> getModelAttributes() { } @Override - public void processModelAttribute(String name, String value) throws XFormParseException { + public void processModelAttribute(String name, String value) throws XFormParser.ParseException { versionPresent = true; try { diff --git a/src/main/java/org/javarosa/xform/parse/IElementHandler.java b/src/main/java/org/javarosa/xform/parse/IElementHandler.java index d1258745e..004ab4bec 100644 --- a/src/main/java/org/javarosa/xform/parse/IElementHandler.java +++ b/src/main/java/org/javarosa/xform/parse/IElementHandler.java @@ -26,5 +26,5 @@ * */ public interface IElementHandler { - /*Object*/ void handle (XFormParser p, Element e, Object parent); + /*Object*/ void handle (XFormParser p, Element e, Object parent) throws XFormParser.ParseException; } diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 325b84131..9a4e9ccab 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -218,7 +218,7 @@ private static void initProcessingRules() { groupLevelHandlers = new HashMap() {{ put("input", new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { // Attributes that are passed through to additionalAttributes but shouldn't lead to warnings. // These are consistently used by clients but are expected in additionalAttributes for historical // reasons. @@ -228,7 +228,7 @@ public void handle(XFormParser p, Element e, Object parent) { }); put("range", new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseControl((IFormElement) parent, e, CONTROL_RANGE, asList("start", "end", "step") // Prevent warning about unexpected attributes ); @@ -236,49 +236,49 @@ public void handle(XFormParser p, Element e, Object parent) { }); put("secret", new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseControl((IFormElement) parent, e, CONTROL_SECRET); } }); put(SELECT, new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseControl((IFormElement) parent, e, CONTROL_SELECT_MULTI); } }); put(RANK, new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseControl((IFormElement) parent, e, CONTROL_RANK); } }); put(SELECTONE, new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseControl((IFormElement) parent, e, CONTROL_SELECT_ONE); } }); put("group", new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseGroup((IFormElement) parent, e, CONTAINER_GROUP); } }); put("repeat", new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseGroup((IFormElement) parent, e, CONTAINER_REPEAT); } }); put("trigger", new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseControl((IFormElement) parent, e, CONTROL_TRIGGER); } }); //multi-purpose now; need to dig deeper put(XFTAG_UPLOAD, new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseUpload((IFormElement) parent, e, CONTROL_UPLOAD); } }); @@ -296,7 +296,7 @@ public void handle(XFormParser p, Element e, Object parent) { topLevelHandlers = new HashMap() {{ put("model", new IElementHandler() { @Override - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseModel(e); } }); @@ -370,12 +370,12 @@ public XFormParser(Document form, Document instance) { _instDoc = instance; } - public FormDef parse(String lastSavedSrc) throws IOException, ParseException { + public FormDef parse(String lastSavedSrc) throws ParseException { return parse(null, lastSavedSrc); } - public FormDef parse() throws IOException, ParseException { + public FormDef parse() throws ParseException { return parse(null, null); } @@ -384,19 +384,27 @@ public FormDef parse() throws IOException, ParseException { * @param lastSavedSrc The src of the last-saved instance of this form (for auto-filling). If null, * no data will be loaded and the instance will be blank. */ - public FormDef parse(String formXmlSrc, String lastSavedSrc) throws IOException, ParseException { + public FormDef parse(String formXmlSrc, String lastSavedSrc) throws ParseException { if (_f == null) { logger.info("Parsing form..."); if (_xmldoc == null) { - _xmldoc = getXMLDocument(_reader, stringCache); + try { + _xmldoc = getXMLDocument(_reader, stringCache); + } catch (IOException e) { + throw new ParseException("IO Exception during parse! " + e.getMessage()); + } } parseDoc(formXmlSrc, buildNamespacesMap(_xmldoc.getRootElement()), lastSavedSrc); //load in a custom xml instance, if applicable if (_instReader != null) { - loadXmlInstance(_f, _instReader); + try { + loadXmlInstance(_f, _instReader); + } catch (IOException e) { + throw new ParseException("IO Exception during parse! " + e.getMessage()); + } } else if (_instDoc != null) { loadXmlInstance(_f, _instDoc); } @@ -506,7 +514,7 @@ public static Document getXMLDocument(Reader reader, CacheTable stringCa return doc; } - private void parseDoc(String formXmlSrc, Map namespacePrefixesByUri, String lastSavedSrc) { + private void parseDoc(String formXmlSrc, Map namespacePrefixesByUri, String lastSavedSrc) throws ParseException { final StopWatch codeTimer = StopWatch.start(); _f = new FormDef(); _f.setFormXmlPath(formXmlSrc); @@ -620,7 +628,7 @@ private String parseInstanceSrc(Element instance, String lastSavedSrc) { "delHeader" ))); - private void parseElement(Element e, Object parent, HashMap handlers) { + private void parseElement(Element e, Object parent, HashMap handlers) throws ParseException { String name = e.getName(); IElementHandler eh = handlers.get(name); @@ -685,18 +693,18 @@ private void parseMeta(Element e) { } //for ease of parsing, we assume a model comes before the controls, which isn't necessarily mandated by the xforms spec - private void parseModel(Element e) throws XFormParseException { - modelAttributeProcessors.stream().forEach(processor -> { + private void parseModel(Element e) throws XFormParser.ParseException { + for (ModelAttributeProcessor modelAttributeProcessor : modelAttributeProcessors) { for (int i = 0; i < e.getAttributeCount(); i++) { String namespace = e.getAttributeNamespace(i); String name = e.getAttributeName(i); String value = e.getAttributeValue(i); - if (processor.getModelAttributes().contains(new Pair<>(namespace, name))) { - processor.processModelAttribute(name, value); + if (modelAttributeProcessor.getModelAttributes().contains(new Pair<>(namespace, name))) { + modelAttributeProcessor.processModelAttribute(name, value); } } - }); + } List usedAtts = new ArrayList<>(); //no attributes parsed in title. List delayedParseElements = new ArrayList<>(); @@ -769,7 +777,7 @@ private void parseModel(Element e) throws XFormParseException { * event attribute and location in the xform are both valid, and then invokes the more specific * handler that is provided. */ - private void parseAction(Element e, Object parent, IElementHandler specificHandler) { + private void parseAction(Element e, Object parent, IElementHandler specificHandler) throws ParseException { // Check that all events registered to trigger this action are valid events that we support List validEvents = getValidEventNames(e.getAttributeValue(null, EVENT_ATTR)); @@ -954,7 +962,7 @@ private void processAdditionalAttributes(QuestionDef question, Element e, List additionalUsedAtts) { + private QuestionDef parseControl(IFormElement parent, Element e, int controlType, List additionalUsedAtts) throws ParseException { return parseControl(parent, e, controlType, additionalUsedAtts, null); } @@ -1072,7 +1080,7 @@ private QuestionDef parseControl(IFormElement parent, Element e, int controlType * @return a {@link org.javarosa.core.model.QuestionDef} representing the form control element */ private QuestionDef parseControl(IFormElement parent, Element e, int controlType, List additionalUsedAtts, - List passedThroughAtts) { + List passedThroughAtts) throws ParseException { final QuestionDef question = questionForControlType(controlType); question.setID(serialQuestionID++); //until we come up with a better scheme @@ -1567,7 +1575,7 @@ else if (seedStr != null) } - private void parseGroup(IFormElement parent, Element e, int groupType) { + private void parseGroup(IFormElement parent, Element e, int groupType) throws ParseException { GroupDef group = new GroupDef(); group.setID(serialQuestionID++); //until we come up with a better scheme IDataReference dataRef = null; @@ -2258,7 +2266,7 @@ public static void registerActionHandler(String name, final IElementHandler spec actionHandlers.put( name, new IElementHandler() { - public void handle(XFormParser p, Element e, Object parent) { + public void handle(XFormParser p, Element e, Object parent) throws ParseException { p.parseAction(e, parent, specificHandler); } } @@ -2438,11 +2446,18 @@ public interface ModelAttributeProcessor extends Processor { Set> getModelAttributes(); - void processModelAttribute(String name, String value) throws XFormParseException; + void processModelAttribute(String name, String value) throws ParseException; } public static class ParseException extends Exception { + public ParseException() { + super(); + } + + public ParseException(String message) { + super(message); + } } public static class MissingModelAttributeException extends ParseException { diff --git a/src/main/java/org/javarosa/xform/util/XFormUtils.java b/src/main/java/org/javarosa/xform/util/XFormUtils.java index a7ec97ff5..460d886bc 100644 --- a/src/main/java/org/javarosa/xform/util/XFormUtils.java +++ b/src/main/java/org/javarosa/xform/util/XFormUtils.java @@ -85,7 +85,7 @@ public static FormDef getFormFromInputStream(InputStream is) throws XFormParseEx * @param lastSavedSrc The src of the last-saved instance of this form (for auto-filling). If null, * no data will be loaded and the instance will be blank. */ - public static FormDef getFormFromInputStream(InputStream is, String lastSavedSrc) throws XFormParseException, XFormParser.ParseException { + public static FormDef getFormFromInputStream(InputStream is, String lastSavedSrc) throws XFormParser.ParseException { InputStreamReader isr = null; try { try { @@ -96,8 +96,6 @@ public static FormDef getFormFromInputStream(InputStream is, String lastSavedSrc XFormParser xFormParser = _factory.getXFormParser(isr); return xFormParser.parse(lastSavedSrc); - } catch(IOException e) { - throw new XFormParseException("IO Exception during parse! " + e.getMessage()); } finally { try { if (isr != null) { From c20aa0b7c11e71b1835859b81d2447cd0af8e655 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 3 Oct 2022 14:49:33 +0100 Subject: [PATCH 35/36] Correct version --- .../javarosa/entities/internal/EntityFormParseProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java index 4cefb8c4f..cbda00c9e 100644 --- a/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java +++ b/src/main/java/org/javarosa/entities/internal/EntityFormParseProcessor.java @@ -15,7 +15,7 @@ public class EntityFormParseProcessor implements XFormParser.BindAttributeProcessor, XFormParser.FormDefProcessor, XFormParser.ModelAttributeProcessor { private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities"; - public static final String SUPPORTED_VERSION = "v2022.1"; + public static final String SUPPORTED_VERSION = "2022.1"; private final List> saveTos = new ArrayList<>(); private boolean versionPresent; From 9afa909d06fac022c79b8cb0d0ad3f191d9719dc Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Tue, 4 Oct 2022 09:26:50 +0100 Subject: [PATCH 36/36] Check that parsing works with patch versions --- .../internal/EntityFormParseProcessorTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java index 93451c626..e7c691fb7 100644 --- a/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java +++ b/src/test/java/org/javarosa/entities/internal/EntityFormParseProcessorTest.java @@ -9,7 +9,6 @@ import org.junit.Test; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStreamReader; import static java.util.Arrays.asList; @@ -18,6 +17,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.javarosa.core.util.BindBuilderXFormsElement.bind; import static org.javarosa.core.util.XFormsElement.body; import static org.javarosa.core.util.XFormsElement.head; @@ -31,7 +31,7 @@ public class EntityFormParseProcessorTest { @Test - public void whenVersionIsMissing_parsesWithoutError() throws IOException, XFormParser.ParseException { + public void whenVersionIsMissing_parsesWithoutError() throws XFormParser.ParseException { XFormsElement form = XFormsElement.html( head( title("Non entity form"), @@ -100,7 +100,7 @@ public void whenVersionIsMissing_andThereIsAnEntityElement_throwsException() { } @Test(expected = UnrecognizedEntityVersionException.class) - public void whenVersionIsNotRecognized_throwsException() throws IOException, XFormParser.ParseException { + public void whenVersionIsNotRecognized_throwsException() throws XFormParser.ParseException { XFormsElement form = XFormsElement.html( asList( new Pair<>("entities", "http://www.opendatakit.org/xforms/entities") @@ -133,7 +133,7 @@ public void whenVersionIsNotRecognized_throwsException() throws IOException, XFo } @Test - public void whenVersionIsNewPatch_doesNotThrowException() throws IOException, XFormParser.ParseException { + public void whenVersionIsNewPatch_parsesCorrectly() throws XFormParser.ParseException { String newPatchVersion = EntityFormParseProcessor.SUPPORTED_VERSION + ".12"; XFormsElement form = XFormsElement.html( @@ -164,11 +164,13 @@ public void whenVersionIsNewPatch_doesNotThrowException() throws IOException, XF EntityFormParseProcessor processor = new EntityFormParseProcessor(); XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes()))); parser.addProcessor(processor); - parser.parse(null); + + FormDef formDef = parser.parse(null); + assertThat(formDef.getExtras().get(EntityFormExtra.class), notNullValue()); } @Test - public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException, XFormParser.ParseException { + public void saveTosWithIncorrectNamespaceAreIgnored() throws XFormParser.ParseException { XFormsElement form = XFormsElement.html( asList( new Pair<>("correct", "http://www.opendatakit.org/xforms/entities"),