Skip to content

Commit 1402a40

Browse files
committed
initial check-in
1 parent 1f5412e commit 1402a40

File tree

171 files changed

+91577
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

171 files changed

+91577
-0
lines changed

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

.yalc/@citolab/qti-convert/LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

.yalc/@citolab/qti-convert/README.md

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
# @citolab/qti-convert
2+
3+
Welcome to **@citolab/qti-convert**, a tool for converting and transforming QTI. This package has scripts that can be executed from the command line and contains functions that can be integrated in JavaScript/TypeScript applications.
4+
5+
## Installation
6+
7+
You can easily install the package using npm:
8+
9+
```sh
10+
npm install @citolab/qti-convert
11+
```
12+
13+
## Usage
14+
15+
@citolab/qti-convert can be used directly from the command line for quick conversions.
16+
And for more advanced usage @citolab/qti-convert can be integrate within your JavaScript or TypeScript projects.
17+
18+
## CLI
19+
20+
Command Line Tool.
21+
22+
The following commands can be run from the terminal:
23+
24+
#### Converting a zip file
25+
26+
```sh
27+
npx -p=@citolab/qti-convert qti-convert-pkg yourpackage.zip
28+
```
29+
30+
Should have a qti2.x zip file as input parameter.
31+
It will create a qti3 zip file in the same folder call yourpackage-qti3.zip
32+
33+
#### Converting a folder
34+
35+
```sh
36+
npx -p=@citolab/qti-convert qti-convert-folder yourfolder (e.g c:\users\you\qti-folder or /Users/you/qti-folder)
37+
```
38+
39+
Should have the path to a folder as input parameter. This folder should contain the content of a qti.2x package
40+
It will convert all files inside the folder and copy the converted files to a new folder called: yourfolder-qti3
41+
42+
#### Removing media files
43+
44+
For test purposes it must sometimes be helpfull to remove large files from your qti-package.
45+
This works on both qti2x as qti3. It will create a new zip file called: {orginal-name}-stripped.zip
46+
47+
```sh
48+
npx -p=@citolab/qti-convert qti-strip-media-pkg yourpackage.zip
49+
```
50+
51+
This will remove audio and video by default. But you can specify filetype/file size yourself as well:
52+
53+
```sh
54+
npx -p=@citolab/qti-convert qti-strip-media-pkg yourpackage.zip audio,.css,300kb
55+
```
56+
57+
This will remove all audio file of any known extension, .css files and files larger than 300kb.
58+
audio,video,images are supported as type, for other files you should add the extension.
59+
60+
Not only the files are remove but the reference in the item/test and manifest will be removed as well. In the item and test if will be replace by an image placeholder that indicates that there is a file removed. css and xsd references will just be deleted just like references in the manifest.
61+
62+
#### Creating an assessment test
63+
64+
```sh
65+
npx -p=@citolab/qti-convert qti-create-assessment yourfolder (e.g c:\users\you\qti-folder or /Users/you/qti-folder)
66+
```
67+
68+
I you have a directory with one or more items but no assessment test, this command will create a assessment test that contains all the items that are in the foldername.
69+
It will override an existing assessment that's callled test.xml.
70+
71+
Should have the path to a folder as input parameter. This folder should contain the content of a qti3 or qti2x package
72+
73+
#### Creating or updating an manifest
74+
75+
```sh
76+
npx -p=@citolab/qti-convert qti-create-manifest yourfolder (e.g c:\users\you\qti-folder or /Users/you/qti-folder)
77+
```
78+
79+
This will create or update an existing manifest. It will look into the directory and search for all items, tests and resources.
80+
Also it will add the resources that are used in an item as a dependency. This folder should contain the content of a qti3 or qti2x package
81+
82+
#### Creating a package zip
83+
84+
This create a package.zip based on all resources in a manifest. So it you have an existing package and you want to remove some items, you can extract the package.zip, remove the manifest, re-generate a manifest using qti-create-manifest and then run this command. The resources used in only the items you deleted, wont be packaged in the new zip.
85+
86+
```sh
87+
npx -p=@citolab/qti-convert qti-create-package yourpackage.zip
88+
```
89+
90+
#### Creating a package zip per item
91+
92+
This create a package.zip per item, for all items in a folder. The package will be called: package\_{item_title || item_identifer}.zip.
93+
94+
```sh
95+
npx -p=@citolab/qti-convert qti-create-package-per-item yourpackage.zip
96+
```
97+
98+
## API
99+
100+
### Convert
101+
102+
#### Converting a QTI 2.x XML String to QTI 3.0
103+
104+
```ts
105+
import { convertQti2toQti3 } from '@citolab/qti-convert/qti-convert';
106+
107+
const qti2Xml = '<qti-assessment-item ...>...</qti-assessment-item>';
108+
convertQti2toQti3(qti2Xml).then(qti3Xml => {
109+
console.log(qti3Xml);
110+
});
111+
```
112+
113+
#### Converting a Zipped QTI Package
114+
115+
The convertPackageStream function processes a zipped QTI package from a stream and converts all relevant QTI 2x files to QTI 3.0.
116+
117+
```ts
118+
import { convertPackageStream } from '@citolab/qti-convert/qti-convert';
119+
import { createReadStream, writeFileSync } from 'fs';
120+
import * as unzipper from 'unzipper';
121+
122+
const inputZipStream = createReadStream('path/to/qti2.zip').pipe(unzipper.Parse({ forceStream: true }));
123+
124+
convertPackageStream(inputZipStream).then(outputBuffer => {
125+
writeFileSync('path/to/qti3.zip', outputBuffer);
126+
});
127+
```
128+
129+
#### Converting a Local QTI Package File
130+
131+
The convertPackageFile function reads a local QTI package file, converts it, and writes the converted package to a specified output file.
132+
133+
```ts
134+
import { convertPackageFile } from '@citolab/qti-convert/qti-convert';
135+
136+
const inputFilePath = 'path/to/qti2-package.zip';
137+
const outputFilePath = 'path/to/qti3-package.zip';
138+
139+
convertPackageFile(inputFilePath, outputFilePath).then(() => {
140+
console.log('Package conversion complete!');
141+
});
142+
```
143+
144+
#### Converting a QTI Package Directory
145+
146+
The convertPackageFolder function converts all QTI 2.x files in a directory to QTI 3.0 and saves them to an output directory.
147+
148+
```ts
149+
import { convertPackageFolder } from '@citolab/qti-convert/qti-convert';
150+
151+
const inputFolder = 'path/to/qti2-folder';
152+
const outputFolder = 'path/to/qti3-folder';
153+
154+
convertPackageFolder(inputFolder, outputFolder).then(() => {
155+
console.log('Conversion complete!');
156+
});
157+
```
158+
159+
#### Customizing Conversion Logic
160+
161+
You can customize the conversion logic by providing custom conversion functions for manifest files, assessment files, and item files.
162+
163+
This is typically needed when a specific platform needs specific conversions.
164+
165+
```ts
166+
import { convertPackageFolder } from '@citolab/qti-convert/qti-convert';
167+
import * as cheerio from 'cheerio';
168+
169+
const customConvertManifest = async ($manifest: cheerio.CheerioAPI): Promise<cheerio.CheerioAPI> => {
170+
// Base conversion:
171+
convertManifestFile($manifest);
172+
// Custom manifest conversion logic here
173+
174+
return $manifest;
175+
};
176+
177+
const customConvertAssessment = async ($assessment: cheerio.CheerioAPI): Promise<cheerio.CheerioAPI> => {
178+
// Base conversion:
179+
if ($assessment('assessmentTest').length > 0) {
180+
const modifiedContent = await convertQti2toQti3(cleanXMLString($assessment.xml()));
181+
$assessment = cheerio.load(modifiedContent, { xmlMode: true, xml: true });
182+
}
183+
// Custom assessment conversion logic here
184+
185+
return $assessment;
186+
};
187+
188+
const customConvertItem = async ($item: cheerio.CheerioAPI): Promise<cheerio.CheerioAPI> => {
189+
// Base conversion:
190+
if ($item('assessmentItem').length > 0) {
191+
const modifiedContent = await convertQti2toQti3(cleanXMLString($item.xml()));
192+
$item = cheerio.load(modifiedContent, { xmlMode: true, xml: true });
193+
}
194+
// Custom item conversion logic here
195+
return $item;
196+
};
197+
198+
convertPackageFolder(
199+
'path/to/qti2-folder',
200+
'path/to/qti3-folder',
201+
customConvertManifest,
202+
customConvertAssessment,
203+
customConvertItem
204+
).then(() => {
205+
console.log('Custom conversion complete!');
206+
});
207+
```
208+
209+
### Transform
210+
211+
To use the qtiTransform function, import it and pass a QTI XML string. The returned API allows you to chain various transformation methods. There are some built-in functions but you can also create your own functions and chain these
212+
213+
```ts
214+
import { qtiTransform } from '@citolab/qti-convert/qti-transformer';
215+
216+
const qtiXml = '<qti-assessment-item ...>...</qti-assessment-item>';
217+
const transformedXml = qtiTransform(qtiXml).stripStylesheets().objectToImg().customTypes().xml();
218+
219+
console.log(transformedXml);
220+
```
221+
222+
#### API Methods
223+
224+
##### fnCh(fn: (xmlString: cheerio.CheerioAPI) => void): QtiTransformAPI
225+
226+
Apply a synchronous function to the XML.
227+
228+
```ts
229+
qtiTransform(xmlValue).fnCh($ => {
230+
// Your custom synchronous transformation logic
231+
});
232+
```
233+
234+
##### fnChAsync(fn: (xmlString: cheerio.CheerioAPI) => Promise<void>): Promise<QtiTransformAPI>
235+
236+
Apply an asynchronous function to the XML.
237+
238+
```ts
239+
await(xmlValue).fnChAsync(async $ => {
240+
// Your custom asynchronous transformation logic
241+
});
242+
```
243+
244+
The build-in functions that can be chained are:
245+
246+
- `mathml(): QtiTransformAPI`: Convert MathML elements to web components.
247+
- `objectToVideo(): QtiTransformAPI`: Convert `<object>` elements to `<video>` elements..
248+
- `objectToAudio(): QtiTransformAPI`: Convert `<object>` elements to `<audio>` elements..
249+
- `objectToImg(): QtiTransformAPI`: Convert `<object>` elements to `<img>` elements.
250+
- `stripStylesheets(): QtiTransformAPI`: Remove all stylesheet references from the XML.
251+
- `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.
252+
- `changeAssetLocationAsync(getNewUrl: (oldUrl: string) => Promise<string>, srcAttribute?: string[], skipBase64 = true): QtiTransformAPI`: Async function of changeAssetLocation
253+
- `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.
254+
- `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.
255+
- `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.
256+
- `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.
257+
- `stripMaterialInfo(): QtiTransformAPI`: Remove unnecessary material information from the XML
258+
- `qbCleanup(): QtiTransformAPI`: Clean-up for package created with the Quesify Platform
259+
- `depConvert(): QtiTransformAPI`: Converts qti from the Dutch Extension Profile. For now only dep-dialog to a html popover. With is basic support for these dialog.
260+
- `minChoicesToOne(): QtiTransformAPI`: Ensure the minimum number of choices is one.
261+
- `suffix(elements: string[], suffix: string)`: Add a suffix to specified elements.
262+
- `externalScored()`: Mark the XML as externally scored.
263+
264+
Other function to get the output of the transformer:
265+
266+
- `xml()`: returns the xml as string
267+
- `browser.htmlDoc()`: returns a DocumentFragment. Won't work on node.
268+
- `browser.xmlDoc()`: returns a XMLDocument. Won't work on node.
269+
270+
### Loader
271+
272+
The @citolab/qti-convert/qti-loader module provides utilities for loading and processing QTI XML content. It includes functions to fetch IMS manifest files, retrieve assessment tests, and extract item information.
273+
274+
#### Fetching IMS Manifest Data
275+
276+
The `testLoader` function fetches and processes the IMS manifest XML file from a given URI. It retrieves the assessment test and its associated items.
277+
278+
### Helper
279+
280+
@citolab/qti-convert/qti-helper provides a set of helper functions for QTI package. E.g. removeMediaFromPackage is a helper to remove media files from a QTI-package
281+
282+
### Helper Node
283+
284+
@citolab/qti-convert/qti-helper-node provides a set of helper functions for QTI packages. It includes functions to recursively retrieve QTI resources, create or update IMS manifest files and generate QTI assessment tests. These only work in NodeJS and wont work in the browser.
285+
286+
#### Generate an assessement and manifest
287+
288+
createAssessmentTest create an assessment with all items in a folder.
289+
createOrCompleteManifest will create or update a manifest based on all resources in a folder.
290+
291+
```ts
292+
import { createOrCompleteManifest, createAssessmentTest } from '@citolab/qti-convert/qti-helper';
293+
294+
const foldername = 'path/to/qti-folder';
295+
296+
async function processQtiFolder(foldername: string) {
297+
try {
298+
const manifest = await createOrCompleteManifest(foldername);
299+
console.log('Manifest:', manifest);
300+
301+
const assessmentTest = await createAssessmentTest(foldername);
302+
console.log('Assessment Test:', assessmentTest);
303+
} catch (error) {
304+
console.error('Error processing QTI folder:', error);
305+
}
306+
}
307+
308+
processQtiFolder(foldername);
309+
```
310+
311+
#### Get all resources
312+
313+
Returns a list of all resources with its type.
314+
315+
```ts
316+
import { getAllResourcesRecursively, QtiResource } from '@citolab/qti-convert/qti-helper';
317+
318+
const allResources: QtiResource[] = [];
319+
const foldername = 'path/to/qti-folder';
320+
321+
getAllResourcesRecursively(allResources, foldername);
322+
console.log(allResources);
323+
```
324+
325+
## License
326+
327+
This package is licensed under the GNU General Public License. See the [LICENSE file](/LICENSE) for more information.
328+
329+
## Contributing
330+
331+
Contributions are welcome! Please open an issue or submit a pull request on GitHub.

0 commit comments

Comments
 (0)