Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 60: Allow http://openrosa.org/xforms namespace for meta #153

Merged
merged 13 commits into from
Jul 19, 2017
Merged
22 changes: 22 additions & 0 deletions resources/meta-namespace-form.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:orx2="http://openrosa.org/xforms">
<h:head>
<h:title>Namespace for Metadata</h:title>
<model>
<instance>
<data id="test">
<orx2:meta>
<orx2:audit/>
<orx2:audit2/>
<audit3/>
</orx2:meta>
</data>
</instance>
<bind nodeset="/data/orx2:meta/orx2:audit"/>
<bind nodeset="/data/meta/audit2"/>
<bind nodeset="/data/orx2:meta/audit3"/>
</model>
</h:head>
<h:body>
</h:body>
</h:html>
33 changes: 22 additions & 11 deletions src/org/javarosa/core/model/instance/TreeElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

Expand All @@ -35,6 +36,7 @@
import org.javarosa.core.model.instance.utils.DefaultAnswerResolver;
import org.javarosa.core.model.instance.utils.IAnswerResolver;
import org.javarosa.core.model.instance.utils.ITreeVisitor;
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;
Expand Down Expand Up @@ -105,6 +107,7 @@ public class TreeElement implements Externalizable, AbstractTreeElement<TreeElem
private int flags = MASK_RELEVANT | MASK_ENABLED | MASK_RELEVANT_INH | MASK_ENABLED_INH;

private String namespace;
private String namespacePrefix;

private String instanceName = null;

Expand Down Expand Up @@ -272,19 +275,20 @@ public List<TreeElement> getChildrenWithName(String name) {
return getChildrenWithName(name, false);
}

private List<TreeElement> getChildrenWithName(String name, boolean includeTemplate) {
if(children == null) { return new ArrayList<TreeElement>(0);}
private List<TreeElement> getChildrenWithName(String name, boolean includeTemplate) {
if (children == null) {
return Collections.emptyList();
}

List<TreeElement> v = new ArrayList<TreeElement>();
for (int i = 0; i < this.children.size(); i++) {
TreeElement child = this.children.get(i);
if ((child.getName().equals(name) || name.equals(TreeReference.NAME_WILDCARD))
&& (includeTemplate || child.multiplicity != TreeReference.INDEX_TEMPLATE))
v.add(child);
}
List<TreeElement> v = new ArrayList<>();
for (TreeElement child : children) {
if (TreeElementNameComparator.elementMatchesName(child, name)
&& (includeTemplate || child.multiplicity != TreeReference.INDEX_TEMPLATE))
v.add(child);
}

return v;
}
return v;
}

/* (non-Javadoc)
* @see org.javarosa.core.model.instance.AbstractTreeElement#getNumChildren()
Expand Down Expand Up @@ -1401,4 +1405,11 @@ public List<TreeReference> tryBatchChildFetch(String name, int mult, List<XPathE
return selectedChildren;
}

public String getNamespacePrefix() {
return namespacePrefix;
}

public void setNamespacePrefix(String namespacePrefix) {
this.namespacePrefix = namespacePrefix;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.javarosa.core.model.instance.utils;

import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.core.model.instance.TreeReference;


/** Supports locating {@link TreeElement}s by name. */
public class TreeElementNameComparator {

/**
* Determines whether treeElement matches name. If name is a wildcard, it does. If the element’s
* name matches name (using equals), it does. If neither of those cases are true, a namespace
* prefix for treeElement, if one can be located, is prepended to treeElement’s name, and that is
* compared to name with equals.
*
* @param treeElement the TreeElement under examination
* @param name the name to be compared with treeElement’s name
* @return whether treeElement matches name
*/
public static boolean elementMatchesName(TreeElement treeElement, String name) {
final String namespacePrefix = treeElement.getNamespacePrefix();

if (name.equals(TreeReference.NAME_WILDCARD)) {
return true;
}

final String elementName = treeElement.getName();

return elementName.equals(name) ||
(namespacePrefix != null && (namespacePrefix + ":" + elementName).equals(name));
}

}
5 changes: 3 additions & 2 deletions src/org/javarosa/xform/parse/FormInstanceParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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;
Expand Down Expand Up @@ -53,8 +54,8 @@ class FormInstanceParser {
this.actionTargets = actionTargets;
}

