01Supported Question Types

The following SRP question types have direct XForm equivalents and export without warnings or errors.

SRP Type Category XForm Mapping
OpenEnded Text <input> with type="string"
LongText Text <input> with appearance="multiline"
Number Numeric <input> with type="decimal"
Age Numeric <input> with type="integer" + constraint
PhoneNumber Text <input> with type="string" + regex constraint
Date Temporal <input> with type="date"
SingleSelect Choice <select1>
MultiSelect Choice <select>
Dropdown Choice <select1> with appearance="minimal"
Rating Scale <input> with type="integer" + rating constraint
Ranking Order <select> with appearance="ranking"
SingleSelectMatrix Matrix Flattened to one <select1> per row. See Section 05.
MultiSelectMatrix Matrix Flattened to one <select> per row. See Section 05.
NetPromoterScore Scale <input> with type="integer" + 0–10 constraint
YesNo Choice <select1> with "Yes" / "No" items
ImageCapture Media <upload> with mediatype="image/*"
Geolocation Location <input> with type="geopoint"

02Unsupported Question Types

These SRP question types have no XForm equivalent and will cause export validation to fail with an Error. Remove or replace them before exporting.

SRP Type Category Reason
CardSort Specialized Drag-and-drop card categorization. No XForm equivalent.
CardRating Specialized Card-based rating interface. No XForm equivalent.
MaxDiff Specialized Maximum difference scaling (best-worst). No standard XForm mapping.
ConstantSum Specialized Allocate a fixed total across items. No XForm equivalent.
Category Specialized Drag items into named categories. No XForm equivalent.
Autosuggest Specialized Typeahead search with external data. No XForm equivalent.
ConjointChoice Specialized Conjoint analysis with attribute profiles. No XForm equivalent.
EmotionSelector Specialized Emotion/sentiment picker. No XForm equivalent.
TextHighlighter Specialized In-text highlighting/annotation. No XForm equivalent.
USAddress Address Multi-field US address form. Could theoretically flatten but not implemented.
InternationalAddress Address Multi-field international address form. Could theoretically flatten but not implemented.

03Supported Language Features

required <bind required="true()">

When a question has required true in the DSL, the XForm bind emits required="true()". ODK Collect and compatible tools will enforce this at submission time.

SRP DSL
open_ended "name" do
  text "Your name?"
  required true
end
XForm Bind
<bind nodeset="/data/name"
      type="string"
      required="true()"/>

show_only_if (segments) <bind relevant="...XPath...">

Segment-based conditional display is translated to XPath relevant expressions. When a question references a segment, the renderer resolves which source question and value that segment corresponds to. Multiple top-level show_only_if entries use OR logic. See Section 06 for the full translation process.

show_only_if (group references / AND logic) combined XPath with and

When show_only_if references a group, the group members (segments) are combined with and logic. Multiple groups at the top level are joined with or.

# If group "qualified" has members ["is_adult", "has_consent"]:
relevant="(${age_q} = 'yes' and ${consent_q} = 'agreed')"

action :skip_to relevance conditions

Skip-to actions on options and rows are translated to XForm relevance expressions on the target questions/pages. The XForm renderer uses the segment registry to determine which questions should be hidden based on the respondent's selection path. This is a Supported mapping.

Special options <item> with canonical values

The special option directives add_none_option, add_dont_know_option, and add_other_option are appended as <item> elements using their canonical values.

DSL Directive Label Value
add_none_option None __none__
add_dont_know_option Don't Know __dont_know__
add_other_option Other __other__

Segments on special options are also collected into the segment registry and produce valid XPath references.

Pages <group appearance="field-list">

