Migrating questionnaires from ODK and compatible systems

August 29, 2021

To simplify migration from Open Data Kit (ODK) or compatible systems (Survey123, SurveyCTO, KoBoToolBox, etc) we present the possible/recommended alternatives in Survey Solutions for:

Individual system implementation of these features may vary. Online documentation for ODK was used as a reference when compiling the list of alternatives (accessed August 2021). For specifics of function implementation in different systems, refer to their respective documentation pages, e.g. for Survey123, for SurveyCTO, etc.

Where we leave a note "#no alternative", this is to be interpreted that there is no 1:1 mapping of an ODK feature to a corresponding Survey Solutions feature, and no obvious workaround. Yet this is usually not a dead end, and a workaround may be formulated for a particular situation.

Please note the basic correspondence between the questionnaire elements:

ODK, SurveyCTO, Survey123, KoBoToolBox Survey Solutions
Group Section or sub-section
Repeated group Roster
Question Question
Calculate Variable
Note Static text
Relevance Enabling condition
Constraint Validation

Question types

The following table follows the list of question types present in ODK and compatible systems (SurveyCTO, Survey123, KoBoToolBox, etc) and shows a possible equivalent or alternative in Survey Solutions.

ODK, SurveyCTO, Survey123, KoBoToolBox Survey Solutions
Text widget Text question
Number widget Numeric question
Date and time widget Date question.
For time use a formatted string question, with a pattern such as "##:##"
Select widget Single-select question, multi-select question
Select questions have several presentation alternatives in XForms and likewise in Survey Solutions. Selection from an external file is not supported in Survey Solutions - all possible alternatives must be part of the questionnaire.
Rank widget Multi-select question
Location widget GPS location question
Image widget Image question
Audio widget Audio question
File upload widget #no alternative.
Existing images may be uploaded as files using an image question.
Barcode widget Barcode question
Range widget Single-select or numeric questions
Note widget Static text
URL widget Static text
Read more about hyperlinks
Printer widget #no equivalent
Trigger/acknowledge widget Categorical single-select question with condition on the remaining part of the questionnaire.
signature widget Image question with signature flag.
Grouping multiple widgets on the same screen Questions can be placed in the same section/sub-section, to appear on the same screen, or placed in different sections/sub-sections to appear on the different screens.
Grid of selects on the same screen See matrix presentation of categorical questions (CAWI only).
Hidden questions Questions of most types can be marked as hidden. See, Designer limitations by question type

Operators

The following table follows the list of operators present in ODK and compatible systems (SurveyCTO, Survey123, KoBoToolBox, etc) and shows a possible equivalent or alternative in Survey Solutions.

ODK, SurveyCTO, Survey123, KoBoToolBox Survey Solutions
Math operators

+ (addition) + (addition)
- (subtraction) - (subtraction)
* (mutliplication) * (multiplication)
div (division) / (division)
mod (modulo (division remainder)) % (modulo (division remainder))
Comparison operators

Note that relational operators in C# used in Survey Solutions can also be used to compare strings.
= (equal to) == (equal to)
!= (not equal to) != (not equal to)
> (greater than) > (greater than)
>= (greater than or equal) >= (greater than or equal)
< (less than) < (less than)
<= (less than or equal) <= (less than or equal)
Boolean operators
and &&
or ||
Path operators
. self
.. #no direct equivalent. Use @rowcode or @rowindex depending on the context. For example: MEMBERS[2].@rowcode is identical to 2 (if such a member was included in the roster MEMBERS)

Functions

The following table follows the list of functions present in ODK and compatible systems (SurveyCTO, Survey123, KoBoToolBox, etc) and shows a possible equivalent or alternative in Survey Solutions.

Some functions have several possible alternatives, and the exact choice may depend on the context.