FormInstance parseInstance(Element e, boolean isMainInstance, String name) {
TreeElement root = buildInstanceStructure(e, null, !isMainInstance ? name : null, e.getNamespace());
FormInstance parseInstance(Element e, boolean isMainInstance, String name, Map<String, String> namespacePrefixesByUri) {
TreeElement root = buildInstanceStructure(e, null, !isMainInstance ? name : null, e.getNamespace(), namespacePrefixesByUri);
FormInstance instanceModel = new FormInstance(root);
instanceModel.setName(isMainInstance ? formDef.getTitle() : name);

Expand Down
37 changes: 26 additions & 11 deletions src/org/javarosa/xform/parse/XFormParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ public class XFormParser implements IXFormParserFunctions {
private List<String> namedActions;
private HashMap<String, IElementHandler> structuredActions;



//incremented to provide unique question ID for each question
private int serialQuestionID = 1;

Expand Down Expand Up @@ -318,7 +316,7 @@ public FormDef parse() throws IOException {
_xmldoc = getXMLDocument(_reader, stringCache);
}

parseDoc();
parseDoc(buildNamespacesMap(_xmldoc.getRootElement()));

//load in a custom xml instance, if applicable
if (_instReader != null) {
Expand All @@ -330,6 +328,17 @@ public FormDef parse() throws IOException {
return _f;
}

/** Extracts the namespaces from the given element and creates a map of URI to prefix */
private static Map<String, String> buildNamespacesMap(Element el) {
final Map<String, String> namespacePrefixesByURI = new HashMap<>();

for (int i = 0; i < el.getNamespaceCount(); i++) {
namespacePrefixesByURI.put(el.getNamespaceUri(i), el.getNamespacePrefix(i));
}

return namespacePrefixesByURI;
}

public static Document getXMLDocument(Reader reader) throws IOException {
return getXMLDocument(reader, null);
}
Expand Down Expand Up @@ -425,7 +434,7 @@ public static Document getXMLDocument(Reader reader, CacheTable<String> stringCa
return doc;
}

private void parseDoc() {
private void parseDoc(Map<String, String> namespacePrefixesByUri) {
_f = new FormDef();

initState();
Expand All @@ -445,15 +454,18 @@ private void parseDoc() {
for(int i = 1; i < instanceNodes.size(); i++)
{
Element e = instanceNodes.get(i);
FormInstance fi = instanceParser.parseInstance(e, false, instanceNodeIdStrs.get(instanceNodes.indexOf(e)));
FormInstance fi = instanceParser.parseInstance(e, false,
instanceNodeIdStrs.get(instanceNodes.indexOf(e)), namespacePrefixesByUri);
loadNamespaces(_xmldoc.getRootElement(), fi);
loadInstanceData(e, fi.getRoot(), _f);
_f.addNonMainInstance(fi);
}
}
//now parse the main instance
if(mainInstanceNode != null) {
FormInstance fi = instanceParser.parseInstance(mainInstanceNode, true,
instanceNodeIdStrs.get(instanceNodes.indexOf(mainInstanceNode)));
instanceNodeIdStrs.get(instanceNodes.indexOf(mainInstanceNode)), namespacePrefixesByUri);
loadNamespaces(_xmldoc.getRootElement(), fi);
addMainInstanceToFormDef(mainInstanceNode, fi);
}

Expand Down Expand Up @@ -1750,12 +1762,12 @@ static HashMap<String, String> loadNamespaces(Element e, FormInstance tree) {
return prefixes;
}

public static TreeElement buildInstanceStructure (Element node, TreeElement parent) {
return buildInstanceStructure(node, parent, null, node.getNamespace());
public static TreeElement buildInstanceStructure (Element node, TreeElement parent, Map<String, String> namespacePrefixesByUri) {
return buildInstanceStructure(node, parent, null, node.getNamespace(), namespacePrefixesByUri);
}

/** Parses instance hierarchy and turns into a skeleton model; ignoring data content, but respecting repeated nodes and 'template' flags */
public static TreeElement buildInstanceStructure (Element node, TreeElement parent, String instanceName, String docnamespace) {
public static TreeElement buildInstanceStructure (Element node, TreeElement parent, String instanceName, String docnamespace, Map<String, String> namespacePrefixesByUri) {
TreeElement element = null;

//catch when text content is mixed with children
Expand Down Expand Up @@ -1811,13 +1823,16 @@ public static TreeElement buildInstanceStructure (Element node, TreeElement pare
if(!node.getNamespace().equals(docnamespace)) {
element.setNamespace(node.getNamespace());
}
if (namespacePrefixesByUri.containsKey(node.getNamespace())) {
element.setNamespacePrefix(namespacePrefixesByUri.get(node.getNamespace()));
}
}


if (hasElements) {
for (int i = 0; i < numChildren; i++) {
if (node.getType(i) == Node.ELEMENT) {
element.addChild(buildInstanceStructure(node.getElement(i), element, instanceName, docnamespace));
element.addChild(buildInstanceStructure(node.getElement(i), element, instanceName, docnamespace, namespacePrefixesByUri));
}
}
}
Expand Down Expand Up @@ -1978,7 +1993,7 @@ public static FormInstance restoreDataModel (Document doc, Class restorableType)

Element e = doc.getRootElement();

TreeElement te = buildInstanceStructure(e, null);
TreeElement te = buildInstanceStructure(e, null, buildNamespacesMap(e));
FormInstance dm = new FormInstance(te);
loadNamespaces(e, dm);
if (r != null) {
Expand Down
62 changes: 62 additions & 0 deletions test/org/javarosa/xform/parse/TreeElementNameComparatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.javarosa.xform.parse;

import org.javarosa.core.model.instance.TreeElement;
import org.junit.Test;

import static org.javarosa.core.model.instance.utils.TreeElementNameComparator.elementMatchesName;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TreeElementNameComparatorTest {

private static final String NS_ORX = "http://openrosa.org/xforms";
private static final String PREFIX_ORX = "orx";

@Test public void wildcardMatches() {
assertTrue(elementMatchesName(new TreeElement("a"), "*"));
}

@Test public void simpleMatches() {
assertTrue(elementMatchesName(new TreeElement("a"), "a"));
}

@Test public void simpleMisMatches() {
assertFalse(elementMatchesName(new TreeElement("a"), "b"));
}

@Test public void explicitMatches() {
assertTrue(elementMatchesName(createTreeElement("a", NS_ORX, PREFIX_ORX), "orx:a"));
}

@Test public void explicitMismatchesName() {
assertFalse(elementMatchesName(createTreeElement("a", NS_ORX, PREFIX_ORX), "orx:b"));
}

@Test public void explicitMismatchesPrefix() {
assertFalse(elementMatchesName(createTreeElement("a", NS_ORX, PREFIX_ORX), "orx2:b"));
}

@Test public void explicitMismatchesNoNsElement() {
assertFalse(elementMatchesName(new TreeElement("a"), "orx:a"));
}

/**
* Creates a TreeElement for testing.
* <p>
* JavaRosa creates TreeElements that don’t contain prefixes. Rather, they have the namespace
* associated with the prefix.
*
* @param name the name of the element, without any prefix
* @param namespace the namespace, e.g., http://openrosa.org/xforms, or null
* @param prefix the prefix we are pretending has been associated with namespace, or null
* @return a TreeElement
*/
private TreeElement createTreeElement(String name, final String namespace, final String prefix) {
TreeElement te = new TreeElement(name);
if (namespace != null && prefix != null) {
te.setNamespace(namespace);
te.setNamespacePrefix(prefix);
}
return te;
}
}
Loading