Skip to content

QTI Item Roundtrip

@citolab/prose-qti/item-roundtrip bundles the editor’s item import and export bridge. Use it when you want to move a single assessment item between QTI XML and ProseMirror without wiring the transform chain together yourself.

When to use this

Use this package when you need to:

  • import a QTI 3.0 item XML string or URL into an editor document
  • export a ProseMirror document back to a complete QTI assessment item
  • keep the import transform chain, schema parsing, and export composer in one place

What it provides

The package exposes a small set of convenience helpers:

  • importItemFromString
  • importItemFromUrl
  • importItemXmlDoc
  • itemBodyAndGapsFromString / itemBodyAndGapsFromUrl (see Scoring gaps below)
  • exportItemXml
  • exportItemXmlDoc

It also exports the roundtrip transform and export option types so callers can type their integration code precisely, plus the prompt-handling helper stripEmptyPrompts (see Prompt handling below).

Typical usage

Import an item from a URL:

import { importItemFromUrl } from '@citolab/prose-qti/item-roundtrip';
const node = await importItemFromUrl('https://example.com/items/item-1.xml', schema);

Export the current editor document:

import { exportItemXml } from '@citolab/prose-qti/item-roundtrip';
const xml = exportItemXml(editor.state.doc, schema, {
identifier: 'item-1',
title: 'Sample Item',
});

Prompt handling

qti-prompt is optional on block interactions, in QTI 3.0 and in this package’s schemas alike, so importing needs no transform for it. An item that omits a prompt parses as an interaction without one.

This used to say the opposite. The schemas required a leading qti-prompt — so that authors always saw a slot to fill in — and a pair of transforms bridged the gap: ensureInteractionPrompts injected an empty prompt on import, stripEmptyPrompts took it out again on export. The requirement was the bug: synthesising a prompt made a valid QTI item import as a document containing an element its author never wrote, which then exported back out as real markup. The roundtrip added content. Every interaction’s content expression now leads with qtiPrompt? and ensureInteractionPrompts is gone.

  • On export, stripEmptyPrompts remains, with a smaller and no longer symmetrical job: it removes any <qti-prompt> whose text content is empty or whitespace-only before the item-body is wrapped in <qti-assessment-item>. Since qti-prompt is 0..1 in QTI 3.0, dropping an empty one is lossless. It now cleans up after an author who left a prompt blank, rather than after us. It is part of defaultRoundtripExportTransforms and runs automatically; pass { transforms: [] } to disable it, or your own list to customize.

Scoring gaps

The editor’s ProseMirror schema only models three response-processing templates — match_correct, map_response, map_response_point. An item scored any other way still imports and exports cleanly, but is worth zero marks in the editor, with nothing to say so unless you ask.

itemBodyAndGapsFromString and itemBodyAndGapsFromUrl are the way to ask. They run the same import pipeline as importItemFromString / importItemFromUrl, but return both the item body and what the item’s response processing could not be represented as:

import { itemBodyAndGapsFromString } from '@citolab/prose-qti/item-roundtrip';
import { findUnrepresentableElements, TRANSPARENT_WRAPPER_TAGS } from '@citolab/prose-extensions/schema-gaps';
const { itemBody, scoringGaps } = itemBodyAndGapsFromString(xml);
const elementGaps = findUnrepresentableElements(schema, itemBody.documentElement, {
ignoreTags: TRANSPARENT_WRAPPER_TAGS,
});

scoringGaps is a SchemaGapOutcome from Schema Gaps — the same shape findUnrepresentableElements returns, so both can feed one notice. Silence is the expected result for items scored by the three standard models; a notice that fired on every import would teach readers to dismiss the one time it matters.

Notes

  • The caller still supplies the ProseMirror schema, because the editor schema is application-specific.
  • Import uses the standard QTI 3.0 roundtrip transforms before parsing into ProseMirror.
  • Export produces the same canonical item-body and full assessment-item XML used by the editor’s save flow.