use of org.javarosa.core.model.IDataReference in project javarosa by opendatakit.
the class XFormParser method parseSubmission.
private void parseSubmission(Element submission) {
String id = submission.getAttributeValue(null, ID_ATTR);
// These two are always required
String method = submission.getAttributeValue(null, "method");
String action = submission.getAttributeValue(null, "action");
SubmissionParser parser = new SubmissionParser();
for (SubmissionParser p : submissionParsers) {
if (p.matchesCustomMethod(method)) {
parser = p;
}
}
// These two might exist, but if neither do, we just assume you want the entire instance.
String ref = submission.getAttributeValue(null, REF_ATTR);
String bind = submission.getAttributeValue(null, BIND_ATTR);
IDataReference dataRef = null;
boolean refFromBind = false;
if (bind != null) {
DataBinding binding = bindingsByID.get(bind);
if (binding == null) {
throw new XFormParseException("XForm Parse: invalid binding ID in submit'" + bind + "'", submission);
}
dataRef = binding.getReference();
refFromBind = true;
} else if (ref != null) {
dataRef = new XPathReference(ref);
} else {
// no reference! No big deal, assume we want the root reference
dataRef = new XPathReference("/");
}
if (dataRef != null) {
if (!refFromBind) {
dataRef = FormDef.getAbsRef(dataRef, TreeReference.rootRef());
}
}
SubmissionProfile profile = parser.parseSubmission(method, action, dataRef, submission);
if (id == null) {
// default submission profile
_f.setDefaultSubmission(profile);
} else {
// typed submission profile
_f.addSubmissionProfile(id, profile);
}
}
use of org.javarosa.core.model.IDataReference in project javarosa by opendatakit.
the class XFormParser method parseControl.
/**
* Parses a form control element into a {@link org.javarosa.core.model.QuestionDef} and attaches it to its parent.
*
* @param parent the form control element's parent
* @param e the form control element to parse
* @param controlType one of the control types defined in {@link org.javarosa.core.model.Constants}
* @param additionalUsedAtts attributes specific to the control type
* @param passedThroughAtts attributes specific to the control type that should be passed through to
* additionalAttributes for historical reasons
* @return a {@link org.javarosa.core.model.QuestionDef} representing the form control element
*/
private QuestionDef parseControl(IFormElement parent, Element e, int controlType, List<String> additionalUsedAtts, List<String> passedThroughAtts) {
final QuestionDef question = questionForControlType(controlType);
// until we come up with a better scheme
question.setID(serialQuestionID++);
final List<String> usedAtts = new ArrayList<>(Arrays.asList(REF_ATTR, BIND_ATTR, APPEARANCE_ATTR));
if (additionalUsedAtts != null) {
usedAtts.addAll(additionalUsedAtts);
}
IDataReference dataRef = null;
boolean refFromBind = false;
String ref = e.getAttributeValue(null, REF_ATTR);
String bind = e.getAttributeValue(null, BIND_ATTR);
if (bind != null) {
DataBinding binding = bindingsByID.get(bind);
if (binding == null) {
throw new XFormParseException("XForm Parse: invalid binding ID '" + bind + "'", e);
}
dataRef = binding.getReference();
refFromBind = true;
} else if (ref != null) {
try {
dataRef = new XPathReference(ref);
} catch (RuntimeException el) {
Std.out.println(XFormParser.getVagueLocation(e));
throw el;
}
} else {
// noinspection StatementWithEmptyBody
if (controlType == Constants.CONTROL_TRIGGER) {
// TODO: special handling for triggers? also, not all triggers created equal
} else {
throw new XFormParseException("XForm Parse: input control with neither 'ref' nor 'bind'", e);
}
}
if (dataRef != null) {
if (!refFromBind) {
dataRef = getAbsRef(dataRef, parent);
}
question.setBind(dataRef);
if (controlType == Constants.CONTROL_SELECT_ONE) {
selectOnes.add((TreeReference) dataRef.getReference());
} else if (controlType == Constants.CONTROL_SELECT_MULTI) {
selectMultis.add((TreeReference) dataRef.getReference());
}
}
boolean isSelect = (controlType == Constants.CONTROL_SELECT_MULTI || controlType == Constants.CONTROL_SELECT_ONE);
question.setControlType(controlType);
question.setAppearanceAttr(e.getAttributeValue(null, APPEARANCE_ATTR));
for (int i = 0; i < e.getChildCount(); i++) {
int type = e.getType(i);
Element child = (type == Node.ELEMENT ? e.getElement(i) : null);
String childName = (child != null ? child.getName() : null);
if (LABEL_ELEMENT.equals(childName)) {
parseQuestionLabel(question, child);
} else if ("hint".equals(childName)) {
parseHint(question, child);
} else if (isSelect && "item".equals(childName)) {
parseItem(question, child);
} else if (isSelect && "itemset".equals(childName)) {
parseItemset(question, child, parent);
}
}
if (isSelect) {
if (question.getNumChoices() > 0 && question.getDynamicChoices() != null) {
throw new XFormParseException("Select question contains both literal choices and <itemset>");
} else if (question.getNumChoices() == 0 && question.getDynamicChoices() == null) {
throw new XFormParseException("Select question has no choices");
}
}
if (question instanceof RangeQuestion) {
populateQuestionWithRangeAttributes((RangeQuestion) question, e);
}
parent.addChild(question);
processAdditionalAttributes(question, e, usedAtts, passedThroughAtts);
return question;
}
use of org.javarosa.core.model.IDataReference in project briefcase by opendatakit.
the class BaseFormParserForJavaRosa method compareXml.
/**
* Compare two XML files to assess their level of structural difference (if
* any).
*
* @param incomingParser -- parsed version of incoming form
* @param existingXml -- the existing Xml for this form
* @return XFORMS_SHARE_INSTANCE when bodies differ but instances and bindings
* are identical; XFORMS_SHARE_SCHEMA when bodies and/or bindings
* differ, but database structure remains unchanged; XFORMS_DIFFERENT
* when forms are different enough to affect database structure and/or
* encryption.
* @throws ODKIncompleteSubmissionData
*/
public static DifferenceResult compareXml(BaseFormParserForJavaRosa incomingParser, String existingXml, String existingTitle, boolean isWithinUpdateWindow) throws ODKIncompleteSubmissionData {
if (incomingParser == null || existingXml == null) {
throw new ODKIncompleteSubmissionData(Reason.MISSING_XML);
}
// generally only the case within Briefcase
if (incomingParser.xml.equals(existingXml)) {
return DifferenceResult.XFORMS_IDENTICAL;
}
// parse XML
FormDef formDef1;
FormDef formDef2;
BaseFormParserForJavaRosa existingParser = new BaseFormParserForJavaRosa(existingXml, existingTitle, true);
formDef1 = incomingParser.rootJavaRosaFormDef;
formDef2 = existingParser.rootJavaRosaFormDef;
if (formDef1 == null || formDef2 == null) {
throw new ODKIncompleteSubmissionData("Javarosa failed to construct a FormDef. Is this an XForm definition?", Reason.BAD_JR_PARSE);
}
// check that the version is advancing from the earlier
// form upload. The comparison is string-based, not
// numeric-based (OpenRosa compliance). The recommended
// version format is: yyyymmddnn e.g., 2012060100
String ivs = incomingParser.rootElementDefn.versionString;
if (ivs == null) {
// if we are changing the file, the new file must have a version string
return DifferenceResult.XFORMS_MISSING_VERSION;
}
String evs = existingParser.rootElementDefn.versionString;
boolean modelVersionSame = (incomingParser.rootElementDefn.modelVersion == null) ? (existingParser.rootElementDefn.modelVersion == null) : incomingParser.rootElementDefn.modelVersion.equals(existingParser.rootElementDefn.modelVersion);
boolean isEarlierVersion = false;
if (!(evs == null || (modelVersionSame && ivs.length() > evs.length()) || (!modelVersionSame && ivs.compareTo(evs) > 0))) {
// disallow updates if none of the following applies:
// (1) if the existing form does not have a version (the new one does).
// (2) if the existing form and new form have the same model version
// and the new form has more leading zeros.
// (3) if the existing form and new form have different model versions
// and the new version string is lexically greater than the old one.
isEarlierVersion = true;
return DifferenceResult.XFORMS_EARLIER_VERSION;
}
/*
* Changes in encryption (either on or off, or change in key) are a major
* change. We could allow the public key to be revised, but most users won't
* understand that this is possible or know how to do it.
*
* Ignore whether a submission profile is present or absent provided it does
* not affect encryption or change the portion of the form being returned.
*/
SubmissionProfile subProfile1 = formDef1.getSubmissionProfile();
SubmissionProfile subProfile2 = formDef2.getSubmissionProfile();
if (subProfile1 != null && subProfile2 != null) {
// we have two profiles -- check that any encryption key matches...
String publicKey1 = subProfile1.getAttribute(BASE64_RSA_PUBLIC_KEY);
String publicKey2 = subProfile2.getAttribute(BASE64_RSA_PUBLIC_KEY);
if (publicKey1 != null && publicKey2 != null) {
// both have encryption
if (!publicKey1.equals(publicKey2)) {
// keys differ
return (DifferenceResult.XFORMS_DIFFERENT);
}
} else if (publicKey1 != null || publicKey2 != null) {
// one or the other has encryption (and the other doesn't)...
return (DifferenceResult.XFORMS_DIFFERENT);
}
// get the TreeElement (e1, e2) that identifies the portion of the form
// that will be submitted to Aggregate.
IDataReference r;
r = subProfile1.getRef();
AbstractTreeElement<?> e1 = (r != null) ? formDef1.getInstance().resolveReference(r) : null;
r = subProfile2.getRef();
AbstractTreeElement<?> e2 = (r != null) ? formDef2.getInstance().resolveReference(r) : null;
if (e1 != null && e2 != null) {
// Ignore all namespace differences (Aggregate ignores them)...
while (e1 != null && e2 != null) {
if (!e1.getName().equals(e2.getName())) {
return (DifferenceResult.XFORMS_DIFFERENT);
}
e1 = e1.getParent();
e2 = e2.getParent();
}
if (e1 != null || e2 != null) {
// they should both terminate at the same time...
return (DifferenceResult.XFORMS_DIFFERENT);
}
// we may still have differences, but if the overall form
// is identical, we are golden...
} else if (e1 != null || e2 != null) {
// one returns a portion of the form and the other doesn't
return (DifferenceResult.XFORMS_DIFFERENT);
}
} else if (subProfile1 != null) {
if (subProfile1.getAttribute(BASE64_RSA_PUBLIC_KEY) != null) {
// xml1 does encryption, the other doesn't
return (DifferenceResult.XFORMS_DIFFERENT);
}
IDataReference r = subProfile1.getRef();
if (r != null && formDef1.getInstance().resolveReference(r) != null) {
// xml1 returns a portion of the form, the other doesn't
return (DifferenceResult.XFORMS_DIFFERENT);
}
} else if (subProfile2 != null) {
if (subProfile2.getAttribute(BASE64_RSA_PUBLIC_KEY) != null) {
// xml2 does encryption, the other doesn't
return (DifferenceResult.XFORMS_DIFFERENT);
}
IDataReference r = subProfile2.getRef();
if (r != null && formDef2.getInstance().resolveReference(r) != null) {
// xml2 returns a portion of the form, the other doesn't
return (DifferenceResult.XFORMS_DIFFERENT);
}
}
// get data model to compare instances
FormInstance dataModel1 = formDef1.getInstance();
FormInstance dataModel2 = formDef2.getInstance();
if (dataModel1 == null || dataModel2 == null) {
throw new ODKIncompleteSubmissionData("Javarosa failed to construct a FormInstance. Is this an XForm definition?", Reason.BAD_JR_PARSE);
}
// return result of element-by-element instance/binding comparison
DifferenceResult rc = compareTreeElements(dataModel1.getRoot(), incomingParser, dataModel2.getRoot(), existingParser);
if (DifferenceResult.XFORMS_DIFFERENT == rc) {
return rc;
} else if (isEarlierVersion) {
return DifferenceResult.XFORMS_EARLIER_VERSION;
} else {
return rc;
}
}
use of org.javarosa.core.model.IDataReference in project javarosa by opendatakit.
the class StandardBindAttributesProcessor method createBinding.
DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef, Collection<String> usedAttributes, Collection<String> passedThroughAttributes, Element element) {
final DataBinding binding = new DataBinding();
binding.setId(element.getAttributeValue("", ID_ATTR));
final String nodeset = element.getAttributeValue(null, NODESET_ATTR);
if (nodeset == null) {
throw new XFormParseException("XForm Parse: <bind> without nodeset", element);
}
IDataReference ref;
try {
ref = new XPathReference(nodeset);
} catch (XPathException xpe) {
throw new XFormParseException(xpe.getMessage());
}
ref = parserFunctions.getAbsRef(ref, formDef);
binding.setReference(ref);
binding.setDataType(getDataType(element.getAttributeValue(null, "type")));
String xpathRel = element.getAttributeValue(null, "relevant");
if (xpathRel != null) {
if ("true()".equals(xpathRel)) {
binding.relevantAbsolute = true;
} else if ("false()".equals(xpathRel)) {
binding.relevantAbsolute = false;
} else {
Condition c = buildCondition(xpathRel, "relevant", ref);
c = (Condition) formDef.addTriggerable(c);
binding.relevancyCondition = c;
}
}
String xpathReq = element.getAttributeValue(null, "required");
if (xpathReq != null) {
if ("true()".equals(xpathReq)) {
binding.requiredAbsolute = true;
} else if ("false()".equals(xpathReq)) {
binding.requiredAbsolute = false;
} else {
Condition c = buildCondition(xpathReq, "required", ref);
c = (Condition) formDef.addTriggerable(c);
binding.requiredCondition = c;
}
}
String xpathRO = element.getAttributeValue(null, "readonly");
if (xpathRO != null) {
if ("true()".equals(xpathRO)) {
binding.readonlyAbsolute = true;
} else if ("false()".equals(xpathRO)) {
binding.readonlyAbsolute = false;
} else {
Condition c = buildCondition(xpathRO, "readonly", ref);
c = (Condition) formDef.addTriggerable(c);
binding.readonlyCondition = c;
}
}
final String xpathConstr = element.getAttributeValue(null, "constraint");
if (xpathConstr != null) {
try {
binding.constraint = new XPathConditional(xpathConstr);
} catch (XPathSyntaxException xse) {
throw new XFormParseException("bind for " + nodeset + " contains invalid constraint expression [" + xpathConstr + "] " + xse.getMessage());
}
binding.constraintMessage = element.getAttributeValue(NAMESPACE_JAVAROSA, "constraintMsg");
}
final String xpathCalc = element.getAttributeValue(null, "calculate");
if (xpathCalc != null) {
Recalculate r;
try {
r = buildCalculate(xpathCalc, ref);
} catch (XPathSyntaxException xpse) {
throw new XFormParseException("Invalid calculate for the bind attached to \"" + nodeset + "\" : " + xpse.getMessage() + " in expression " + xpathCalc);
}
r = (Recalculate) formDef.addTriggerable(r);
binding.calculate = r;
}
binding.setPreload(element.getAttributeValue(NAMESPACE_JAVAROSA, "preload"));
binding.setPreloadParams(element.getAttributeValue(NAMESPACE_JAVAROSA, "preloadParams"));
saveUnusedAttributes(binding, element, usedAttributes, passedThroughAttributes);
return binding;
}
use of org.javarosa.core.model.IDataReference in project javarosa by opendatakit.
the class XFormParser method parseSetValueAction.
private void parseSetValueAction(FormDef form, Element e) {
String ref = e.getAttributeValue(null, REF_ATTR);
String bind = e.getAttributeValue(null, BIND_ATTR);
String event = e.getAttributeValue(null, "event");
IDataReference dataRef = null;
boolean refFromBind = false;
// TODO: There is a _lot_ of duplication of this code, fix that!
if (bind != null) {
DataBinding binding = bindingsByID.get(bind);
if (binding == null) {
throw new XFormParseException("XForm Parse: invalid binding ID in submit'" + bind + "'", e);
}
dataRef = binding.getReference();
refFromBind = true;
} else if (ref != null) {
dataRef = new XPathReference(ref);
} else {
throw new XFormParseException("setvalue action with no target!", e);
}
if (dataRef != null) {
if (!refFromBind) {
dataRef = FormDef.getAbsRef(dataRef, TreeReference.rootRef());
}
}
String valueRef = e.getAttributeValue(null, "value");
Action action;
TreeReference treeref = FormInstance.unpackReference(dataRef);
actionTargets.add(treeref);
if (valueRef == null) {
if (e.getChildCount() == 0 || !e.isText(0)) {
throw new XFormParseException("No 'value' attribute and no inner value set in <setvalue> associated with: " + treeref, e);
}
// Set expression
action = new SetValueAction(treeref, e.getText(0));
} else {
try {
action = new SetValueAction(treeref, XPathParseTool.parseXPath(valueRef));
} catch (XPathSyntaxException e1) {
Std.printStack(e1);
throw new XFormParseException("Invalid XPath in value set action declaration: '" + valueRef + "'", e);
}
}
form.registerEventListener(event, action);
}
Aggregations