Word (.docx) to Moodle XML converter with graphical interface.
| File | Description |
|---|---|
converter_gui.py |
GUI application (PyQt5) |
universal_moodle_converter_v3_stable.py |
Converter core (CLI + library) |
table_compare.py |
Utility for comparing with reference XML |
Шаблоны вопросов_new.docx |
Documentation on Word file markup |
taskmanager/task-tracker.html |
Task history |
pip install PyQt5 PyQtWebEngine lxml python-docx docxlatex
python converter_gui.py
Or double-click Запустить конвертер.bat
python universal_moodle_converter_v3_stable.py <path to docx or folder> --output-folder <folder>
python table_compare.py
V1: Subject name (category)
{marker}V2: Block name (subcategory)
I:Task N. Author I.O., TZ X-Y, b=N
S: Question text
+: Correct answer
-: Incorrect answer
Marker is placed before V2: in format {marker}V2: Block description.
All questions in the block inherit the marker until the next V2:.
| Marker | Moodle Type | Description |
|---|---|---|
{multichoice_one} |
multichoice (single=true) | One correct answer. +: = 100%, -: = 0% |
{multichoice_many} |
multichoice (single=false) | Multiple correct answers. Penalty -100% for each incorrect |
{shortanswer_phrase} |
shortanswer | Text input. Multiple +: = multiple acceptable answers |
{numerical_partial} |
numerical | Multiple choice (numbered 1)2)3)...). All permutations with partial scoring: 100%/50%/0% |
{numerical_numcombo} |
numerical | Multiple choice. All position permutations = 100% |
{matching} / {match} |
matching | Matching. Format L1: / R1:. All L and R entries are used. Duplicate R = distractors |
{match_123} |
matching | Sequence. Format N: phrase -> phrase matched to number |
{ddmatch} |
ddmatch | Drag-and-drop. Format L1: / R1: |
{gapselect} |
gapselect | Dropdown lists. Text with (N), options A)...D), key +:ABCD |
{cloze} |
cloze | Embedded answers {1:SHORTANSWER:=answer} |
{numerical} |
shortanswer | Numeric answer. Generates two variants: with . and with , |
Notes:
{numerical_partial}and{numerical_numcombo}generatetype="numerical"in XML output (not "shortanswer"){matching}now uses all L and R entries (not just unique). Duplicate R entries become distractors
If marker is not specified, type is determined by heuristic based on content.
The converter recognizes 7 formats of question beginning:
I:Task N.— standardI I:Task N.— double I (Word artifact)I Task N.— space instead of colon:Task N.— missing I symbolTask N. Author, TZ X-Y, b=N— without I: prefixKn-=mTask N.— garbage before TaskAuthor I.O., TZ X-Y, b=N— only author (without Task word)
- File selection — "Browse" button for .docx
- Output folder selection — where to save XML
- Question selection — checkboxes to select which questions to convert
- "Выделить все" checkbox to select/deselect all
- Counter shows "Выбрано: X / Y"
- Question tree (50% width) + Moodle preview (50% width)
- Left: QTreeWidget with all questions, expandable content
- Right: Real-time preview of how question will look in Moodle (QWebEngineView)
- Click any question to see generated XML rendered as HTML
- Supports images (base64) and formulas
- Preview types:
- multichoice: Radio/checkbox with Moodle styling
- matching/match: Table with dropdown selects
- ddmatch: Drag-and-drop preview with left items and shuffled right targets
- gapselect: Dropdowns inline in question text at [[N]] positions
- cloze: Embedded gaps shown inline
- numerical/shortanswer: Input field with correct answer
- Preprocessing errors:
- Missing correct answer
- Empty question text
- Unknown marker
- Conversion in separate thread with progress bar
- XML post-processing:
- Root element check (
quiz) - Question type check (only valid Moodle types)
- Base64 images check (not empty)
- Check for
_IMAGE_/@@PLUGINFILE@@markers without files - Matching structure check (subquestion/answer)
- Gapselect check (selectoption)
- Answer presence check
- Root element check (
- XML splitting into parts up to 1 MB (checkbox)
| Color | Markers |
|---|---|
| Blue | multichoice_one, multichoice_many |
| Green | numerical_partial, numerical_numcombo, shortanswer_phrase |
| Orange | matching, match_123, match |
| Reddish | ddmatch |
| Violet | gapselect |
| Yellow | cloze |
| Turquoise | numerical |
ImageProcessor— extract images from docx (base64)FormulaProcessor— convert LaTeX formulas ($...$->\(...\))QuestionTypeDetector— determine question type (marker has priority over heuristic)XMLGenerator— generate Moodle XML:create_multichoice(single, penalty_wrong)— single/multi choicecreate_shortanswer(subject)— shortanswer + permutations + partial scoringcreate_shortanswer_numerical()— numeric answer (. and , variants)create_matching()— matching with distractorscreate_ddmatch()— drag-and-dropcreate_gapselect()— dropdown listscreate_cloze()— embedded answerscreate_numerical()— numerical (fallback)
MoodleConverter— docx parser + orchestrator
For questions with multiple correct/incorrect answers:
- Question text is numbered:
1),2),3)... instead of+:/-: - ALL permutations are generated (1, 2, 3, ..., 12, 13, ..., 654321):
- permutations('123456', 1) → 1,2,3,4,5,6
- permutations('123456', 2) → 12,13,14,...,21,23,24...
- permutations('123456', 6) → 654321
- Fraction:
- 100%: all correct digits, no incorrect
- 50%: ≥50% correct and no more than 1 incorrect OR all correct + 1 incorrect
- 0%: NOT included (only 100% and 50% answers are generated)
For questions with multiple correct/incorrect answers:
- Question text is numbered:
1),2),3)... instead of+:/-: - ALL permutations of correct answer positions are generated:
- 1 correct → position number (e.g., "3")
- multiple correct → all permutations (e.g., "356", "365", "536"...)
- All answers = 100%
If answer is a string of digits in shortanswer_phrase:
- Limit: maximum 7 digits (7! = 5040 permutations)
- 8+ digits: only one answer (8! = 40320 — too many)
- Phrases "in ascending order"/"in descending order" block permutations
File Questions Markers
questions-ИСТ 10кл 131 match, multichoice_one, numerical_partial, match_123
-----
Total: 131 Errors: 0
File Questions Markers
questions-AJ 10cl 615 multichoice_one, shortanswer_phrase, matching, gapselect
questions-AJ 8cl 456 multichoice_one, matching
questions-HIST 10cl 131 match, multichoice_one, numerical_numcombo, match_123
questions-FL 10cl 95 multichoice_one, matching, gapselect
questions-MATH 10cl 422 numerical, multichoice_one
questions-MATH 8cl 200 numerical, multichoice_one
questions-GER 10cl 95 multichoice_one, matching, gapselect
questions-SOC 10cl 375 multichoice_many, numerical_partial, match
questions-RU 10cl 510 multichoice_many, shortanswer_phrase, ddmatch, numerical_numcombo
questions-PHYS 10cl 230 multichoice_one, numerical
questions-LING 10cl 95 multichoice_one, shortanswer_phrase, matching, gapselect
-----
Total: 3224 Errors: 0
| # | Bug | Fix |
|---|---|---|
| 1 | <answer> outside <subquestion> in matching |
Moved inside <subquestion> |
| 2 | No distractors in matching | Added empty <subquestion> for extra R elements |
| 3 | gapselect not recognized for GER/FL/LING | Added format +: ABCD, fixed regex for 1.A) |
| 4 | shortanswer permutations duplication | Removed meaningless duplication |
| 5 | parse_answers_from_line crash (3 vs 2 groups) |
Fixed tuple unpacking |
| 6 | 8! = 40320 permutations for 8-digit answers | Limit reduced to 7 digits (7! = 5040) |
| 7 | "in ascending order" generated permutations | Added check blocking permutations |
| 8 | S:, I:, V1:, V2:, +:, -: in XML questiontext |
Added remove_service_markers() function on output |
| 9 | Line breaks not preserved | Used <br> between question parts |
| 10 | V2 subcategories only worked at file start | Added V1/V2 processing anywhere in file |
| 11 | Category/subcategory duplication when splitting XML | Added duplicate protection + file starts with last category |
| 12 | {numerical_numcombo} not working | Moved before partial, added numbering + combinations |
| 13 | {numerical_partial} not generating all variants | Replaced combinations with permutations |
| 14 | GUI "Select all" checkbox too large | Fixed to single row, fixed height 32px |
| 15 | Missing feedback elements in NUMERICAL_TEMPLATE | Added correctfeedback, partiallycorrectfeedback, incorrectfeedback |
| 16 | XML splitting - duplicate XML declaration | Fixed: each part has single <?xml> and proper </quiz> closing |
| 17 | {numerical_numcombo} - answers not added to XML | Added missing self.root.append(tree) after return |
| 18 | {numerical_numcombo} - wrong answer logic | Rewrote: takes answer from +: and generates all permutations of digits |
| 19 | {numerical_numcombo} - question text split with <br> |
Preserved original line breaks, keeps (1), (2), (3) intact |
| 20 | {numerical_partial} - duplicate text answers | Fixed: removes duplicate else branch, only numerical answers |
| 21 | {matching} - not all L and R entries used | Fixed: uses all entries, duplicate R = distractors |
| 22 | {gapselect} - options in one line not parsed | Added regex for A) ...; B) ...; C) ...; D) ... |
| 23 | {gapselect} - +:CBCAB key not recognized |
Added regex ^\+:[A-D]+$ without required space |
| 24 | {gapselect} - gaps (1)(2) not replaced with [[N]] | Used class GapReplacer for proper closure counter |
| 25 | {gapselect} - wrong [[N]] formula | Fixed formula: position = alpha[key[i]] + i * 4 |
| 26 | {gapselect} - gaps only in question text, not in S: intro | Combined intro_text + question_text_parts before replacement |
| 27 | {gapselect} - indentation lost | Fixed method indentation after refactoring |
| 28 | {gapselect} - Cyrillic А, Б, В, Г options not parsed | Added А-Г to all [A-D] regex patterns and alpha_map |
| 29 | GUI - added Moodle-like preview with QWebEngineView | Full CSS styling, images and formulas support |
| 30 | GUI - matching now works same as match | Added 'match' as alias for 'matching' |
| 31 | GUI - ddmatch drag-drop preview | Left items + shuffled right targets with styling |
| 32 | GUI - gapselect dropdowns inline in text | Replaced [[N]] with select elements at correct positions |
| 33 | GUI - images not showing in preview | Added image_data extraction and passing to XMLGenerator |