![]() |
VOOZH | about |
dotnet add package Citolab.QTI.Converter --version 0.1.1
NuGet\Install-Package Citolab.QTI.Converter -Version 0.1.1
<PackageReference Include="Citolab.QTI.Converter" Version="0.1.1" />
<PackageVersion Include="Citolab.QTI.Converter" Version="0.1.1" />Directory.Packages.props
<PackageReference Include="Citolab.QTI.Converter" />Project file
paket add Citolab.QTI.Converter --version 0.1.1
#r "nuget: Citolab.QTI.Converter, 0.1.1"
#:package Citolab.QTI.Converter@0.1.1
#addin nuget:?package=Citolab.QTI.Converter&version=0.1.1Install as a Cake Addin
#tool nuget:?package=Citolab.QTI.Converter&version=0.1.1Install as a Cake Tool
Small .NET library that:
Citolab.QTI.Uploader: core uploader abstractions + zip extractionCitolab.QTI.Converter: optional (pure .NET) converter implementation that converts QTI 2.x packages to QTI 3This repo includes a GitHub Actions workflow at .github/workflows/nuget.yml that builds/tests/packs on PRs and on pushes to main, and publishes to NuGet.org on:
v0.1.0 (uses the tag as the NuGet version), orversion input.To enable publishing, add NUGET_API_KEY as a repository secret.
Create an IQtiPackageStore that uploads to your storage, then call QtiPackageUploader.UploadAsync(...).
If you want automatic conversion for QTI 2.x packages, configure QtiUploaderOptions:
using Citolab.QTI.Converter;
using Citolab.QTI.Uploader;
var uploader = new QtiPackageUploader();
var options = new QtiUploaderOptions
{
ConvertQti2ToQti3 = true,
Converter = new Qti2ToQti3PackageConverter(
new Qti2ToQti3PackageConverterOptions
{
ItemTransformOptions = new QtiItemTransformOptions
{
// These are enabled by default; set to false to disable.
ObjectToImg = true,
ObjectToVideo = true,
ObjectToAudio = true,
SsmlSubToSpan = true,
StripMaterialInfo = true,
MinChoicesToOne = true,
ExternalScored = true,
// Optional extras (off by default).
QbCleanup = false,
DepConvert = false,
UpgradePci = false,
StripStylesheets = false
},
// Optional per-item hook for consumer-specific transformations.
OnItemTransformedAsync = async (transform, itemPath, ct) =>
{
// Example: add a marker attribute
await transform.FnChAsync(doc =>
{
doc.Root?.SetAttributeValue("data-processed-by", "my-app");
return Task.CompletedTask;
});
}
})
};
The converter uses the qti2xTo30.xsl XSLT 3.0 upgrader (embedded in the NuGet) when running on net9.0. For netstandard2.0, it falls back to a best-effort built-in conversion.
When converting an assessment item, the converter can apply a set of optional XML transformations via QtiTransform and QtiItemTransformOptions.
Default item transforms (enabled by default)
ObjectToImg: converts <object type="image/*" data="...">ALT</object> to <img src="..." alt="ALT" .../>.ObjectToVideo: converts <object type="video/*" ... data="..."> to <video><source .../></video> and normalizes existing <video controls> usage.ObjectToAudio: converts <object type="audio/*" ... data="..."> to <audio><source .../></audio> and normalizes existing <audio controls> usage.SsmlSubToSpan: converts SSML elements (e.g. <ssml:sub>, <ssml:break>, <ssml:say-as>, etc.) to <span> elements with data-ssml-* attributes.StripMaterialInfo: removes <qti-companion-materials-info>.MinChoicesToOne: ensures <qti-choice-interaction min-choices> is at least 1.ExternalScored: when there is no <qti-response-processing>, sets external-scored="human" on the SCORE outcome declaration.Additional item transforms (disabled by default)
QbCleanup: cleanup for common Question Builder (QB) HTML-ish wrappers (unwraps some <div> wrappers, span cleanup, etc.).DepConvert: converts Dutch Extension Profile dialog triggers (.dep-dialogTrigger) into HTML popover buttons.DepConvertExtended: converts DEP dialogs into a <dep-popup> structure (trigger + popup slot).HideInputsForChoiceInteractionWithImages: adds qti-input-control-hidden when all choices contain images.UpgradePci: upgrades TAO-exported portable custom interactions into a structure expected by QTI components (moves properties to data-*, normalizes modules/markup, etc.).StripStylesheets: removes <qti-stylesheet> nodes; supports wildcard matching via StripStylesheetsRemovePattern / StripStylesheetsKeepPattern.StylesheetsInlineAsync: fetches CSS content from external stylesheet URLs and inlines them into <qti-stylesheet> elements. Supports both HTTP URLs and relative file paths within QTI packages. Use OnItemTransformedAsync hook for custom async transformations like this.Usage example for StylesheetsInlineAsync with ZIP uploads:
OnItemTransformedAsync = async (transform, itemPath, fileResolver, ct) =>
{
// Inline external stylesheets from ZIP package files
await transform.StylesheetsInlineAsync(async (resolvedPath, currentItemPath) =>
{
// resolvedPath is the stylesheet path resolved relative to the item
// Use the provided fileResolver to get CSS content from ZIP package
return await fileResolver(resolvedPath);
}, itemPath);
// Alternative: if stylesheets are HTTP URLs, use custom HTTP client
await transform.StylesheetsInlineAsync(async href => {
if (href.StartsWith("http"))
{
using var client = new HttpClient();
var response = await client.GetAsync(href, ct);
return response.IsSuccessStatusCode
? await response.Content.ReadAsStringAsync()
: null;
}
// For relative paths, use the ZIP file resolver
var resolvedPath = Path.Combine(Path.GetDirectoryName(itemPath) ?? "", href.Replace('/', Path.DirectorySeparatorChar));
return await fileResolver(resolvedPath.Replace('\\', '/'));
});
}
Example QTI Package Structure:
item1/
├── question.xml (contains <qti-stylesheet href="../shared/styles.css">)
└── item-specific.css
shared/
└── styles.css (CSS file to be inlined)
When processing item1/question.xml, the stylesheet ../shared/styles.css resolves to shared/styles.css in the ZIP package.
Consumer hook
Qti2ToQti3PackageConverterOptions.OnItemTransformedAsync runs per assessment item after the built-in transforms, and receives a mutable QtiTransform so you can call .FnCh(...) / .FnChAsync(...) or chain additional built-ins.| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.