Each SRP page is wrapped in an XForm group with appearance="field-list", which displays all questions on the page together (matching SRP's page-based layout). The group ref uses a sanitized version of the page name, and the group label comes from the page title.

Constraints

The renderer emits XForm constraint and jr:constraintMsg attributes for types that have built-in validation rules:

  • Age: constraint=". >= 0 and . <= 150" — message: "Age must be between 0 and 150"
  • PhoneNumber: constraint="regex(., '^[0-9+\-\s()]+$')" — message: "Please enter a valid phone number"
  • Number with min_value/max_value: constraint=". >= N and . <= M" — message describes the allowed range

Submission URL <submission action="...">

When you export via the ODK Export page, you can optionally enter a Submission URL. This injects a <submission> element into the XForm <model>, telling ODK Collect where to send responses.

Export option
Submission URL:
https://your-odk-server.example.com/submission
XForm model
<model>
  <instance>...</instance>
  <submission
    action="https://your-odk-server.example.com/submission"/>
  <!-- binds ... -->
</model>

Leave the field blank to omit the <submission> element entirely.

04Unsupported / Degraded Features

The following DSL features are either ignored (with a warning) or blocked (with an error) during XForm export.

Feature Severity Behavior
randomized (options) Warning Ignored. Options export in the order defined in the DSL. XForm has no client-side randomization mechanism.
randomize_rows Warning Ignored. Matrix rows export in defined order.
randomize_columns Warning Ignored. Matrix columns export in defined order.
Text piping {{...}} Warning Placeholders appear as literal text (e.g., the respondent sees {{name}}). XForm has no client-side text substitution.
options_from Error Blocked. Dynamic option references require client-side JavaScript which XForm cannot execute. Survey must use static options for XForm export.
rows_from Error Blocked. Dynamic row references cannot be resolved at export time.
columns_from Error Blocked. Dynamic column references cannot be resolved at export time.
anchored options Warning Ignored. The option order is preserved as defined, but the anchoring attribute has no effect in XForm.
hidden questions Warning Hidden questions are included in the instance and binds as calculate fields (calculate="", readonly="true()"), but are omitted from the XForm body so they collect no input. They are present in the submission data with an empty value.
Page timing (min_seconds, max_seconds, show_timer) Warning Ignored. XForm/ODK has no page-level timing mechanism. Pages export without timing constraints.
Page randomized Warning Ignored. Page randomization has no XForm equivalent. Pages export in defined order.
action :terminate Warning Converted to a skip-to-end-of-survey. XForm has no "terminate with disqualification" concept; the best approximation is skipping remaining questions.
action :complete Warning Converted to a skip-to-end-of-survey. Functionally identical to terminate in the XForm output.
add_why_field Warning Omitted. The "why" explanation text field has no XForm equivalent.
Complex matrix conditions (if_multiple_cells, if_any_cells, if_column_count) Warning May not translate precisely to XForm XPath. Simple conditions work; complex multi-cell conditions may produce approximate results.

05Matrix Question Flattening

XForm has no native matrix question type. The SRP XForm renderer flattens each matrix question by creating a separate select question for every row. Each flattened question shares the same set of column options.

Naming Convention

The node name for each row is constructed as:

{matrix_name}_{sanitized_row_text}

Where sanitized_row_text is the row label converted to lowercase, stripped of non-alphanumeric characters, with spaces replaced by underscores.

Label Format

Each flattened question's label combines the matrix question text and the row text:

{question_text} - {row_text}

Full Example

SRP DSL
single_select_matrix "satisfaction" do
  text "Rate each area"
  row "Speed"
  row "Quality"
  column "Poor"
  column "Fair"
  column "Good"
end
XForm Instance
<satisfaction_speed/>
<satisfaction_quality/>
XForm Bindings
<bind nodeset="/data/satisfaction_speed"
      type="string"/>
<bind nodeset="/data/satisfaction_quality"
      type="string"/>
XForm Body
<select1 ref="/data/satisfaction_speed">
  <label>Rate each area - Speed</label>
  <item>
    <label>Poor</label>
    <value>poor</value>
  </item>
  <!-- Fair, Good items... -->
</select1>

<select1 ref="/data/satisfaction_quality">
  <label>Rate each area - Quality</label>
  <!-- same <item> children -->
</select1>

MultiSelectMatrix works identically, but uses <select> instead of <select1>.

06Segment → XPath Translation

How It Works

The XForm renderer builds a segment registry at render time by scanning all questions for segments defined on options, special options, matrix rows, and matrix columns. Each segment entry records:

  • question_name — the XForm node name of the source question
  • value — the sanitized option/row/column value that activates the segment (or nil for "any value" segments)

Translation Rules

Segment Source XPath Output
Option with value ${question_name} = 'value'
Option without value (any selection) ${question_name} != ''
Special option ${question_name} = '__canonical_value__'
Matrix row (with if_column) ${matrix_row_node} = 'column_value'
Matrix row (no column filter) ${matrix_row_node} != ''
Matrix column ${question_name} = 'column_value'

Combining Logic

  • Multiple top-level show_only_if entries are joined with or.
  • Group references expand their member segments, joined with and, wrapped in parentheses.

Full Example

SRP DSL
page "screening" do
  single_select "owns_car" do
    text "Do you own a car?"
    option "Yes" do
      segment "car_owner"
    end
    option "No"
  end
end

page "car_details" do
  open_ended "car_make" do
    text "What make is your car?"
    show_only_if "car_owner"
  end
end
XForm Output
<!-- Instance -->
<owns_car/>
<car_make/>

<!-- Bindings -->
<bind nodeset="/data/owns_car"
      type="string"/>

<bind nodeset="/data/car_make"
      type="string"
      relevant="${owns_car} = 'yes'"/>

<!-- Body -->
<select1 ref="/data/owns_car">
  <label>Do you own a car?</label>
  <item><label>Yes</label><value>yes</value></item>
  <item><label>No</label><value>no</value></item>
</select1>

<input ref="/data/car_make">
  <label>What make is your car?</label>
</input>

The segment "car_owner" is resolved to the option "Yes" on question owns_car. The sanitized value is 'yes'. The resulting relevant expression is ${owns_car} = 'yes', which ODK Collect evaluates to show or hide car_make based on the respondent's answer.

Group Reference Example

# Given a group "qualified" with members ["is_adult", "gave_consent"]
# where is_adult → age_check = "18_plus"
# and gave_consent → consent_q = "yes"

show_only_if :group, "qualified"

# Produces XPath:
# relevant="(${age_check} = '18_plus' and ${consent_q} = 'yes')"