ODK, SurveyCTO, Survey123, KoBoToolBox Survey Solutions
Control Flow
if(expression, then, else) expression ? then : else
position(xpath) @rowindex - returns the position of the current roster item in its parent roster. It can also be used with non-current objects: MEMBERS[fatherid].@rowindex returns the index of the row of the person with the code equal to fatherid in the roster ROSTER.
once(expression) #no equivalent.
An expression in Survey Solutions may not be calculated only once, instead the system must be able to recalculate any expression on demand.
Accessing response values
▸▸ Select questions
selected(space_delimited_array, string) space_delimited_array.split().Contains(string) - literally;
Q.Contains(c) - for checking whether a code c is selected in multiple-select question Q (since the answerto multiple-select questions in Survey Solutions is an array of selected codes).
selected-at(space_delimited_array, n) space_delimited_array.split()[n] - literally;
Q[n]==c - for checking whether a code c is selected as choice n in multiple-select question Q (since the answerto multiple-select questions in Survey Solutions is an array of selected codes).
count-selected(multi_select_question) multi_select_question.Length
jr:choice-name(choice_name, 'select_question') #no equivalent.
Access to localized strings in syntax has been intentionally disabled.
▸▸ Repeat groups
indexed-repeat(name, group, i[, sub_grp, sub_i[, sub_sub_grp, sub_sub_i]]) group[i].name
count(nodeset) nodeset.Count()
count-non-empty(nodeset) ROSTER.Count(x=>IsAnswered(x.q)) - the Survey Solutions equivalent counts the number of answered questions q in the roster ROSTER.
sum(nodeset) ROSTER.Sum(x=>x.q) - the Survey Solutions equivalent sums the values of answers to questions q in the roster ROSTER. Empty values (unanswered questions) do not affect the calculation of the sum (are ignored).
max(nodeset) ROSTER.Max(x=>x.q)- the Survey Solutions equivalent returns the maximum of values of answers to questions q in the roster ROSTER. Empty values (unanswered questions) are ignored.
min(nodeset) ROSTER.Min(x=>x.q)- the Survey Solutions equivalent returns the minimum of values of answers to questions q in the roster ROSTER. Empty values (unanswered questions) are ignored.
Strings
▸▸ Searching and matching strings
regex(string, expression) new System.Text.RegularExpressions.Regex(expression).Match(string) - note that the regular expression in Survey Solutions will not be the same as in ODK-based systems and needs to be transformed. Example: find the first 4-digit number in the string s: new System.Text.RegularExpressions.Regex(@"\b\d{4}\b").Match(s).ToString() For the value of s="19 April 1980 - 16 November 2021" returns "1980".
contains(string, substring) string.Contains(substring)
starts-with(string, substring) string.StartsWith(substring)
ends-with(string, substring) string.EndsWith(substring)
substr(string, start[, end]) string.Substring(start, length) Note: (length=end-start+1)
substring-before(string, target) string.Contains(target) ? string.Substring(0, IndexOf(target)) : String.Empty
substring-after(string, target) string.Contains(target) ? string.Substring(IndexOf(target)+target.Length) : String.Empty
translate(string, fromchars, tochars) string.Replace(fromchars,tochars)
string-length(string) string.Length
normalize-space(string) string.StrTrim()
▸▸ Combining strings
concat(arg [, arg1 [, arg2 [, argk [...]]]]) String.Concat(arg1,arg2,...,argk)
join(separator, nodeset) String.Join(separator,ROSTER.Select(x=>x.q)) - concatenates answers to question q of roster ROSTER.
▸▸ Converting to and from strings
boolean-from-string(string) string.InList("true","1")
string(arg) arg.ToString() - objects of some types may require specification of a format or may use a default format that depends on the system.
Mathematics
▸▸ Number handling
round(number, places) Math.Round(number, places)
int(number) (int)(Math.Truncate(number)
number(arg) Double.Parse(arg) for decimals,
(arg==true)?1:0 for Boolean values
digest() #no equivalent
Formally digest algorithms are implemented in System.Security.Cryptography, such as System.Security.Cryptography.MD5(), but access to these classes has been disabled for security reasons.
▸▸ Calculation
pow(number, power) Math.Pow(number, power)
log(number) Math.Log(number)
log10(number) Math.Log10(number)
abs(number) Math.Abs(number)
sin(number) Math.Sin(number)
cos(number) Math.Cos(number)
tan(number) Math.Tan(number)
asin(number) Math.Asin(number)
acos(number) Math.Acos(number)
atan(number) Math.Atan(number)
atan2(number) Math.Atan2(number)
sqrt(number) Math.Sqrt(number)
exp(x) Math.Exp(x)
exp10(x) Math.Pow(10,x)
pi() Math.PI
Date and time
today() #no equivalent.
Access to DateTime.Now() has been disabled to avoid logical mistakes.
now() #no equivalent.
Access to DateTime.Now() has been disabled to avoid logical mistakes. Use a current timestamp question at the beginning of the interview. Refer to that date/time when you need the reference time.
▸▸ Converting dates and times
decimal-date-time(dt) dt.Subtract(DateTime.Parse("01-Jan-1970")).Days
date(days) new DateTime(1970, 1, 1, 0, 0, 0, 0).AddDays(days).Date
decimal-time(t) Survey Solutions doesn't have a separate time class. If you separately have HOUR, MIN, SEC of the value oft, then decimal-time(time) is new TimeSpan(HOUR,MIN,SEC) / (24*60*60) .
▸▸ Formatting dates and times for display

format-date(date, format), format-date-time(dateTime, format)
refer to Standard date and time format strings and Custom date and time format strings.

Example: new DateTime(1970, 4, 19, 13, 45, 06, 200).ToString(“dd MMM HH:mm:ss”) produces 19 Apr 13:45:06

Geography
area(nodeset | geoshape) #no equivalent.
distance(nodeset | geoshape | geotrace) #no equivalent.
For distance between two specific locations A and B use Survey Solutions function A.GpsDistance(B) or A.GpsDistanceKm(B).
Utility
random() Quest.IRnd() - returns a single random number,
new Random() - create a new random number generator object.
randomize() #no equivalent.
For randomization refer to examples in the public questionnaires.
uuid([length]) Guid.NewGuid().ToString("D")
#no alternative to generating a UUID of a specified length. useful link.
boolean(arg) (arg!=0) for numbers;
(arg!="") for strings (see also function IsNullOrEmpty()); etc for other types.
not(arg) !arg
coalesce(arg1, arg2) arg1??arg2
checklist(min, max, response1[, response2[, responsek[, ...]]]) new []{response1,response2,...,responsek}.Count(x=>x>min && x<=max)
weighted-checklist(min, max, reponse, weight[, response, weight[, response, weight[, response, weight[, ... ]]])
(description)
ROSTER.Where(x=>x.response=="yes").Sum(x=>x.weight).InRange(min,max)

If you notice any incorrect mapping in this table, feel free to raise the issue in the users' forum.