A1 · fx =convert(survey)
rxform converts XLSForm workbooks into ODK XForm XML — the format ODK Collect, Enketo and KoboToolbox speak. A Rust implementation of pyxform: one fast static binary, output checked form-by-form against the original, and error messages that point at the exact cell.
| type | name | label | |
|---|---|---|---|
| 2 | select_one yes_no | good_day | Good day today? |
| 3 | begin repeat | visit | Each visit |
| 4 | geopoint | spot | Where are you? |
<select1 ref="/data/good_day">
<itemset nodeset="instance('yes_no')/root/item">
<value ref="name"/>
</itemset>
</select1>
<repeat nodeset="/data/visit">…</repeat>
<bind type="geopoint" nodeset="/data/visit/spot"/>▌
34/34
reference forms identical to pyxform 4.5.0
100%
of the xlsform.org specification
65
tests pinning output & diagnostics
1
static binary — no Python required
B7 · when forms break
A typo in row 3 should not read like a crash report. Every rxform error
carries the sheet, row and column where it happened — plus a probable
cause: the closest question type, the choice list you probably meant,
the begin row a stray
end group forgot about.
$ rxform household_survey.xlsx
error: [sheet 'survey', row 12, column 'type'] unknown question type 'selct_one districts'
probable cause: did you mean 'select_one'?
$ rxform household_survey.xlsx
error: [sheet 'survey', row 48, column 'relevant'] '${distrct}' does not match
the name of any question or group
probable cause: did you mean 'district'?
$ rxform household_survey.xlsx
household_survey.xml▌
C2 · sheet “features”
Everything on xlsform.org converts — each row below was verified against pyxform’s own output.
| type | name | label | |
|---|---|---|---|
| 2 | select_one spec | question_types | Every type: text → geo traces, ranges, media, barcodes, rank, calculate, audit, metadata preloads |
| 3 | begin repeat | structure | Groups, repeats (+ counts), legacy loops, table-list, flat forms — nested arbitrarily |
| 4 | select_multiple langs | multilanguage | label::Idioma (código), translated hints, media & messages via <itext>, guidance hints |
| 5 | select_one_from_file | external_data | CSV/XML/GeoJSON selects, pulldata(), search(), last-saved, select_one_external + itemsets.csv |
| 6 | text | entities | Create & update entity lists: save_to, create_if/update_if, offline version tracking |
| 7 | calculate | logic | ${refs} → xpaths, cascading choice_filter, dynamic defaults, trigger actions, randomize |
| 8 | note | settings | Titles, ids, versions, styles, encryption keys, namespaces, custom attributes, SMS compact tags |
D4 · under the hood
Workbook → normalized cells → question tree → validation with locations → XForm XML, printed with pyxform’s conventions so existing tooling sees familiar output.
E9 · quick start
Prebuilt binaries for every platform ship with each
GitHub release —
Linux (static musl + .deb), macOS and Windows.
macOS · Homebrew
Windows · Scoop
Anywhere · Cargo
# install once
$ cargo install rxform
# convert — writes survey.xml next to the input
$ rxform survey.xlsx
# or choose the destination / print to stdout
$ rxform survey.xlsx -o out/form.xml
$ rxform survey.xlsx --stdout
// cargo add rxform
use std::path::Path;
let conversion = rxform::convert(Path::new("survey.xlsx"))?;
std::fs::write("form.xml", &conversion.xml)?;
if let Some(csv) = conversion.itemsets_csv {
std::fs::write("itemsets.csv", csv)?; // external selects
}