Skip to content

[QTI] Consolidate QTIEditor DOM parsing on parseXML and support text/html #6044

Closed

Description

@AlexVelezLl

❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Overview

parseXML in QTIEditor/serialization/parseItem.js already wraps DOMParser with the one thing raw DOMParser doesn't give you: a real failure signal. DOMParser never throws on malformed input — it returns a document containing a <parsererror> node — so every call site that skips that check silently proceeds with a broken document.

Most QTIEditor code doesn't go through parseXML. It instantiates new DOMParser() directly, so the parsererror check is inconsistently applied and the parser setup is duplicated across the module. Consolidating on parseXML makes malformed-XML handling uniform and gives us one place to change parsing behavior.

parseXML currently hardcodes 'text/xml', but several call sites legitimately need 'text/html' (HTML fragment assembly, sanitization). It needs to accept the mime type to be usable by them.

Complexity: Low
Target branch: unstable

Context

parseXML (serialization/parseItem.js:13) parses as 'text/xml' and throws on a parsererror node. Direct DOMParser use in QTIEditor today:

Production code

File Line Parsed as Notes
interactions/choice/ChoiceInteractionDescriptor.js 31 text/xml no parsererror check
serialization/assembleItem.js 41 text/html HTML fragment assembly
serialization/assembleItem.js 83, 88, 93 text/xml separate declParser instance
serialization/qti/QTISanitizer.js 94 text/html

Tests and helpers

interactions/choice/__tests__/ChoiceInteractionDescriptor.spec.js (16, 28, 40), interactions/choice/__tests__/parse.spec.js (149), serialization/__tests__/assembleItem.spec.js (79, 155, 174, 186, 215, 228, 236, 247), serialization/qti/__tests__/testUtils.js (4).

One wrinkle worth knowing before starting: parsererror detection is XML-only. The HTML parser never fails — it recovers silently — so a text/html parse would never produce a parsererror node, and an HTML document that happens to contain an element named parsererror would trip a naive check. The error check has to apply only to XML parsing.

The Change

Consolidate QTIEditor's XML and HTML parsing onto parseXML so that parser setup and malformed-input handling live in one place.

parseXML gains a mime type parameter, defaulting to 'text/xml' so existing callers are unaffected. Callers that need HTML fragment semantics pass 'text/html'. The parsererror check applies only when parsing as XML, since the HTML parser never signals failure that way.

parseXML stays in serialization/parseItem.js; call sites import it from there.

Every direct new DOMParser() in QTIEditor — production code and tests alike — routes through parseXML instead. Call sites that currently skip the parsererror check inherit it, which is the point: ChoiceInteractionDescriptor.js:31 parsing a response declaration with no validation is exactly the case where a malformed document should surface rather than propagate.

pasteTransform.js in TipTapEditor is left alone. It's outside QTIEditor, and pointing a shared editor at QTIEditor/serialization/ would work against #5970's goal of keeping QTIEditor extractable as a standalone package.

Out of Scope

  • TipTapEditor/utils/pasteTransform.js:78 — outside QTIEditor; converting it would create a cross-feature import into QTIEditor/serialization/.
  • serialization/assembleItem.js:11 — parses '<root/>' as a document factory for createElement rather than to parse real input; leave as-is.
  • Moving parseXML to a different module. It stays in parseItem.js.

Acceptance Criteria

General

  • parseXML accepts a mime type argument, defaulting to 'text/xml' so existing callers are unchanged
  • The parsererror check runs only when parsing as XML; 'text/html' parses leniently without throwing
  • parseXML remains exported from serialization/parseItem.js
  • QTIEditor parses through parseXML except where a bare Document is needed as an element factory
  • ChoiceInteractionDescriptor.js:31, assembleItem.js:41, assembleItem.js:83/88/93, and qti/QTISanitizer.js:94 all parse via parseXML, each passing the mime type it needs
  • Call sites that previously parsed without a parsererror check now surface malformed input as a thrown error
  • QTIEditor tests and test helpers parse via parseXML rather than constructing their own DOMParser

Testing

  • parseXML unit tests cover: default XML parsing, explicit 'text/xml', 'text/html', malformed XML throwing, and malformed HTML not throwing
  • Existing QTIEditor serialization and interaction tests pass unchanged

References

AI usage

This issue was drafted with Claude Code (Opus 4.8) from a code review of #6012. I described the change I wanted; Claude surveyed the DOMParser call sites, surfaced the XML-vs-HTML error-handling asymmetry, and drafted the sections. I reviewed and corrected the scope and criteria before filing.

Activity

  1. added theissue type on Jul 16, 2026
  2. rtibblesbot commented on Jul 16, 2026

    @rtibblesbot
    Contributor

    Plan: Give parseXML a mime-type parameter (XML-only parsererror check) and route every direct new DOMParser() in QTIEditor through it, except the one bare-Document element factory.

    • parseXML gains a mime-type parameter with an XML-only error check
    • Route QTISanitizer.stripTags through parseXML('text/html')
    • Route the test helper testUtils.js through the shared parseXML
    • Full-tree verification — no stray direct DOMParser, whole suite green

    @rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

    How was this generated?
    • Implemented code following the pre-approved plan using test-driven development (red-green-refactor)
    • Ran the project's full test suite and linter before committing
    • For UI changes: captured live screenshots to verify rendering
    • For backend changes: verified with example API/CLI output
  3. rtibblesbot commented on Jul 16, 2026

    @rtibblesbot
    Contributor

    The bot has been waiting for a response for 2 hour(s). It will resume automatically when someone replies.

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions