You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Summary
convert_legacy_question_to_qti writes the item's language as language="en-US" on <qti-assessment-item>. QTI 3.0 has no such attribute — the schema declares xml:lang.
The item still validates, for two independent reasons, so nothing catches it.
The schema
AssessmentItemDType in utils/assessment/qti/schema/xsd/imsqti_itemv3p0p1_v1p0.xsd:
xml:lang is the language attribute. language is not declared.
Why validation passes anyway
extensionAnyTypeLax.Attr is a lax wildcard, so any unrecognised attribute on the
item validates silently.
The same XSD does carry a Schematron rule that would reject it — it enumerates the
legal attribute names and fails anything else with [RULE ROOT ELEMENT (qti-assessment-item): Assertion N] Invalid XML attribute in position N with name of …. validate_qti_item uses lxml.etree.XMLSchema, which does
not execute Schematron, so the rule never runs.
So validate_qti_item cannot currently catch any "well-structured but illegal attribute"
mistake. That is arguably the more valuable half of this issue.
Every fixture under contentcuration/tests/utils/qti/fixtures/ shows the same, e.g. single_selection.xml has language="en-US".
Impact
A migrated item's language is not where a QTI consumer looks for it. Nothing in Studio
reads it back (perseus_derive does not; archive.py takes the language from the
content node), so the effect is on what we hand to Kolibri and to any other QTI
consumer of a published package.
The QTI editor writes xml:lang (the spec attribute), so a converted item's language
currently has to be read from one attribute and written to another. Replace the legacy assessment editor with the QTI editor #6095 tolerates language on read for exactly this reason; that tolerance can be deleted once this is
fixed.
No data migration needed: consolidate() converts on read without persisting, so no
stored raw_data carries language=. Verified on a dev database — of 24 rows with raw_data, 0 contain language= and 24 contain xml:lang=. The global backfill
([QTI] Backfill all non-raw AssessmentItems to QTI items #6007) would persist it, so this is worth fixing before that runs.
Suggested fix
Serialise the pydantic AssessmentItem.language field as xml:lang
(utils/assessment/qti/assessment_item.py), and regenerate the fixtures.
Separately: make validate_qti_item run the XSD's Schematron rules — or add an
explicit allow-list check for the root element's attributes — so this class of mistake
fails a test rather than reaching a package.
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Summary
convert_legacy_question_to_qtiwrites the item's language aslanguage="en-US"on<qti-assessment-item>. QTI 3.0 has no such attribute — the schema declaresxml:lang.The item still validates, for two independent reasons, so nothing catches it.
The schema
AssessmentItemDTypeinutils/assessment/qti/schema/xsd/imsqti_itemv3p0p1_v1p0.xsd:xml:langis the language attribute.languageis not declared.Why validation passes anyway
extensionAnyTypeLax.Attris a lax wildcard, so any unrecognised attribute on theitem validates silently.
legal attribute names and fails anything else with
[RULE ROOT ELEMENT (qti-assessment-item): Assertion N] Invalid XML attribute in position N with name of ….validate_qti_itemuseslxml.etree.XMLSchema, which doesnot execute Schematron, so the rule never runs.
So
validate_qti_itemcannot currently catch any "well-structured but illegal attribute"mistake. That is arguably the more valuable half of this issue.
Reproducing
Every fixture under
contentcuration/tests/utils/qti/fixtures/shows the same, e.g.single_selection.xmlhaslanguage="en-US".Impact
reads it back (
perseus_derivedoes not;archive.pytakes the language from thecontent node), so the effect is on what we hand to Kolibri and to any other QTI
consumer of a published package.
xml:lang(the spec attribute), so a converted item's languagecurrently has to be read from one attribute and written to another. Replace the legacy assessment editor with the QTI editor #6095 tolerates
languageon read for exactly this reason; that tolerance can be deleted once this isfixed.
consolidate()converts on read without persisting, so nostored
raw_datacarrieslanguage=. Verified on a dev database — of 24 rows withraw_data, 0 containlanguage=and 24 containxml:lang=. The global backfill([QTI] Backfill all non-raw AssessmentItems to QTI items #6007) would persist it, so this is worth fixing before that runs.
Suggested fix
AssessmentItem.languagefield asxml:lang(
utils/assessment/qti/assessment_item.py), and regenerate the fixtures.validate_qti_itemrun the XSD's Schematron rules — or add anexplicit allow-list check for the root element's attributes — so this class of mistake
fails a test rather than reaching a package.