Skip to main content

Text-to-QTI Converter

Tests PyPI version Python Versions License: MIT Code style: black Built with Claude

Convert markdown-based quiz files to QTI format for Canvas LMS import.

Overview

Text-to-QTI is a Python CLI tool that converts simple markdown quiz files into QTI (Question and Test Interoperability) packages compatible with Canvas LMS. It supports multiple choice and true/false questions with optional feedback.

Features

  • 📝 Simple markdown syntax for quiz authoring
  • ✅ Pre-parsing syntax validation with helpful error messages
  • 🎯 Support for Multiple Choice and True/False questions
  • 📦 Canvas-compatible QTI 1.2 ZIP package generation
  • 🔧 Extensible architecture for adding new question types
  • 🎨 Colorful CLI with progress indicators
  • 📊 Configurable metadata (title, description, points, shuffle)

Installation

From PyPI (Recommended)

pip install text-to-qti

From GitHub

pip install git+https://github.com/tableaprogramming-rgb/textmd-to-qti.git

From Source (Development)

git clone https://github.com/tableaprogramming-rgb/textmd-to-qti.git
cd textmd-to-qti
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install -e .

Requirements

  • Python 3.8+
  • lxml
  • pydantic
  • click
  • rich
  • markdown
  • PyYAML

Quick Start

1. Create a Quiz File

Create a file quiz.txt:

---
title: My First Quiz
description: A simple quiz about geography
points_per_question: 1
shuffle_answers: false
---

## Question 1
[Type: multiple_choice]

What is the capital of France?

a) London
b) Berlin
*c) Paris
d) Madrid

Feedback: Paris is the capital and largest city of France.

## Question 2
[Type: true_false]

The Earth is flat.

*a) False
b) True

Feedback: The Earth is an oblate spheroid.

2. Validate (Optional)

text-to-qti validate quiz.txt

3. Convert to QTI

text-to-qti convert quiz.txt -o quiz.zip

4. Import to Canvas

  1. Go to your Canvas course
  2. Navigate to Quizzes
  3. Click Import Existing Content
  4. Select QTI .zip file
  5. Upload quiz.zip

Syntax Guide

File Structure

Every quiz file has:

  1. Optional YAML front matter with metadata
  2. Section headers (optional) for organization
  3. Questions with metadata and answer choices

YAML Front Matter

---
title: Quiz Title (required)
description: Optional description
points_per_question: 1 (default)
shuffle_answers: false (default)
---

Question Format

## Question N
[Type: question_type]
[Points: number]
[ID: custom_id]

Question text here.

a) Answer choice 1
b) Answer choice 2
*c) Correct answer
d) Answer choice 4

Feedback: Optional feedback shown after answering.

Metadata Tags

  • [Type: multiple_choice] or [Type: true_false] (required)
  • [Points: number] - Points for this question (default: from metadata)
  • [ID: custom_id] - Custom question ID (default: auto-generated)
  • Feedback: - Feedback text shown after answering (optional)

Answer Choices

  • Each choice starts with a letter: a), b), c), etc.
  • Letters must be sequential
  • Exactly one answer must be marked as correct with *
    • *a) Correct answer
    • b) Wrong answer
  • For True/False: must have exactly 2 choices

Text Formatting

Question text supports markdown:

## Question 1
[Type: multiple_choice]

This is **bold** text and *italic* text.

You can use `inline code` or [links](https://example.com).

a) First choice
*b) Correct choice

Comments

HTML comments are ignored:

<!-- This comment will be ignored -->

## Question 1
[Type: multiple_choice]

CLI Commands

Convert Command

text-to-qti convert INPUT_FILE [OPTIONS]

Options:
  -o, --output PATH         Output ZIP file path (default: output.zip)
  --validate-only          Only validate syntax, don't generate
  --qti-version {1.2,2.1}  QTI version (default: 1.2)

Validate Command

text-to-qti validate INPUT_FILE

Validates syntax without generating QTI.

Examples

Simple Multiple Choice

---
title: Basic Math Quiz
---

## Question 1
[Type: multiple_choice]

What is 2 + 2?

a) 3
*b) 4
c) 5

True/False with Custom Points

---
title: Science Quiz
points_per_question: 2
---

## Question 1
[Type: true_false]
[Points: 5]

Photosynthesis requires sunlight.

*a) True
b) False

Feedback: Photosynthesis is the process plants use to convert light energy into chemical energy.

Mixed Question Types

---
title: Comprehensive Quiz
description: Tests various knowledge areas
points_per_question: 1
---

# Section 1: Multiple Choice

## Question 1
[Type: multiple_choice]

What programming language is known for web development?

a) Python
b) Go
*c) JavaScript
d) Rust

## Question 2
[Type: multiple_choice]

Which is NOT a relational database?

a) PostgreSQL
b) MySQL
c) Oracle
*d) MongoDB

# Section 2: True/False

## Question 3
[Type: true_false]

Git is a distributed version control system.

*a) True
b) False

## Question 4
[Type: true_false]

REST APIs always use JSON.

a) True
*b) False

Error Messages

The validator provides helpful error messages:

ERROR: Line 15: Question 1: No correct answer specified.
  Mark correct answer with * (e.g., *c) Correct answer)

ERROR: Line 20: Question 2: Answer letters must be sequential (a, b, c, ...).
  Found: a, c

ERROR: Line 25: Question 3: True/False questions must have exactly 2 choices, found 3

Canvas Import Guide

  1. Prepare your quiz file - Use the syntax guide above
  2. Validate - Run text-to-qti validate quiz.txt to check for errors
  3. Convert - Run text-to-qti convert quiz.txt to create output.zip
  4. Import to Canvas:
    • Go to your course
    • Click Settings → Import Existing Content
    • Select QTI .zip file
    • Upload the ZIP file
    • Review the imported quiz in your question bank or quizzes

Architecture

The project is designed with extensibility in mind:

Input Text File
      ↓
   [Parser] ──→ Validates & parses markdown
      ↓
[Question Models] ──→ Pydantic validated data
      ↓
[Item Generators] ──→ Pluggable generators per question type
      ↓
[QTI XML Builders] ──→ Creates assessment & manifest
      ↓
[ZIP Packager] ──→ Creates Canvas-ready package
      ↓
Output QTI ZIP

Adding New Question Types

To add a new question type:

  1. Create a new generator class inheriting from BaseItemGenerator
  2. Implement the generate() method
  3. Register with the main generator

Example (in your code):

from text_to_qti.qti.base_item import BaseItemGenerator
from text_to_qti.qti.generator import QTIGenerator

class FillInBlankGenerator(BaseItemGenerator):
    def generate(self, question):
        # Custom implementation
        pass

# Use it
generator = QTIGenerator(quiz)
generator.register_item_generator(
    QuestionType.FILL_IN_BLANK,
    FillInBlankGenerator()
)

Development

Install Development Dependencies

pip install -e ".[dev]"

Run Tests

pytest                    # Run all tests
pytest -v               # Verbose output
pytest --cov           # With coverage report

Code Quality

black src/              # Format code
ruff check src/         # Lint
mypy src/              # Type checking

Limitations

Current MVP limitations (Phase 1):

  • Only supports Multiple Choice and True/False questions
  • QTI 1.2 format only (2.1 support coming)
  • No image/media support yet
  • No question shuffling within quiz (only answer shuffling)
  • No partial credit support
  • No essay/free-form response questions

Troubleshooting

"File must be UTF-8 encoded"

Save your quiz file as UTF-8. In most editors:

  • VS Code: Click encoding in bottom right, select UTF-8
  • Sublime: File → Save with Encoding → UTF-8
  • macOS: Use TextEdit → Format → Plain Text, then save

"No questions found"

Ensure questions start with ## Question N where N is a number.

"Invalid question type"

Ensure you use exactly: [Type: multiple_choice] or [Type: true_false]

"Answer letters must be sequential"

Answers must be a, b, c, d, etc. in order. You can't skip letters.

Contributing

Contributions are welcome and appreciated! Please see CONTRIBUTING.md for:

  • Development setup instructions
  • How to run tests locally
  • Code style guidelines
  • How to submit pull requests
  • Reporting bugs and suggesting features

Acknowledgments

This project was made possible with Claude, an AI assistant by Anthropic. Claude contributed significantly to:

  • Architecture Design - Conceptualized the overall system design for QTI generation
  • Implementation - Built the complete markdown-to-QTI conversion pipeline
  • Testing - Developed comprehensive test suite (57 tests, 95%+ coverage)
  • Documentation - Created all guides, templates, and setup documentation
  • DevOps - Configured GitHub Actions CI/CD and PyPI publishing automation
  • Best Practices - Implemented repository standards and security scanning

We're grateful for Claude's essential contributions to making educational technology more accessible.

License

MIT License - See LICENSE file for details

Support

For issues, questions, or suggestions:

  1. Check the troubleshooting section
  2. Review examples for similar use cases
  3. Check existing issues on GitHub
  4. Create a new issue with details

Changelog

See CHANGELOG.md for the full version history and release notes.

Latest Version: 0.1.0 (Initial MVP)

  • ✅ Markdown parser with YAML metadata
  • ✅ Multiple choice and true/false support
  • ✅ Syntax validation with error reporting
  • ✅ QTI 1.2 package generation
  • ✅ Canvas LMS compatibility
  • ✅ CLI with progress indicators
  • ✅ Extensible architecture for question types

Roadmap

Phase 2 (Coming Soon)

  • Fill-in-the-blank questions
  • Multiple answer questions
  • Image/media support
  • QTI 2.1 format support
  • Quiz import/editing from Canvas

Phase 3 (Future)

  • Web UI for quiz creation
  • Question bank management
  • CSV/Excel import
  • Question randomization
  • Partial credit support

Made with ❤️ for educators

Metadata

Release files for text-to-qti 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for text-to-qti 0.1.1
File Size Uploaded
text_to_qti-0.1.1.tar.gz 37.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for text-to-qti 0.1.1
File Interpreter ABI Platform
text_to_qti-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 68.6 kB

Release files / text_to_qti-0.1.1.tar.gz

Download URL text_to_qti-0.1.1.tar.gz
Size 37.7 kB
Tags Source
SHA-256 checksum
How to use checksums
b9ee27dcc719bde0eddef3e7788f665644f66765cc9d537f23bfad0f02923dd7
BLAKE2b-256 checksum
How to use checksums
432893ae67006a5210e4cc88eaa0148ecbedf1fbea04289e934b418329aa8b42
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.14

Release files / text_to_qti-0.1.1-py3-none-any.whl

Download URL text_to_qti-0.1.1-py3-none-any.whl
Size 30.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fb042852af8b0dcfb35ff3971aac98aa49e1bf3d23abff3ea8d6829d4bf05104
BLAKE2b-256 checksum
How to use checksums
e45096a6ac7c115fd24a4eba40588266463e8e8e8976ae1da228f220bdac4906
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.14

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page