Skip to content

Legacy conversion writes the item language as language= instead of xml:lang, and validation cannot catch it #6098

Description

@AlexVelezLl

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:

<xs:attribute name="identifier"     use="required" type="IdentifierDType" />
<xs:attribute name="title"          use="required" type="xs:normalizedString" />
<xs:attribute name="label"          use="optional" type="xs:normalizedString" />
<xs:attribute ref="xml:lang"        use="optional" />
<xs:attribute name="tool-name"      use="optional" type="xs:normalizedString" />
<xs:attribute name="tool-version"   use="optional" type="xs:normalizedString" />
<xs:attribute name="adaptive"       use="optional" default="false" type="xs:boolean" />
<xs:attribute name="time-dependent" use="required" type="xs:boolean" />
<xs:attributeGroup ref="extensionAnyTypeLax.Attr" />

xml:lang is the language attribute. language is not declared.

Why validation passes anyway

  1. extensionAnyTypeLax.Attr is a lax wildcard, so any unrecognised attribute on the
    item validates silently.
  2. 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.

Reproducing

from contentcuration.utils.assessment.qti.ingest import convert_legacy_question_to_qti
res = convert_legacy_question_to_qti({
    'type': 'single_selection', 'question': 'Q', 'answers': '[]', 'hints': '[]',
    'randomize': False, 'assessment_id': 'a' * 32, 'language': 'es',
})
# root carries language="es"; no xml:lang

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

  1. Serialise the pydantic AssessmentItem.language field as xml:lang
    (utils/assessment/qti/assessment_item.py), and regenerate the fixtures.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions