From 8a4427636bb07d94ac81bf30eff0f63f646db18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 26 Oct 2022 11:40:56 -0700 Subject: [PATCH 1/4] Consider namespace when comparing node names --- .../utils/TreeElementChildrenList.java | 13 +++--- .../instance/TreeElementNamespacedTest.java | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java 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 66d4ee365..8a95513e8 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 @@ -1,14 +1,13 @@ package org.javarosa.core.model.instance.utils; -import org.javarosa.core.model.instance.TreeElement; -import org.javarosa.core.model.instance.TreeReference; -import org.jetbrains.annotations.Nullable; +import static org.javarosa.core.model.instance.TreeReference.DEFAULT_MULTIPLICITY; import java.util.ArrayList; import java.util.Iterator; import java.util.List; - -import static org.javarosa.core.model.instance.TreeReference.DEFAULT_MULTIPLICITY; +import org.javarosa.core.model.instance.TreeElement; +import org.javarosa.core.model.instance.TreeReference; +import org.jetbrains.annotations.Nullable; /** * A collection of {@link TreeElement} children. They are stored in an {@link ArrayList}. @@ -100,7 +99,7 @@ private void checkAndSetSameNameAndNormalMult(String name, int mult) { */ private boolean sameNameAndNormalMult(String name, int mult) { return allHaveSameNameAndNormalMult && mult >= 0 && - (children.isEmpty() || name.equals(children.get(0).getName())); + (children.isEmpty() || TreeElementNameComparator.elementMatchesName(children.get(0), name)); } /** @@ -190,7 +189,7 @@ private ElementAndLoc getChildAndLoc(String name, int multiplicity) { for (int i = 0; i < children.size(); i++) { TreeElement child = children.get(i); - if (name.equals(child.getName()) && child.getMult() == multiplicity) { + if (TreeElementNameComparator.elementMatchesName(child, name) && child.getMult() == multiplicity) { return new ElementAndLoc(child, i); } } diff --git a/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java b/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java new file mode 100644 index 000000000..c4ab745e0 --- /dev/null +++ b/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java @@ -0,0 +1,46 @@ +package org.javarosa.core.model.instance; + +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.javarosa.core.test.AnswerDataMatchers.stringAnswer; +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.html; +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; + +import java.io.IOException; +import kotlin.Pair; +import org.javarosa.core.test.Scenario; +import org.javarosa.xform.parse.XFormParser; +import org.junit.Test; + +public class TreeElementNamespacedTest { + + @Test + public void namespacedElement_canBeQueried() throws IOException, XFormParser.ParseException { + Scenario scenario = Scenario.init("Entity creation", html( + asList(new Pair<>("example", "http://example.fake")), + head( + title("Entity creation"), + model( + mainInstance(t("data id=\"entity-creation\"", + t("question"), + t("example:calculate") + )), + bind("/data/example:calculate").calculate("/data/question") + ) + ), + body( + input("/data/question") + ))); + + scenario.answer("/data/question", "foo"); + assertThat(scenario.answerOf("/data/example:calculate"), is(stringAnswer("foo"))); + } +} From 6ab1370f0933429e2b6019fa3345de342ae32bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 26 Oct 2022 12:03:12 -0700 Subject: [PATCH 2/4] Serialize and deserialize namespace prefix An alternative would be to serialize the namespace map. Currently this is only available at parse time so it would be a bigger change. We generally don't expect many namespaced instance elements so this is a pragmatic approach for now. If we find namespace usage takes off, we can reconsider. --- .../core/model/instance/TreeElement.java | 4 +++ .../instance/TreeElementNamespacedTest.java | 30 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) 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 48bdad219..646ff5780 100644 --- a/src/main/java/org/javarosa/core/model/instance/TreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/TreeElement.java @@ -677,6 +677,8 @@ public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOExcep bindAttributes = ExtUtil.readAttributes(in, this); attributes = ExtUtil.readAttributes(in, this); + + namespacePrefix = ExtUtil.nullIfEmpty(ExtUtil.readString(in)); } @Override @@ -731,6 +733,8 @@ public void writeExternal(DataOutputStream out) throws IOException { ExtUtil.writeAttributes(out, bindAttributes); ExtUtil.writeAttributes(out, attributes); + + ExtUtil.writeString(out, ExtUtil.emptyIfNull(namespacePrefix)); } //rebuilding a node from an imported instance diff --git a/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java b/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java index c4ab745e0..4b9f3b9c9 100644 --- a/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java +++ b/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java @@ -17,6 +17,7 @@ import java.io.IOException; import kotlin.Pair; import org.javarosa.core.test.Scenario; +import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.xform.parse.XFormParser; import org.junit.Test; @@ -24,10 +25,10 @@ public class TreeElementNamespacedTest { @Test public void namespacedElement_canBeQueried() throws IOException, XFormParser.ParseException { - Scenario scenario = Scenario.init("Entity creation", html( + Scenario scenario = Scenario.init("Namespaced element", html( asList(new Pair<>("example", "http://example.fake")), head( - title("Entity creation"), + title("Namespaced element"), model( mainInstance(t("data id=\"entity-creation\"", t("question"), @@ -43,4 +44,29 @@ public void namespacedElement_canBeQueried() throws IOException, XFormParser.Par scenario.answer("/data/question", "foo"); assertThat(scenario.answerOf("/data/example:calculate"), is(stringAnswer("foo"))); } + + @Test + public void namespacedElement_canBeQueried_afterSerializationDeserialization() throws IOException, XFormParser.ParseException, DeserializationException { + Scenario scenario = Scenario.init("Namespaced element", html( + asList(new Pair<>("example", "http://example.fake")), + head( + title("Namespaced element"), + model( + mainInstance(t("data id=\"entity-creation\"", + t("question"), + t("example:calculate") + )), + bind("/data/example:calculate").calculate("/data/question") + ) + ), + body( + input("/data/question") + ))); + + scenario.answer("/data/question", "foo"); + + Scenario cachedScenario = scenario.serializeAndDeserializeForm(); + + assertThat(cachedScenario.answerOf("/data/example:calculate"), is(stringAnswer("foo"))); + } } From 9c7109b3800a76e9e3b93efc2d5bbc29b8d4614a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 26 Oct 2022 14:55:52 -0700 Subject: [PATCH 3/4] Fix getAttribute with namespaced attributes There was a test for this but the assertion was wrong. Fixed that and added more cases. Ideally the attribute namespace would never be a blank string but I can't figure out where that is being set. --- .../core/model/instance/TreeElement.java | 15 ++++-- .../instance/TreeElementNamespacedTest.java | 48 +++++++++++++++++++ .../model/instance/test/TreeElementTests.java | 2 +- 3 files changed, 61 insertions(+), 4 deletions(-) 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 646ff5780..203fc8a14 100644 --- a/src/main/java/org/javarosa/core/model/instance/TreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/TreeElement.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; +import java.util.Objects; import org.javarosa.core.model.Constants; import org.javarosa.core.model.FormDef; import org.javarosa.core.model.FormElementStateListener; @@ -36,6 +37,7 @@ import org.javarosa.core.model.instance.utils.IAnswerResolver; import org.javarosa.core.model.instance.utils.ITreeVisitor; import org.javarosa.core.model.instance.utils.TreeElementChildrenList; +import org.javarosa.core.model.instance.utils.TreeElementNameComparator; import org.javarosa.core.model.util.restorable.RestoreUtils; import org.javarosa.core.util.DataUtil; import org.javarosa.core.util.externalizable.DeserializationException; @@ -137,7 +139,7 @@ public TreeElement(String name, int multiplicity) { public static TreeElement constructAttributeElement(String namespace, String name, String value) { TreeElement element = new TreeElement(name); element.setIsAttribute(true); - element.namespace = (namespace == null) ? "" : namespace; + element.namespace = namespace; element.multiplicity = TreeReference.INDEX_ATTRIBUTE; element.value = new UncastData(value); return element; @@ -161,8 +163,15 @@ private void setIsAttribute(boolean attribute) { */ public static TreeElement getAttribute(List attributes, String namespace, String name) { for (TreeElement attribute : attributes) { - if(attribute.getName().equals(name) && (namespace == null || namespace.equals(attribute.namespace))) { - return attribute; + if (name.contains(":")) { + if (TreeElementNameComparator.elementMatchesName(attribute, name)) { + return attribute; + } + } else { + if (attribute.getName().equals(name) && (Objects.equals(namespace, attribute.namespace) + || (Objects.equals(attribute.namespace, "") && namespace == null))) { + return attribute; + } } } return null; diff --git a/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java b/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java index 4b9f3b9c9..37d4c2b88 100644 --- a/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java +++ b/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java @@ -3,6 +3,7 @@ import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.javarosa.core.model.instance.TreeReference.INDEX_ATTRIBUTE; import static org.javarosa.core.test.AnswerDataMatchers.stringAnswer; import static org.javarosa.core.util.BindBuilderXFormsElement.bind; import static org.javarosa.core.util.XFormsElement.body; @@ -69,4 +70,51 @@ public void namespacedElement_canBeQueried_afterSerializationDeserialization() t assertThat(cachedScenario.answerOf("/data/example:calculate"), is(stringAnswer("foo"))); } + + @Test + public void getAttribute_getsNamespacedAttribute() { + TreeElement e1 = new TreeElement("a", INDEX_ATTRIBUTE); + TreeElement e2 = new TreeElement("a", INDEX_ATTRIBUTE); + e2.setNamespace("https://fake.fake"); + e2.setNamespacePrefix("example"); + TreeElement result = TreeElement.getAttribute(asList(e1, e2), "https://fake.fake", "a"); + + assertThat(result, is(e2)); + } + + @Test + // This is what happens when evaluating an XPath expression + public void getAttribute_getsNamespacedAttribute_usingPrefix() { + TreeElement e1 = new TreeElement("a", INDEX_ATTRIBUTE); + TreeElement e2 = new TreeElement("a", INDEX_ATTRIBUTE); + e2.setNamespace("https://fake.fake"); + e2.setNamespacePrefix("example"); + TreeElement result = TreeElement.getAttribute(asList(e1, e2), null, "example:a"); + + assertThat(result, is(e2)); + } + + @Test + public void getAttribute_getsDefaultNamespaceAttribute() { + TreeElement e1 = new TreeElement("a", INDEX_ATTRIBUTE); + e1.setNamespace("https://fake.fake"); + e1.setNamespacePrefix("example"); + TreeElement e2 = new TreeElement("a", INDEX_ATTRIBUTE); + TreeElement result = TreeElement.getAttribute(asList(e1, e2), null, "a"); + + assertThat(result, is(e2)); + } + + @Test + // Attributes in the main instance without a custom namespace have empty string namespace + public void getAttribute_getsDefaultNamespaceAttribute_withBlankNamespace() { + TreeElement e1 = new TreeElement("a", INDEX_ATTRIBUTE); + e1.setNamespace("https://fake.fake"); + e1.setNamespacePrefix("example"); + TreeElement e2 = new TreeElement("a", INDEX_ATTRIBUTE); + e2.setNamespace(""); + TreeElement result = TreeElement.getAttribute(asList(e1, e2), null, "a"); + + assertThat(result, is(e2)); + } } 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 e98f40283..678f49981 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 @@ -50,7 +50,7 @@ public void testPopulate_withNodesAttributes() throws IOException, XFormParser.P assertEquals("regular_group", regularGroup.getName()); assertEquals(1, regularGroup.getAttributeCount()); - customAttr1 = regularGroup.getAttribute(null, "custom_attr_1"); + customAttr1 = regularGroup.getAttribute("custom_name_space", "custom_attr_1"); assertNotNull(customAttr1); assertEquals("custom_attr_1", customAttr1.getName()); assertEquals("custom_name_space", customAttr1.getNamespace()); From c1cd379ee186df8ffe41645ca7d385118d639a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Wed, 26 Oct 2022 15:24:48 -0700 Subject: [PATCH 4/4] Save attribute namespace prefixes --- .../core/model/instance/TreeElement.java | 41 +++++---- .../org/javarosa/xform/parse/XFormParser.java | 88 +++++++++---------- .../instance/TreeElementNamespacedTest.java | 42 +++++++++ 3 files changed, 110 insertions(+), 61 deletions(-) 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 203fc8a14..fd2a73973 100644 --- a/src/main/java/org/javarosa/core/model/instance/TreeElement.java +++ b/src/main/java/org/javarosa/core/model/instance/TreeElement.java @@ -127,19 +127,15 @@ public TreeElement(String name, int multiplicity) { attributes = new ArrayList(0); } - /** - * Construct a TreeElement which represents an attribute with the provided - * namespace and name. - * - * @param namespace - if null will be converted to empty string - * @param name - * @param value - * @return A new instance of a TreeElement - */ public static TreeElement constructAttributeElement(String namespace, String name, String value) { + return constructAttributeElement(namespace, null, name, value); + } + + public static TreeElement constructAttributeElement(String namespace, String namespacePrefix, String name, String value) { TreeElement element = new TreeElement(name); element.setIsAttribute(true); - element.namespace = namespace; + element.setNamespace(namespace); + element.setNamespacePrefix(namespacePrefix); element.multiplicity = TreeReference.INDEX_ATTRIBUTE; element.value = new UncastData(value); return element; @@ -176,9 +172,11 @@ public static TreeElement getAttribute(List attributes, String name } return null; } - public static void setAttribute(TreeElement parent, List attrs, String namespace, String name, String value) { + setAttribute(parent, attrs, namespace, null, name, value); + } + public static void setAttribute(TreeElement parent, List attrs, String namespace, String namespacePrefix, String name, String value) { TreeElement attribut = getAttribute(attrs, namespace, name); if ( attribut != null ) { if (value == null) { @@ -193,7 +191,7 @@ public static void setAttribute(TreeElement parent, List attrs, Str if ( value == null ) return; // create an attribute... - TreeElement attr = TreeElement.constructAttributeElement(namespace, name, value); + TreeElement attr = TreeElement.constructAttributeElement(namespace, namespacePrefix, name, value); attr.setParent(parent); attrs.add(attr); @@ -349,7 +347,7 @@ public TreeElement shallowCopy() { newNode.attributes = new ArrayList<>(attributes.size()); for (TreeElement attr : attributes) { - newNode.setAttribute(attr.getNamespace(), attr.getName(), attr.getAttributeValue()); + newNode.setAttribute(attr.getNamespace(), attr.getNamespacePrefix(), attr.getName(), attr.getAttributeValue()); } if (value != null) { @@ -446,7 +444,7 @@ private void setRelevant(boolean relevant, boolean inherited) { public void setBindAttributes(List bindAttributes ) { // create new tree elements for all the bind definitions... for ( TreeElement ref : bindAttributes ) { - setBindAttribute(ref.getNamespace(), ref.getName(), ref.getAttributeValue()); + setBindAttribute(ref.getNamespace(), ref.getNamespacePrefix(), ref.getName(), ref.getAttributeValue()); } } @@ -485,8 +483,8 @@ public String getBindAttributeValue(String namespace, String name) { return element == null ? null: getAttributeValue(element); } - public void setBindAttribute(String namespace, String name, String value) { - setAttribute(this, bindAttributes, namespace, name, value); + public void setBindAttribute(String namespace, String namespacePrefix, String name, String value) { + setAttribute(this, bindAttributes, namespace, namespacePrefix, name, value); } public void setEnabled(boolean enabled) { @@ -565,6 +563,10 @@ public String getAttributeNamespace(int index) { return attributes.get(index).namespace; } + public String getAttributeNamespacePrefix(int index) { + return attributes.get(index).namespacePrefix; + } + @Override public String getAttributeName(int index) { return attributes.get(index).name; @@ -611,6 +613,10 @@ public void setAttribute(String namespace, String name, String value) { setAttribute(this, attributes, namespace, name, value); } + public void setAttribute(String namespace, String namespacePrefix, String name, String value) { + setAttribute(this, attributes, namespace, namespacePrefix, name, value); + } + /* ==== SERIALIZATION ==== */ /* @@ -841,9 +847,10 @@ public void populate(TreeElement incoming, FormDef f) { for (int i = 0; i < incoming.getAttributeCount(); i++) { String name = incoming.getAttributeName(i); String ns = incoming.getAttributeNamespace(i); + String nsPrefix = incoming.getAttributeNamespacePrefix(i); String value = incoming.getAttributeValue(i); - this.setAttribute(ns, name, value); + this.setAttribute(ns, nsPrefix, name, value); } } diff --git a/src/main/java/org/javarosa/xform/parse/XFormParser.java b/src/main/java/org/javarosa/xform/parse/XFormParser.java index 9a4e9ccab..68ca1294e 100644 --- a/src/main/java/org/javarosa/xform/parse/XFormParser.java +++ b/src/main/java/org/javarosa/xform/parse/XFormParser.java @@ -16,6 +16,48 @@ 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; @@ -73,49 +115,6 @@ 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.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 */ /** @@ -2108,6 +2107,7 @@ public static TreeElement buildInstanceStructure(Element node, TreeElement paren if (node.getAttributeCount() > 0) { for (int i = 0; i < node.getAttributeCount(); i++) { String attrNamespace = node.getAttributeNamespace(i); + String attrNamespacePrefix = namespacePrefixesByUri.get(attrNamespace); String attrName = node.getAttributeName(i); if (attrNamespace.equals(NAMESPACE_JAVAROSA) && attrName.equals("template")) { continue; @@ -2116,7 +2116,7 @@ public static TreeElement buildInstanceStructure(Element node, TreeElement paren continue; } - element.setAttribute(attrNamespace, attrName, node.getAttributeValue(i)); + element.setAttribute(attrNamespace, attrNamespacePrefix, attrName, node.getAttributeValue(i)); } } diff --git a/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java b/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java index 37d4c2b88..66f980f40 100644 --- a/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java +++ b/src/test/java/org/javarosa/core/model/instance/TreeElementNamespacedTest.java @@ -117,4 +117,46 @@ public void getAttribute_getsDefaultNamespaceAttribute_withBlankNamespace() { assertThat(result, is(e2)); } + + @Test + public void pathExpressionWithNamespacedAttribute_selectsCorrectAttribute() throws IOException, XFormParser.ParseException { + Scenario scenario = Scenario.init("Attr", html( + asList(new Pair<>("example", "http://example.fake")), + head( + title("Attr"), + model( + mainInstance(t("data id=\"attr\"", + t("question example:a=\"b\" a=\"c\"") + )) + ) + ), + body( + input("/data/question") + ))); + + assertThat(scenario.answerOf("/data/question/@a").getDisplayText(), is("c")); + assertThat(scenario.answerOf("/data/question/@example:a").getDisplayText(), is("b")); + } + + @Test + public void pathExpressionWithNamespacedAttribute_selectsCorrectAttribute_afterSerializationDeserialization() throws IOException, XFormParser.ParseException, DeserializationException { + Scenario scenario = Scenario.init("Attr", html( + asList(new Pair<>("example", "http://example.fake")), + head( + title("Attr"), + model( + mainInstance(t("data id=\"attr\"", + t("question example:a=\"b\" a=\"c\"") + )) + ) + ), + body( + input("/data/question") + ))); + + scenario.serializeAndDeserializeForm(); + + assertThat(scenario.answerOf("/data/question/@a").getDisplayText(), is("c")); + assertThat(scenario.answerOf("/data/question/@example:a").getDisplayText(), is("b")); + } }