Skip to content

Commit

Permalink
added upgradePci to upgrade TAO PCI's after qti3 stylesheet conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcelh1983 committed Sep 11, 2024
1 parent 683955f commit fdd8830
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ The build-in functions that can be chained are:
- `changeAssetLocation(getNewUrl: (oldUrl: string) => string, srcAttribute?: string[], skipBase64 = true): QtiTransformAPI`: Helper function to change the asset location of media files. Url can be changed in the callback function. By default the following attributes are checked for references: `['src', 'href', 'data', 'primary-path', 'fallback-path', 'template-location']` but that can be overriden. Also by default you won't get a callback for base64 urls.
- `changeAssetLocationAsync(getNewUrl: (oldUrl: string) => Promise<string>, srcAttribute?: string[], skipBase64 = true): QtiTransformAPI`: Async function of changeAssetLocation
- `configurePciAsync(baseUrl: string, getModuleResolutionConfig: (url: string) => Promise<ModuleResolutionConfig>): Promise<QtiTransformAPI>`: makes sure custom-interaction-type-identifier are unique per item, adds /modules/module_resolution.js and /modules/fallback_module_resolution.js to the qti-interaction-modules tag of the item qti and sets a baseUrl to be able to get the full path of the modules.
- `upgradePci()`: The default qti3 upgrader doesn't handle pci's exported from TAO properly. This is tested only for PCI's that use the latest PCI standard and are exported to qti2.x with TAO.
- `customTypes(): QtiTransformAPI`: Apply custom type transformations to the XML. Can be used override default web-components. E.g. `<qti-choice-interaction class="type:custom">` will result in `<qti-choice-interaction-custom>` so you can create your own web-component to render choice interactions.
- `customInteraction(baseRef: string, baseItem: string)` Transforms qti-custom-interactions that contain an object tag. Object tag will be removed and attributes will be merged in the qti-custom-interactions tag.
- `stripMaterialInfo(): QtiTransformAPI`: Remove unnecessary material information from the XML
Expand Down
6 changes: 6 additions & 0 deletions src/lib/qti-transformer/qti-transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
objectToImg,
objectToVideo,
objectToAudio,
upgradePci,
suffixa,
toMathMLWebcomponents,
qbCleanup,
Expand Down Expand Up @@ -46,6 +47,7 @@ interface QtiTransformAPI {
baseUrl: string,
getModuleResolutionConfig: (url: string) => Promise<ModuleResolutionConfig>
): Promise<QtiTransformAPI>;
upgradePci(): QtiTransformAPI;
stripStylesheets(): QtiTransformAPI;
customTypes(): QtiTransformAPI;
stripMaterialInfo(): QtiTransformAPI;
Expand Down Expand Up @@ -152,6 +154,10 @@ export const qtiTransform = (xmlValue: string): QtiTransformAPI => {
await configurePciAsync($, baseUrl, getModuleResolutionConfig);
return api;
},
upgradePci() {
upgradePci($);
return api;
},
stripStylesheets() {
stripStylesheets($);
return api;
Expand Down
1 change: 1 addition & 0 deletions src/lib/qti-transformer/transformers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export * from './min-choices-to-one';
export * from './external-scored';
export * from './change-asset-location';
export * from './configure-pci';
export * from './upgrade-pci';
export * from './strip-stylesheets';
79 changes: 79 additions & 0 deletions src/lib/qti-transformer/transformers/upgrade-pci/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { expect, test } from 'vitest';

import { upgradePci } from '.';
import { qtiTransform } from '../../qti-transform';
import { areXmlEqual } from '../utils-node-only';

const xml = String.raw;

const transformObjectTags = (xmlContent: string) => {
const modifiedContent = qtiTransform(xmlContent).fnCh(upgradePci).xml();
return modifiedContent;
};

test('upgrade tao exported pci', async () => {
const input = xml`<?xml version="1.0" encoding="UTF-8"?>
<qti-custom-interaction response-identifier="RESPONSE"
data-base-ref="https://europe-west4-qti-convert.cloudfunctions.net/api/application/convert-online/package/b11f2f15259a9289eab9ff1c8bb6b94bd503914b62da22668547d27855b5df5a"
data-base-item="https://europe-west4-qti-convert.cloudfunctions.net/api/application/convert-online/package/b11f2f15259a9289eab9ff1c8bb6b94bd503914b62da22668547d27855b5df5a">
<qti-portable-custom-interaction
custom-interaction-type-identifier="colorProportions" data-version="1.0.1"
data-base-url="https://europe-west4-qti-convert.cloudfunctions.net/api/application/convert-online/package/b11f2f15259a9289eab9ff1c8bb6b94bd503914b62da22668547d27855b5df5a">
<properties>
<property key="colors">red, blue, yellow</property>
<property key="width">400</property>
<property key="height">400</property>
</properties>
<modules>
<module id="colorProportions/interaction/runtime/js/index"
primary-path="http://localhost:3333/application/convert-online/package/b11f2f15259a9289eab9ff1c8bb6b94bd503914b62da22668547d27855b5df5a/interaction/runtime/js/index.js" />
</modules>
<markup>
<div class="pciInteraction">
<style>.pciInteraction ul.pci{
list-style-type: none;
margin: 0 5px;
padding: 0;
display: inline-block;
position: relative;
top: 7px;
}
.pciInteraction ul.pci li{
float: left;
text-align: left;
list-style-type: none;
}</style>
<div class="prompt" />
<ul class="pci" />
</div>
</markup>
</qti-portable-custom-interaction>
</qti-custom-interaction>
`;
const expectedOutput = xml`<?xml version="1.0" encoding="UTF-8"?>
<qti-portable-custom-interaction
custom-interaction-type-identifier="colorProportions"
data-version="1.0.1"
data-colors="red, blue, yellow"
data-width="400"
data-height="400"
module="colorProportions"
response-identifier="RESPONSE"
>
<qti-interaction-modules>
<qti-interaction-module
id="colorProportions"
primary-path="http://localhost:3333/application/convert-online/package/b11f2f15259a9289eab9ff1c8bb6b94bd503914b62da22668547d27855b5df5a/interaction/runtime/js/index.js"
></qti-interaction-module>
</qti-interaction-modules>
<qti-interaction-markup>
<div class="pciInteraction">
<div class="prompt" />
<ul class="pci" />
</div>
</qti-interaction-markup>
</qti-portable-custom-interaction>`;
const result = await transformObjectTags(input);
const areEqual = await areXmlEqual(result, expectedOutput);
expect(areEqual).toEqual(true);
});
71 changes: 71 additions & 0 deletions src/lib/qti-transformer/transformers/upgrade-pci/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as cheerio from 'cheerio';

export function upgradePci($: cheerio.CheerioAPI) {
const customInteraction = $('qti-custom-interaction');
const portableCustomInteraction = customInteraction.find('qti-portable-custom-interaction');

if (portableCustomInteraction.length === 0) {
return $;
}

// 1. Remove the <style> tag
portableCustomInteraction.find('style').remove();

// 2. Move properties to data-attributes on qti-portable-custom-interaction
const properties = portableCustomInteraction.find('properties property');
properties.each((_, property) => {
const key = $(property).attr('key');
const value = $(property).text();
if (key && value) {
portableCustomInteraction.attr(`data-${key}`, value);
}
});
portableCustomInteraction.find('properties').remove();

// 3. Fix tagnames and structure
// Change <modules> to <qti-interaction-modules> and <module> to <qti-interaction-module>
const modules = portableCustomInteraction.find('modules');
if (modules.length > 0) {
const newModules = $('<qti-interaction-modules></qti-interaction-modules>');
modules.find('module').each((_, module) => {
const newModule = $('<qti-interaction-module></qti-interaction-module>');
const id = $(module).attr('id')?.split('/')[0]; // Get the id up to the first slash
newModule.attr('id', id || '');
newModule.attr('primary-path', $(module).attr('primary-path'));
newModules.append(newModule);
});
modules.replaceWith(newModules);
}

// 4. Remove data-base-* attributes
portableCustomInteraction.removeAttr('data-base-ref');
portableCustomInteraction.removeAttr('data-base-item');
portableCustomInteraction.removeAttr('data-base-url');

// 5. Fix the id of the qti-interaction-module to be the value of custom-interaction-type-identifier
const customInteractionTypeIdentifier = portableCustomInteraction.attr('custom-interaction-type-identifier');
if (customInteractionTypeIdentifier) {
portableCustomInteraction.find('qti-interaction-module').attr('id', customInteractionTypeIdentifier);

// Add the module attribute with the same value as custom-interaction-type-identifier
portableCustomInteraction.attr('module', customInteractionTypeIdentifier);
}

// 6. Copy response-identifier from qti-custom-interaction to qti-portable-custom-interaction
const responseIdentifier = customInteraction.attr('response-identifier');
if (responseIdentifier) {
portableCustomInteraction.attr('response-identifier', responseIdentifier);
}

// 7. Rename <markup> to <qti-interaction-markup>
const markup = portableCustomInteraction.find('markup');
if (markup.length > 0) {
markup.replaceWith('<qti-interaction-markup>' + markup.html() + '</qti-interaction-markup>');
}

// 8. Remove the qti-custom-interaction wrapper
const newPortableCustomInteraction = portableCustomInteraction.clone();
customInteraction.replaceWith(newPortableCustomInteraction);

return $;
}

0 comments on commit fdd8830

Please sign in to comment.