Read, write, and convert REFI-QDA qualitative data analysis project files
(.qdpx, .qde, .qdc) in Python.
REFI-QDA is the open interchange standard that lets a qualitative coding project (its documents, hierarchical codebook, coded segments, cases, variables, and memos) move between tools such as QualCoder, MAXQDA, ATLAS.ti, NVivo, Dedoose, and Taguette. refio gives that standard a clean, typed, well tested Python API and a command line tool, so you can script around coded data, convert between formats, and build other tools on top.
The REFI-QDA standard exists, but the Python tooling around it is almost empty:
the closest projects on GitHub are zero and one star, partial, single direction,
or tied to one application. There is no maintained library that reads and writes
the format both ways with tests. refio is that library. See ../OPPORTUNITIES.md
for the evidence and the market scan.
Core library:
- A typed model for the REFI-QDA elements that matter for interoperability: users, a hierarchical codebook (codes with color and description), text and PDF sources with coded selections, cases, variables, and notes (memos).
- A hardened XML reader that parses project documents safely (see Security).
- An XML writer that serializes the model back in the standard's element order.
- A
.qdpxarchive reader and writer that links each source to its file in the archive'sSourcesfolder, with zip bomb and zip slip protections. - A
.qdccodebook reader and writer for sharing coding schemes. - High level
load,save, andconvert_filethat pick the format by extension and round trip the project model.
Usability features:
- Tidy table helpers: flatten a project into codes, sources, and segments tables
as lists of rows, write them to CSV (standard library, no pandas needed), or get
a pandas DataFrame via the optional
pandasextra. - A structural validator that catches duplicate GUIDs, codings that point at missing codes, and out of range text offsets.
- A safe
extract_sourceshelper to unpack a.qdpxarchive's source files. - A command line tool:
inspect,codes,segments,tables,convert,validate, andversion.
Requires Python 3.10 or newer (developed and tested on 3.14).
python -m venv .venv
# Windows PowerShell: .venv\Scripts\Activate.ps1
# Git Bash / Linux / macOS: source .venv/bin/activate
pip install -e .Exact pinned environment:
pip install -r requirements-lock.txt
pip install -e . --no-depsOptional extras:
pip install -e ".[pandas]" # enables to_dataframe
pip install -e ".[dev]" # pytest and hypothesis for the test suiteimport refio
from refio.model import Project, Code, TextSource, PlainTextSelection, Coding, new_guid
# Build a project
project = Project.new("My study", author="Alice")
theme = Code(guid=new_guid(), name="Funding", color="#FF8800")
project.codebook.codes.append(theme)
text = "Community programs need sustained funding to work."
start = text.index("sustained funding")
src = TextSource(guid=new_guid(), name="Interview 1", plain_text=text)
src.selections.append(
PlainTextSelection(
guid=new_guid(), start=start, end=start + len("sustained funding"),
codings=[Coding(guid=new_guid(), code_guid=theme.guid)],
)
)
project.text_sources.append(src)
# Save as a .qdpx archive, then load it back
refio.save(project, "study.qdpx")
loaded = refio.load("study.qdpx")
print(loaded.summary())
# Flatten coded segments to rows or CSV
from refio import segments_table, write_csv
write_csv(segments_table(loaded), "segments.csv")refio inspect study.qdpx # summary counts as JSON
refio codes study.qdpx # the code hierarchy as a tree
refio segments study.qdpx # list coded segments
refio segments study.qdpx --csv segments.csv
refio tables study.qdpx --out out # codes.csv, sources.csv, segments.csv
refio convert study.qdpx study.qde # convert between formats
refio convert study.qdpx codes.qdc # export the codebook only
refio validate study.qdpx # structural check, non zero exit on errorsYou can also run it without installing the script: python -m refio inspect study.qdpx.
refio has no configuration file and reads no environment variables. Behavior is
controlled by function arguments and CLI flags. Safety ceilings for archive
handling (entry count, per entry size, total size) are defined in
refio/constants.py.
| Element | Read | Write |
|---|---|---|
| Project metadata, users | yes | yes |
| Hierarchical codes (color, description) | yes | yes |
| Text sources and plain text content | yes | yes |
| Plain text selections and codings | yes | yes |
| PDF sources (bytes) and PDF selections | yes | yes |
| Cases (source, selection, code refs, variable values) | yes | yes |
| Variables (typed) | yes | yes |
| Notes (memos) | yes | yes |
Formats: .qdpx (archive), .qde (project XML, text embedded for portability),
.qdc (codebook only). The reader matches elements by local name, so documents
with or without the namespace prefix, from REFI-QDA 1.0 or 1.5 producers, parse.
pip install -e ".[dev]"
pytestThe suite (37 tests) builds its sample data in memory, so no binary fixtures are checked in, and it runs fully offline. It covers the model, round trips through all three formats, parsing external namespaced and non namespaced XML, the tidy tables and CSV export, the validator, the CLI, and the security properties below.
model.pytyped dataclasses for the project.constants.pynamespace URIs, file roles, and archive safety ceilings.reader.pyhardened XML to model parsing.writer.pymodel to XML serialization.archive.py.qdpxread and write, with archive safety checks.qdc.pystandalone codebook read and write.convert.pyhigh level load, save, and convert.tables.pyflatten to rows, CSV, and optional DataFrame.validate.pystructural validation.cli.pythe Typer command line interface.
refio is built to read files that may be untrusted (a .qdpx shared by a
collaborator, or downloaded from a repository).
- XXE and entity expansion are blocked. The XML parser is configured with
resolve_entities=False,no_network=True, andload_dtd=False. External entities are never fetched (so a craftedfile://orhttp://entity cannot exfiltrate or fetch anything), and entity references are not expanded (so a billion laughs payload cannot blow up memory). Both are covered by tests. - Zip bomb protection. Before reading an archive, refio checks the number of entries, each entry's uncompressed size, and the total uncompressed size against configurable ceilings, and refuses archives that exceed them.
- Zip slip protection. When extracting a
.qdpxto disk, every member path is resolved and confirmed to stay inside the destination directory, so an entry named../../evilis refused rather than written outside the target. Reading source content into memory never touches the filesystem. There is a test that a traversal entry is refused and nothing is written outside the destination. - No code execution paths. There is no
eval, noexec, nopickle, and no untrusted deserialization. XML text is escaped by lxml on write, so source text or memo bodies cannot break the document structure. - No secrets, no network, no configuration side channels. refio reads no environment variables and makes no network calls. It operates only on the files you pass it.
MIT. See LICENSE. REFI-QDA is an open standard; refio is an independent
implementation and is not affiliated with the REFI consortium or any CAQDAS
vendor.