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
  • exportItemXml
  • exportItemXmlDoc

It also exports the roundtrip transform and export option types so callers can type their integration code precisely, plus two prompt-handling helpers: ensureInteractionPrompts and 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

Some editor schemas require a qti-prompt as the first child of block interactions such as order, match (including its tabular variant), associate, and gap-match — so authors always see a prompt slot to fill in, even though QTI 3.0 itself makes qti-prompt optional on these interactions. That mismatch needs a transform on both sides of the roundtrip:

  • On import, ensureInteractionPrompts(schema) derives the set of interaction tags that require a leading prompt from the supplied schema, and injects an empty <qti-prompt><p/></qti-prompt> into any matching interaction whose source XML omits one. This is opt-in — append it to RoundtripImportOptions.transforms yourself, since only your schema knows whether a prompt is required:

    import { importItemFromString, defaultRoundtripTransforms, ensureInteractionPrompts } from '@citolab/prose-qti/item-roundtrip';
    const node = importItemFromString(xml, schema, {
    transforms: [...defaultRoundtripTransforms, ensureInteractionPrompts(schema)],
    });
  • On export, stripEmptyPrompts 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’s part of defaultRoundtripExportTransforms and runs automatically; pass { transforms: [] } to disable it, or your own list to customize.

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.