Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
59ef17b
dev: update github actions versions to latest available
lindsay-stevens Jul 13, 2026
b886e77
dev: bump python versions
lindsay-stevens Jul 13, 2026
1054c26
dev: bump dev dependencies
lindsay-stevens Jul 13, 2026
03857cd
dev: bump dev dependencies (ruff) + linting
lindsay-stevens Jul 13, 2026
f2171c0
dev: bump minimum python, apply linting
lindsay-stevens Jul 13, 2026
9c8e758
dev: add and apply ruff ruleset "furb"
lindsay-stevens Jul 13, 2026
16a05c7
dev: add and apply pydocstyle rule D403
lindsay-stevens Jul 13, 2026
935e482
dev: add and apply pydocstyle rule D202
lindsay-stevens Jul 13, 2026
a3079cd
dev: add and apply pydocstyle rule D209
lindsay-stevens Jul 13, 2026
f94375a
dev: add and apply pydocstyle rules D301 and D415
lindsay-stevens Jul 13, 2026
40735c9
dev: add and apply pydocstyle rule D200
lindsay-stevens Jul 13, 2026
95e8dc5
dev: add and apply pydocstyle rule D400
lindsay-stevens Jul 13, 2026
754016d
dev: invert pydocstyle rule selection to ignore those to defer
lindsay-stevens Jul 13, 2026
9fa8262
dev: add and apply ruff ruleset "flynt"
lindsay-stevens Jul 13, 2026
0154ffe
dev: rename readme from .rst to .md
lindsay-stevens Jul 13, 2026
15206bb
dev: convert readme from .rst to .md with pandoc v3.10
lindsay-stevens Jul 13, 2026
b5ba83d
chg: move tests *xls folders to /fixtures/*forms
lindsay-stevens Jul 13, 2026
e6eb8f4
fix: delete choices sheet name test fixture not referenced anywhere
lindsay-stevens Jul 13, 2026
c8b6d2d
fix: delete blank row test case and fixture covered by another test
lindsay-stevens Jul 13, 2026
e165ee5
fix: replace old xls missing calc test with equivalent markdown test
lindsay-stevens Jul 13, 2026
a9949df
fix: move single calculate test into module for testing calculate
lindsay-stevens Jul 13, 2026
e1cf7f4
chg: rename calculate test module to follow pattern of other tests
lindsay-stevens Jul 13, 2026
f7a0602
chg: replace empty columns xls fixture with xlsx
lindsay-stevens Jul 13, 2026
b39858d
fix: delete missing group name test case and fixture covered elsewhere
lindsay-stevens Jul 14, 2026
75fa6a1
chg: replace empty relevant xls test with md/dict test cases
lindsay-stevens Jul 14, 2026
62a2d59
fix: delete empty/spaces xls test and fixture covered by other tests
lindsay-stevens Jul 14, 2026
a697934
chg: replace comment column/row xlst test with md test case
lindsay-stevens Jul 14, 2026
4cab37f
chg: move last "j2x_creation" test to similar j2x test file
lindsay-stevens Jul 14, 2026
9f9b38c
fix: delete calculate test fixture not referenced anywhere
lindsay-stevens Jul 14, 2026
b9d24d4
fix: delete cascading select test fixture not referenced anywhere
lindsay-stevens Jul 14, 2026
8e4cd16
fix: delete unused func and constants related to "cascading select"
lindsay-stevens Jul 14, 2026
95120c7
chg: delete case_insensitivity xls test fixture
lindsay-stevens Jul 14, 2026
4878e2a
chg: replace choice name "type" xls test with equivalent md tests
lindsay-stevens Jul 14, 2026
d8f7b7a
chg: replace choice name match xls test with equivalent md test
lindsay-stevens Jul 14, 2026
95787bf
chg: replace static default xls/xform test with improved md test
lindsay-stevens Jul 15, 2026
3e012d1
fix: delete unknown question type xls test covered by other tests
lindsay-stevens Jul 15, 2026
426dcb5
chg: replace style settings xls tests with equivalent md tests
lindsay-stevens Jul 16, 2026
d9c17e3
fix: delete xml escaping xls test covered by other tests
lindsay-stevens Jul 16, 2026
243dda1
fix: replace hidden question type xls test with equivalent md test
lindsay-stevens Jul 16, 2026
2c00a11
chg: replace include question type xls tests with equivalent md tests
lindsay-stevens Jul 16, 2026
041c84a
fix: delete tutorial xls test covered by other tests
lindsay-stevens Jul 16, 2026
29cf8c2
fix: delete specify_other xls tests covered by other tests
lindsay-stevens Jul 17, 2026
9d88f2c
fix: delete gps.csv test fixture not referenced anywhere
lindsay-stevens Jul 17, 2026
b75cc8f
fix: delete gps xls test covered by other tests
lindsay-stevens Jul 17, 2026
6701b92
chg: replace table-list xls test with equivalent md tests
lindsay-stevens Jul 17, 2026
16d6ff6
chg: replace sms_info xls test with equivalent md test
lindsay-stevens Jul 17, 2026
2a58710
chg: delete simple_loop xls test covered by other tests
lindsay-stevens Jul 17, 2026
c3a08bc
chg: convert widgets and repeat_date_test from xls to xlsx
lindsay-stevens Jul 17, 2026
46ebebb
chg: swap remaining xls tests to use existing equivalent xlsx file
lindsay-stevens Jul 17, 2026
53d4a0e
chg: remove xls support and xlrd dependency
lindsay-stevens Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python: ['3.12']
python: ['3.13']
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}

# Install dependencies.
- uses: actions/cache@v4
- uses: actions/cache@v6
name: Python cache with dependencies.
id: python-cache
with:
Expand All @@ -35,7 +35,7 @@ jobs:
- name: Publish release to PyPI
if: success()
run: |
pip install flit==3.9.0
pip install flit==3.12.0
flit --debug publish --no-use-vcs
env:
FLIT_USERNAME: __token__
Expand Down
22 changes: 11 additions & 11 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python: ['3.12']
python: ['3.13']
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}

# Install dependencies.
- uses: actions/cache@v4
- uses: actions/cache@v6
name: Python cache with dependencies.
id: python-cache
with:
Expand All @@ -39,24 +39,24 @@ jobs:
# Run all matrix jobs even if one of them fails.
fail-fast: false
matrix:
python: ['3.10', '3.11', '3.12', '3.13']
python: ['3.11', '3.12', '3.13', '3.14']
os: [ubuntu-latest, macos-latest, windows-latest]
PYXFORM_TESTS_RUN_ODK_VALIDATE: ['false']
# Extra job to check PyxformTestCase tests pass with ODK Validate.
# Keep versions sync with default dev version (same as 'lint' job above).
include:
- python: '3.12'
- python: '3.13'
os: ubuntu-latest
PYXFORM_TESTS_RUN_ODK_VALIDATE: 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}

# Install dependencies.
- uses: actions/cache@v4
- uses: actions/cache@v6
name: Python cache with dependencies.
id: python-cache
with:
Expand All @@ -78,11 +78,11 @@ jobs:
- name: Build sdist and wheel.
if: success() && matrix.PYXFORM_TESTS_RUN_ODK_VALIDATE == 'false'
run: |
pip install flit==3.9.0
pip install flit==3.12.0
flit --debug build --no-use-vcs
- name: Upload sdist and wheel.
if: success() && matrix.PYXFORM_TESTS_RUN_ODK_VALIDATE == 'false'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: pyxform--on-${{ matrix.os }}--py${{ matrix.python }}
path: ${{ github.workspace }}/dist/pyxform*
210 changes: 210 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# pyxform

`pyxform` is a Python library that simplifies writing forms for ODK Collect and Enketo by converting spreadsheets that follow the [XLSForm standard](http://xlsform.org/) into [ODK XForms](https://github.com/opendatakit/xforms-spec). The XLSForms format is used in a [number of tools](http://xlsform.org/en/#tools-that-support-xlsforms).

## Project status

`pyxform` is actively maintained by [ODK](https://getodk.org/about/team.html).

Current goals for the project include:

- Enable more complex workflows through sophisticated XPath expressions and [entities](https://getodk.github.io/xforms-spec/entities)
- Improve error messages and make troubleshooting easier
- Improve experience, particularly for multi-language forms

`pyxform` was started at the [Sustainable Engineering Lab at Columbia University](https://qsel.columbia.edu/), and until 2018 was maintained primarily by [Ona](https://ona.io/).

## Using `pyxform`

For user support, please start by posting to [the ODK forum](https://forum.getodk.org/c/support/6) where your question will get the most visibility.

There are 3 main ways that `pyxform` is used:

- Through a form server, such as the [pyxform-http service wrapper](https://github.com/getodk/pyxform-http), or [ODK Central](https://docs.getodk.org/getting-started/).
- The command line utility `xls2xform`, which can be helpful for troubleshooting or as part of a broader form creation pipeline.
- As a library, meaning that another python project imports functionality from `pyxform`.

### Running the latest release of pyxform

To convert forms at the command line, the latest official release of pyxform can be installed using [pip](https://en.wikipedia.org/wiki/Pip_(package_manager)):

pip install pyxform

The `xls2xform` command can then be used:

xls2xform path_to_XLSForm [output_path]

The currently supported Python versions for `pyxform` are 3.11 to 3.14 (the primary development version is 3.13). If this is different from the version you use for other projects, consider using [pyenv](https://github.com/pyenv/pyenv) to manage multiple versions of Python.

### Running pyxform from local source

Note that you must uninstall any globally installed `pyxform` instance in order to use local modules. Please install java 8 or newer version.

From the command line, complete the following. These steps use a virtualenv to make dependency management easier, and to keep the global site-packages directory clean:

# Get a copy of the repository.
mkdir -P ~/repos/pyxform
cd ~/repos/pyxform
git clone https://github.com/XLSForm/pyxform.git repo

# Create and activate a virtual environment for the install.
/usr/local/bin/python -m venv venv
. venv/bin/activate

# Install the pyxform and it's production dependencies.
(venv)$ cd repo
# If this doesn't work, upgrade pip ``pip install --upgrade pip`` and retry.
(venv)$ pip install -e .
(venv)$ python pyxform/xls2xform.py --help
(venv)$ xls2xform --help # same effect as previous line
(venv)$ which xls2xform # ~/repos/pyxform/venv/bin/xls2xform

To leave and return to the virtualenv:

(venv)$ deactivate # leave the venv, scripts not on $PATH
$ xls2xform --help
# -bash: xls2xform: command not found
$ . ~/repos/pyxform/venv/bin/activate # reactivate the venv
(venv)$ which xls2xform # scripts available on $PATH again
~/repos/pyxform/venv/bin/xls2xform

### Installing pyxform from remote source

`pip` can install from the GitHub repository. Only do this if you want to install from the master branch, which is likely to have pre-release code. To install the latest release, see above.:

pip install git+https://github.com/XLSForm/pyxform.git@master#egg=pyxform

You can then run xls2xform from the commandline:

xls2xform path_to_XLSForm [output_path]

## Development

To set up for development / contributing, first complete the above steps for "Running pyxform from local source". Then repeat the command used to install pyxform, but with `[dev]` appended to the end, e.g.:

pip install -e .[dev]

You can run tests with:

python -m unittest

Before committing, make sure to format and lint the code using `ruff`:

ruff format pyxform tests
ruff check pyxform tests

If you are using a copy of `ruff` outside your virtualenv, make sure it is the same version as listed in `pyproject.toml`. Use the project configuration for `ruff` in `pyproject.toml`, which occurs automatically if `ruff` is run from the project root (where `pyproject.toml` is).

### Contributions

We welcome contributions that have a clearly-stated goal and are tightly focused. In general, successful contributions will first be discussed on [the ODK forum](https://forum.getodk.org/) or in an issue. We prefer discussion threads on the ODK forum because `pyxform` issues generally involve considerations for other tools and specifications in ODK and its broader ecosystem. Opening up an issue or a pull request directly may be appropriate if there is a clear bug or an issue that only affects `pyxform` developers.

### Writing tests

Make sure to include tests for the changes you're working on. When writing new tests you should add them in `tests` folder. Add to an existing test module, or create a new test module. Test modules are named after the corresponding source file, or if the tests concern many files then module name is the topic or feature under test.

When creating new test cases, where possible use `PyxformTestCase` as a base class instead of `unittest.TestCase`. The `PyxformTestCase` is a toolkit for writing XLSForms as MarkDown tables, compiling example XLSForms, and making assertions on the resulting XForm. This makes code review much easier by putting the XLSForm content inline with the test, instead of in a separate file. A `unittest.TestCase` may be used if the new tests do not involve compiling an XLSForm (but most will). Do not add new tests using the old style `XFormTestCase`.

When writing new `PyxformTestCase` tests that make content assertions, it is strongly recommended that the `xml__xpath*` matchers are used, in particular `xml__xpath_match`. Most older tests use matchers like `xml__contains` and `xml__excludes`, which are simple string matches of XML snippets against the result XForm. The `xml__xpath_match` kwarg accepts an XPath expression and expects 1 match. The main benefits of using XPath are 1) it allows specifying a document location, and 2) it does not require a particular document order for elements or attributes or whitespace output. To take full advantage of 1), the XPath expressions should specify the full document path (e.g. `/h:html/h:head/x:model`) rather than a search (e.g. `.//x:model`). To take full advantage of 2), the expression should include element predicates that specify the expected attribute values, e.g. `/h:html/h:body/x:input[@ref='/trigger-column/a']`. To specify the absence of an element, an expression like the following may be used with `xml__xpath_match`: `/h:html[not(descendant::x:input)]`, or alternatively `xml__xpath_count`: `.//x:input` with an expected count of 0 (zero).

## Documentation

For developers, `pyxform` uses docstrings, type annotations, and test cases. Most modern IDEs can display docstrings and type annotations in an easily navigable format, so no additional docs are compiled (e.g. sphinx). In addition to the user documentation, developers should be familiar with the <span class="title-ref">ODK XForms Specification https://getodk.github.io/xforms-spec/</span>.

For users, `pyxform` has documentation at the following locations:

- [XLSForm docs](https://xlsform.org/)
- [XLSForm template](https://docs.google.com/spreadsheets/d/1v9Bumt3R0vCOGEKQI6ExUf2-8T72-XXp_CbKKTACuko/edit#gid=1052905058)
- [ODK Docs](https://docs.getodk.org/)

## Change Log

[Changelog](CHANGES.txt)

## Releasing pyxform

1. Make sure the version of ODK Validate in the repo is up-to-date:

pyxform_validator_update odk update ODK-Validate-vx.x.x.jar

2. Run all tests through ODK Validate as follows:

> PYXFORM_TESTS_RUN_ODK_VALIDATE=true python -m unittest --verbose

3. Draft a new GitHub release with the list of merged PRs. Follow the title and description pattern of the previous release.

4. Checkout a release branch from latest upstream master.

5. Update `CHANGES.txt` with the text of the draft release.

6. Update `pyproject.toml`, `pyxform/__init__.py` with the new release version number.

7. Commit, push the branch, and initiate a pull request. Wait for tests to pass, then merge the PR.

8. Tag the release and it will automatically be published

## Manually releasing

Releases are now automatic. These instructions are provided for forks or for a future change in process.

1. In a clean new release only directory, check out master.

2. Create a new virtualenv in this directory to ensure a clean Python environment:

/usr/local/bin/python -m venv pyxform-release
. pyxform-release/bin/activate

3. Install the production and packaging requirements:

pip install -e .
pip install flit==3.12.0

4. Clean up build and dist folders:

rm -rf build dist pyxform.egg-info

5. Prepare `sdist` and `bdist_wheel` distributions, and publish to PyPI:

flit --debug publish --no-use-vcs

6. Tag the GitHub release and publish it.

## Related projects

These projects are not vetted or endorsed but are linked here for reference.

**Converters**

*To XLSForm*

- [cueform](https://github.com/freddieptf/cueform) (Go): from CUE
- [md2xlsform](https://github.com/joshuaberetta/md2xlsform) (Python): from MarkDown
- [xlsform](https://github.com/networkearth/xlsform) (Python): from JSON
- [yxf](https://github.com/Sjlver/yxf) (Python): from YAML

*From XLSForm*

- [ODK2Doc](https://github.com/zaeendesouza/ODK2Doc) (R): to Word
- [OdkGraph](https://github.com/jkpr/OdkGraph) (Python): to a graph
- [Pureser](https://github.com/SwissTPH/Pureser) (Swift): to HTML
- [ppp](https://github.com/pmaengineering/ppp) (Python): to HTML, PDF, Word
- [QuestionnaireHTML](https://github.com/hedibmustapha/QuestionnaireHTML) (R): to HTML
- [xlsform-converter](https://github.com/wq/xlsform-converter) (Python): to Django modules
- [xlsform](https://github.com/networkearth/xlsform) (Python): to JSON
- [xlsform2json](https://github.com/owengrant/xlsform2json) (Java): to JSON
- [XLSform2PDF](https://github.com/HEDERA-PLATFORM/XLSform2PDF) (Python): to PDF
- [xlson](https://github.com/opensrp/xlson) (Python): to OpenSRP JSON
- [yxf](https://github.com/Sjlver/yxf) (Python): to YAML

**Management Tools**

- [surveydesignr](https://github.com/williameoswald/surveydesignr) (R): compare XLSForms
- [ipacheckscto](https://github.com/PovertyAction/ipacheckscto) (Stata): check XLSForm for errors or design issues
- [kobocruncher](https://github.com/Edouard-Legoupil/kobocruncher) (R): generate analysis Rmd from XLSForm
- [odkmeta](https://github.com/PovertyAction/odkmeta) (Stata): use XLSForm to import ODK data to Stata
- [odktools](https://github.com/ilri/odktools) (C++): convert pyxform internal data model to MySQL
- [pmix](https://github.com/pmaengineering/pmix) (Python): manage XLSForm authoring
- [pyxform-docker](https://github.com/seadowg/pyxform-docker) (Dockerfile): image for pyxform development
- [xform-test](https://github.com/PMA-2020/xform-test) (Java): test XLSForms
- [xlsformpo](https://github.com/delcroip/xlsformpo) (Python): use .po files for XLSForm translations
- [XlsFormUtil](https://github.com/unhcr-americas/XlsFormUtil) (R): manage XLSForm authoring
Loading