@citolab/qti-components 7.0.2 → 7.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,6 +59,7 @@ declare const qtiTransformManifest: () => {
59
59
  type transformTestApi = {
60
60
  load: (uri: string) => Promise<transformTestApi>;
61
61
  parse: (xmlString: string) => transformTestApi;
62
+ path: (location: string) => transformTestApi;
62
63
  fn: (fn: (xmlFragment: XMLDocument) => void) => transformTestApi;
63
64
  items: () => {
64
65
  identifier: string;
@@ -250,6 +250,7 @@ var qtiTransformTest = () => {
250
250
  return new Promise((resolve, _) => {
251
251
  loadXML(uri).then((xml2) => {
252
252
  xmlFragment = xml2;
253
+ api.path(uri.substring(0, uri.lastIndexOf("/")));
253
254
  return resolve(api);
254
255
  });
255
256
  });
@@ -258,6 +259,10 @@ var qtiTransformTest = () => {
258
259
  xmlFragment = parseXML(xmlString);
259
260
  return api;
260
261
  },
262
+ path: (location) => {
263
+ setLocation(xmlFragment, location);
264
+ return api;
265
+ },
261
266
  fn(fn) {
262
267
  fn(xmlFragment);
263
268
  return api;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/qti-transformers/qti-transformers.ts","../../src/lib/qti-transformers/qti-transform-item.ts","../../src/lib/qti-transformers/qti-transform-manifest.ts","../../src/lib/qti-transformers/qti-transform-test.ts"],"sourcesContent":["const xml = String.raw;\n\n/* <!-- convert CDATA to comments -->\n <xsl:template match=\"text()[contains(., 'CDATA')]\">\n <xsl:comment>\n <xsl:value-of select=\".\"/>\n </xsl:comment>\n</xsl:template>\n*/\n\n/*\n <!-- remove xml comments -->\n <xsl:template match=\"comment()\" />\n */\n\nconst xmlToHTML = xml`<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n<xsl:output method=\"html\" version=\"5.0\" encoding=\"UTF-8\" indent=\"yes\" />\n <xsl:template match=\"@*|node()\">\n <xsl:copy>\n <xsl:apply-templates select=\"@*|node()\"/>\n </xsl:copy>\n </xsl:template>\n\n <!-- remove existing namespaces -->\n <xsl:template match=\"*\">\n <!-- remove element prefix -->\n <xsl:element name=\"{local-name()}\">\n <!-- process attributes -->\n <xsl:for-each select=\"@*\">\n <!-- remove attribute prefix -->\n <xsl:attribute name=\"{local-name()}\">\n <xsl:value-of select=\".\"/>\n </xsl:attribute>\n </xsl:for-each>\n <xsl:apply-templates/>\n </xsl:element>\n</xsl:template>\n</xsl:stylesheet>`;\n\n// Function to extend elements with a specific tag name by adding an extension suffix\nexport function extendElementName(xmlFragment: XMLDocument, tagName: string, extension: string) {\n xmlFragment.querySelectorAll(tagName).forEach(element => {\n const newTagName = `${tagName}-${extension}`;\n const newElement = createElementWithNewTagName(element, newTagName);\n element.replaceWith(newElement);\n });\n}\n\n// Function to extend any element with a specific class pattern (e.g., \"extend:suffix\")\nexport function extendElementsWithClass(xmlFragment: XMLDocument, classNamePattern: string) {\n xmlFragment.querySelectorAll('*').forEach(element => {\n const classList = element.classList;\n if (classList) {\n classList.forEach(className => {\n if (className.startsWith(`${classNamePattern}:`)) {\n const suffix = className.slice(`${classNamePattern}:`.length);\n const newTagName = `${element.nodeName}-${suffix}`;\n const newElement = createElementWithNewTagName(element, newTagName);\n element.replaceWith(newElement);\n }\n });\n }\n });\n}\n\n// Helper function to create a new element with a new tag name and copy attributes and children\nfunction createElementWithNewTagName(element, newTagName) {\n const newElement = document.createElement(newTagName);\n // Copy attributes\n for (const attr of element.attributes) {\n newElement.setAttribute(attr.name, attr.value);\n }\n // Copy child nodes\n while (element.firstChild) {\n newElement.appendChild(element.firstChild);\n }\n return newElement;\n}\n\nexport function itemsFromTest(xmlFragment: DocumentFragment) {\n const items: { identifier: string; href: string; category: string }[] = [];\n xmlFragment.querySelectorAll('qti-assessment-item-ref').forEach(el => {\n const identifier = el.getAttribute('identifier');\n const href = el.getAttribute('href');\n const category = el.getAttribute('category');\n items.push({ identifier, href, category });\n });\n return items;\n}\n\nlet currentRequest: XMLHttpRequest | null = null;\n\nexport function loadXML(url, cancelPreviousRequest = false) {\n if (cancelPreviousRequest && currentRequest !== null) {\n currentRequest.abort(); // Abort the ongoing request if there is one\n }\n\n return new Promise<XMLDocument | null>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n currentRequest = xhr; // Store the current request\n\n xhr.open('GET', url, true);\n xhr.responseType = 'document';\n\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(xhr.responseXML);\n } else {\n reject(xhr.statusText);\n }\n };\n\n xhr.onerror = () => {\n reject(xhr.statusText);\n };\n\n xhr.send();\n });\n}\n\nexport function parseXML(xmlDocument: string) {\n const parser = new DOMParser();\n const xmlFragment = parser.parseFromString(xmlDocument, 'text/xml');\n return xmlFragment;\n}\n\nexport function toHTML(xmlFragment: Document): DocumentFragment {\n const processor = new XSLTProcessor();\n const xsltDocument = new DOMParser().parseFromString(xmlToHTML, 'text/xml');\n processor.importStylesheet(xsltDocument);\n const itemHTMLFragment = processor.transformToFragment(xmlFragment, document);\n return itemHTMLFragment;\n}\n\nexport function setLocation(xmlFragment: DocumentFragment, location: string) {\n if (!location.endsWith('/')) {\n location += '/';\n }\n\n xmlFragment.querySelectorAll('[src],[href],[primary-path]').forEach(elWithSrc => {\n let attr: 'src' | 'href' | 'primary-path' | '' = '';\n\n if (elWithSrc.getAttribute('src')) {\n attr = 'src';\n }\n if (elWithSrc.getAttribute('href')) {\n attr = 'href';\n }\n if (elWithSrc.getAttribute('primary-path')) {\n attr = 'primary-path';\n }\n const attrValue = elWithSrc.getAttribute(attr)?.trim();\n\n if (!attrValue.startsWith('data:') && !attrValue.startsWith('http')) {\n const newSrcValue = location + encodeURI(attrValue);\n elWithSrc.setAttribute(attr, newSrcValue);\n }\n });\n}\n\nexport function convertCDATAtoComment(xmlFragment: DocumentFragment) {\n const cdataElements = xmlFragment.querySelectorAll('qti-custom-operator[class=\"js.org\"] > qti-base-value');\n cdataElements.forEach(element => {\n const commentText = document.createComment(element.textContent);\n element.replaceChild(commentText, element.firstChild);\n });\n}\n\nexport function stripStyleSheets(xmlFragment: DocumentFragment) {\n // remove qti-stylesheet tag\n xmlFragment.querySelectorAll('qti-stylesheet').forEach(stylesheet => stylesheet.remove());\n}\n","/**\n * Browser based QTI-XML to HTML transformer.\n * Returns an object with methods to load, parse, transform and serialize QTI XML items.\n * @returns An object with methods to load, parse, transform and serialize QTI XML items.\n * @example\n * const qtiTransformer = qtiTransformItem();\n * await qtiTransformer.load('path/to/xml/file.xml');\n * qtiTransformer.path('/assessmentItem/itemBody');\n * const html = qtiTransformer.html();\n * const xml = qtiTransformer.xml();\n * const htmldoc = qtiTransformer.htmldoc();\n * const xmldoc = qtiTransformer.xmldoc();\n *\n * qtiTransformItem().parse(storyXML).html()\n */\n\nimport {\n convertCDATAtoComment,\n extendElementName,\n extendElementsWithClass,\n loadXML,\n parseXML,\n setLocation,\n stripStyleSheets,\n toHTML\n} from './qti-transformers';\n\nexport type transformItemApi = {\n load: (uri: string, cancelPreviousRequest?: boolean) => Promise<transformItemApi>;\n parse: (xmlString: string) => transformItemApi;\n path: (location: string) => transformItemApi;\n fn: (fn: (xmlFragment: XMLDocument) => void) => transformItemApi;\n pciHooks: (uri: string) => transformItemApi;\n extendElementName: (elementName: string, extend: string) => transformItemApi;\n extendElementsWithClass: (param?: string) => transformItemApi;\n customInteraction: (baseRef: string, baseItem: string) => transformItemApi;\n convertCDATAtoComment: () => transformItemApi;\n stripStyleSheets: () => transformItemApi;\n html: () => string;\n xml: () => string;\n htmlDoc: () => DocumentFragment;\n xmlDoc: () => XMLDocument;\n};\n\nexport const qtiTransformItem = () => {\n let xmlFragment: XMLDocument;\n\n const api: transformItemApi = {\n async load(uri: string, cancelPreviousRequest = false): Promise<typeof api> {\n return new Promise<typeof api>(resolve => {\n loadXML(uri, cancelPreviousRequest).then(xml => {\n xmlFragment = xml;\n // set the base path for images and other resources,\n // you probably want to set the base path to the document root else you can use the path method to set it\n api.path(uri.substring(0, uri.lastIndexOf('/')));\n return resolve(api);\n });\n });\n },\n parse(xmlString: string): typeof api {\n xmlFragment = parseXML(xmlString);\n return api;\n },\n path: (location: string): typeof api => {\n setLocation(xmlFragment, location);\n return api;\n },\n fn(fn: (xmlFragment: XMLDocument) => void): typeof api {\n fn(xmlFragment);\n return api;\n },\n pciHooks(uri: string): typeof api {\n const attributes = ['hook', 'module'];\n const documentPath = uri.substring(0, uri.lastIndexOf('/'));\n for (const attribute of attributes) {\n const srcAttributes = xmlFragment.querySelectorAll('[' + attribute + ']');\n srcAttributes.forEach(node => {\n const srcValue = node.getAttribute(attribute)!;\n if (!srcValue.startsWith('data:') && !srcValue.startsWith('http')) {\n // Just paste the relative path of the src location after the documentrootPath\n // old pcis can have a .js, new pci's don't\n node.setAttribute('base-url', uri);\n node.setAttribute(\n 'module',\n documentPath + '/' + encodeURI(srcValue + (srcValue.endsWith('.js') ? '' : '.js'))\n );\n }\n });\n }\n return api;\n },\n extendElementName: (tagName: string, extension: string): typeof api => {\n extendElementName(xmlFragment, tagName, extension);\n return api;\n },\n extendElementsWithClass: (param: string = 'extend'): typeof api => {\n extendElementsWithClass(xmlFragment, param);\n return api;\n },\n customInteraction(baseRef: string, baseItem: string): typeof api {\n const qtiCustomInteraction = xmlFragment.querySelector('qti-custom-interaction');\n const qtiCustomInteractionObject = qtiCustomInteraction.querySelector('object');\n\n qtiCustomInteraction.setAttribute('data-base-ref', baseRef);\n qtiCustomInteraction.setAttribute('data-base-item', baseRef + baseItem);\n qtiCustomInteraction.setAttribute('data', qtiCustomInteractionObject.getAttribute('data'));\n qtiCustomInteraction.setAttribute('width', qtiCustomInteractionObject.getAttribute('width'));\n qtiCustomInteraction.setAttribute('height', qtiCustomInteractionObject.getAttribute('height'));\n\n qtiCustomInteraction.removeChild(qtiCustomInteractionObject);\n return api;\n },\n convertCDATAtoComment(): typeof api {\n convertCDATAtoComment(xmlFragment);\n return api;\n },\n stripStyleSheets(): typeof api {\n stripStyleSheets(xmlFragment);\n return api;\n },\n html() {\n return new XMLSerializer().serializeToString(toHTML(xmlFragment));\n },\n xml(): string {\n return new XMLSerializer().serializeToString(xmlFragment);\n },\n htmlDoc() {\n return toHTML(xmlFragment);\n },\n xmlDoc(): XMLDocument {\n return xmlFragment; // new XMLSerializer().serializeToString(xmlFragment);\n }\n };\n return api;\n};\n","import { loadXML, parseXML } from './qti-transformers';\n\nexport const qtiTransformManifest = (): {\n load: (uri: string) => Promise<typeof api>;\n assessmentTest: () => { href: string; identifier: string };\n} => {\n let xmlFragment: XMLDocument;\n\n const api = {\n async load(uri) {\n return new Promise<typeof api>(resolve => {\n loadXML(uri).then(xml => {\n xmlFragment = xml;\n return resolve(api);\n });\n });\n },\n parse(xmlString: string) {\n xmlFragment = parseXML(xmlString);\n },\n assessmentTest() {\n const el = xmlFragment.querySelector('resource[type=\"imsqti_test_xmlv3p0\"]');\n return { href: el.getAttribute('href'), identifier: el.getAttribute('identifier') };\n }\n };\n return api;\n};\n","/**\n * Returns an object with methods to load, parse and transform QTI tests.\n * @returns An object with methods to load, parse and transform QTI tests.\n * @example\n * const qtiTransformer = qtiTransformTest();\n * await qtiTransformer.load('https://example.com/test.xml');\n * const items = qtiTransformer.items();\n * const html = qtiTransformer.html();\n * const xml = qtiTransformer.xml();\n */\n\nimport { itemsFromTest, loadXML, parseXML, toHTML } from './qti-transformers';\n\nexport type transformTestApi = {\n load: (uri: string) => Promise<transformTestApi>;\n parse: (xmlString: string) => transformTestApi;\n fn: (fn: (xmlFragment: XMLDocument) => void) => transformTestApi;\n items: () => { identifier: string; href: string; category: string }[];\n html: () => string;\n xml: () => string;\n htmlDoc: () => DocumentFragment;\n xmlDoc: () => XMLDocument;\n};\n\nexport const qtiTransformTest = (): transformTestApi => {\n let xmlFragment: XMLDocument;\n\n const api: transformTestApi = {\n async load(uri) {\n return new Promise<transformTestApi>((resolve, _) => {\n loadXML(uri).then(xml => {\n xmlFragment = xml;\n return resolve(api);\n });\n });\n },\n parse(xmlString: string) {\n xmlFragment = parseXML(xmlString);\n return api;\n },\n fn(fn: (xmlFragment: XMLDocument) => void) {\n fn(xmlFragment);\n return api;\n },\n items() {\n return itemsFromTest(xmlFragment);\n },\n html() {\n return new XMLSerializer().serializeToString(toHTML(xmlFragment));\n },\n xml(): string {\n return new XMLSerializer().serializeToString(xmlFragment);\n },\n htmlDoc() {\n return toHTML(xmlFragment);\n },\n xmlDoc(): XMLDocument {\n return xmlFragment;\n }\n };\n return api;\n};\n"],"mappings":"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","names":["xml","xml","xml"]}
1
+ {"version":3,"sources":["../../src/lib/qti-transformers/qti-transformers.ts","../../src/lib/qti-transformers/qti-transform-item.ts","../../src/lib/qti-transformers/qti-transform-manifest.ts","../../src/lib/qti-transformers/qti-transform-test.ts"],"sourcesContent":["const xml = String.raw;\n\n/* <!-- convert CDATA to comments -->\n <xsl:template match=\"text()[contains(., 'CDATA')]\">\n <xsl:comment>\n <xsl:value-of select=\".\"/>\n </xsl:comment>\n</xsl:template>\n*/\n\n/*\n <!-- remove xml comments -->\n <xsl:template match=\"comment()\" />\n */\n\nconst xmlToHTML = xml`<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n<xsl:output method=\"html\" version=\"5.0\" encoding=\"UTF-8\" indent=\"yes\" />\n <xsl:template match=\"@*|node()\">\n <xsl:copy>\n <xsl:apply-templates select=\"@*|node()\"/>\n </xsl:copy>\n </xsl:template>\n\n <!-- remove existing namespaces -->\n <xsl:template match=\"*\">\n <!-- remove element prefix -->\n <xsl:element name=\"{local-name()}\">\n <!-- process attributes -->\n <xsl:for-each select=\"@*\">\n <!-- remove attribute prefix -->\n <xsl:attribute name=\"{local-name()}\">\n <xsl:value-of select=\".\"/>\n </xsl:attribute>\n </xsl:for-each>\n <xsl:apply-templates/>\n </xsl:element>\n</xsl:template>\n</xsl:stylesheet>`;\n\n// Function to extend elements with a specific tag name by adding an extension suffix\nexport function extendElementName(xmlFragment: XMLDocument, tagName: string, extension: string) {\n xmlFragment.querySelectorAll(tagName).forEach(element => {\n const newTagName = `${tagName}-${extension}`;\n const newElement = createElementWithNewTagName(element, newTagName);\n element.replaceWith(newElement);\n });\n}\n\n// Function to extend any element with a specific class pattern (e.g., \"extend:suffix\")\nexport function extendElementsWithClass(xmlFragment: XMLDocument, classNamePattern: string) {\n xmlFragment.querySelectorAll('*').forEach(element => {\n const classList = element.classList;\n if (classList) {\n classList.forEach(className => {\n if (className.startsWith(`${classNamePattern}:`)) {\n const suffix = className.slice(`${classNamePattern}:`.length);\n const newTagName = `${element.nodeName}-${suffix}`;\n const newElement = createElementWithNewTagName(element, newTagName);\n element.replaceWith(newElement);\n }\n });\n }\n });\n}\n\n// Helper function to create a new element with a new tag name and copy attributes and children\nfunction createElementWithNewTagName(element, newTagName) {\n const newElement = document.createElement(newTagName);\n // Copy attributes\n for (const attr of element.attributes) {\n newElement.setAttribute(attr.name, attr.value);\n }\n // Copy child nodes\n while (element.firstChild) {\n newElement.appendChild(element.firstChild);\n }\n return newElement;\n}\n\nexport function itemsFromTest(xmlFragment: DocumentFragment) {\n const items: { identifier: string; href: string; category: string }[] = [];\n xmlFragment.querySelectorAll('qti-assessment-item-ref').forEach(el => {\n const identifier = el.getAttribute('identifier');\n const href = el.getAttribute('href');\n const category = el.getAttribute('category');\n items.push({ identifier, href, category });\n });\n return items;\n}\n\nlet currentRequest: XMLHttpRequest | null = null;\n\nexport function loadXML(url, cancelPreviousRequest = false) {\n if (cancelPreviousRequest && currentRequest !== null) {\n currentRequest.abort(); // Abort the ongoing request if there is one\n }\n\n return new Promise<XMLDocument | null>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n currentRequest = xhr; // Store the current request\n\n xhr.open('GET', url, true);\n xhr.responseType = 'document';\n\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(xhr.responseXML);\n } else {\n reject(xhr.statusText);\n }\n };\n\n xhr.onerror = () => {\n reject(xhr.statusText);\n };\n\n xhr.send();\n });\n}\n\nexport function parseXML(xmlDocument: string) {\n const parser = new DOMParser();\n const xmlFragment = parser.parseFromString(xmlDocument, 'text/xml');\n return xmlFragment;\n}\n\nexport function toHTML(xmlFragment: Document): DocumentFragment {\n const processor = new XSLTProcessor();\n const xsltDocument = new DOMParser().parseFromString(xmlToHTML, 'text/xml');\n processor.importStylesheet(xsltDocument);\n const itemHTMLFragment = processor.transformToFragment(xmlFragment, document);\n return itemHTMLFragment;\n}\n\nexport function setLocation(xmlFragment: DocumentFragment, location: string) {\n if (!location.endsWith('/')) {\n location += '/';\n }\n\n xmlFragment.querySelectorAll('[src],[href],[primary-path]').forEach(elWithSrc => {\n let attr: 'src' | 'href' | 'primary-path' | '' = '';\n\n if (elWithSrc.getAttribute('src')) {\n attr = 'src';\n }\n if (elWithSrc.getAttribute('href')) {\n attr = 'href';\n }\n if (elWithSrc.getAttribute('primary-path')) {\n attr = 'primary-path';\n }\n const attrValue = elWithSrc.getAttribute(attr)?.trim();\n\n if (!attrValue.startsWith('data:') && !attrValue.startsWith('http')) {\n const newSrcValue = location + encodeURI(attrValue);\n elWithSrc.setAttribute(attr, newSrcValue);\n }\n });\n}\n\nexport function convertCDATAtoComment(xmlFragment: DocumentFragment) {\n const cdataElements = xmlFragment.querySelectorAll('qti-custom-operator[class=\"js.org\"] > qti-base-value');\n cdataElements.forEach(element => {\n const commentText = document.createComment(element.textContent);\n element.replaceChild(commentText, element.firstChild);\n });\n}\n\nexport function stripStyleSheets(xmlFragment: DocumentFragment) {\n // remove qti-stylesheet tag\n xmlFragment.querySelectorAll('qti-stylesheet').forEach(stylesheet => stylesheet.remove());\n}\n","/**\n * Browser based QTI-XML to HTML transformer.\n * Returns an object with methods to load, parse, transform and serialize QTI XML items.\n * @returns An object with methods to load, parse, transform and serialize QTI XML items.\n * @example\n * const qtiTransformer = qtiTransformItem();\n * await qtiTransformer.load('path/to/xml/file.xml');\n * qtiTransformer.path('/assessmentItem/itemBody');\n * const html = qtiTransformer.html();\n * const xml = qtiTransformer.xml();\n * const htmldoc = qtiTransformer.htmldoc();\n * const xmldoc = qtiTransformer.xmldoc();\n *\n * qtiTransformItem().parse(storyXML).html()\n */\n\nimport {\n convertCDATAtoComment,\n extendElementName,\n extendElementsWithClass,\n loadXML,\n parseXML,\n setLocation,\n stripStyleSheets,\n toHTML\n} from './qti-transformers';\n\nexport type transformItemApi = {\n load: (uri: string, cancelPreviousRequest?: boolean) => Promise<transformItemApi>;\n parse: (xmlString: string) => transformItemApi;\n path: (location: string) => transformItemApi;\n fn: (fn: (xmlFragment: XMLDocument) => void) => transformItemApi;\n pciHooks: (uri: string) => transformItemApi;\n extendElementName: (elementName: string, extend: string) => transformItemApi;\n extendElementsWithClass: (param?: string) => transformItemApi;\n customInteraction: (baseRef: string, baseItem: string) => transformItemApi;\n convertCDATAtoComment: () => transformItemApi;\n stripStyleSheets: () => transformItemApi;\n html: () => string;\n xml: () => string;\n htmlDoc: () => DocumentFragment;\n xmlDoc: () => XMLDocument;\n};\n\nexport const qtiTransformItem = () => {\n let xmlFragment: XMLDocument;\n\n const api: transformItemApi = {\n async load(uri: string, cancelPreviousRequest = false): Promise<typeof api> {\n return new Promise<typeof api>(resolve => {\n loadXML(uri, cancelPreviousRequest).then(xml => {\n xmlFragment = xml;\n // set the base path for images and other resources,\n // you probably want to set the base path to the document root else you can use the path method to set it\n api.path(uri.substring(0, uri.lastIndexOf('/')));\n return resolve(api);\n });\n });\n },\n parse(xmlString: string): typeof api {\n xmlFragment = parseXML(xmlString);\n return api;\n },\n path: (location: string): typeof api => {\n setLocation(xmlFragment, location);\n return api;\n },\n fn(fn: (xmlFragment: XMLDocument) => void): typeof api {\n fn(xmlFragment);\n return api;\n },\n pciHooks(uri: string): typeof api {\n const attributes = ['hook', 'module'];\n const documentPath = uri.substring(0, uri.lastIndexOf('/'));\n for (const attribute of attributes) {\n const srcAttributes = xmlFragment.querySelectorAll('[' + attribute + ']');\n srcAttributes.forEach(node => {\n const srcValue = node.getAttribute(attribute)!;\n if (!srcValue.startsWith('data:') && !srcValue.startsWith('http')) {\n // Just paste the relative path of the src location after the documentrootPath\n // old pcis can have a .js, new pci's don't\n node.setAttribute('base-url', uri);\n node.setAttribute(\n 'module',\n documentPath + '/' + encodeURI(srcValue + (srcValue.endsWith('.js') ? '' : '.js'))\n );\n }\n });\n }\n return api;\n },\n extendElementName: (tagName: string, extension: string): typeof api => {\n extendElementName(xmlFragment, tagName, extension);\n return api;\n },\n extendElementsWithClass: (param: string = 'extend'): typeof api => {\n extendElementsWithClass(xmlFragment, param);\n return api;\n },\n customInteraction(baseRef: string, baseItem: string): typeof api {\n const qtiCustomInteraction = xmlFragment.querySelector('qti-custom-interaction');\n const qtiCustomInteractionObject = qtiCustomInteraction.querySelector('object');\n\n qtiCustomInteraction.setAttribute('data-base-ref', baseRef);\n qtiCustomInteraction.setAttribute('data-base-item', baseRef + baseItem);\n qtiCustomInteraction.setAttribute('data', qtiCustomInteractionObject.getAttribute('data'));\n qtiCustomInteraction.setAttribute('width', qtiCustomInteractionObject.getAttribute('width'));\n qtiCustomInteraction.setAttribute('height', qtiCustomInteractionObject.getAttribute('height'));\n\n qtiCustomInteraction.removeChild(qtiCustomInteractionObject);\n return api;\n },\n convertCDATAtoComment(): typeof api {\n convertCDATAtoComment(xmlFragment);\n return api;\n },\n stripStyleSheets(): typeof api {\n stripStyleSheets(xmlFragment);\n return api;\n },\n html() {\n return new XMLSerializer().serializeToString(toHTML(xmlFragment));\n },\n xml(): string {\n return new XMLSerializer().serializeToString(xmlFragment);\n },\n htmlDoc() {\n return toHTML(xmlFragment);\n },\n xmlDoc(): XMLDocument {\n return xmlFragment; // new XMLSerializer().serializeToString(xmlFragment);\n }\n };\n return api;\n};\n","import { loadXML, parseXML } from './qti-transformers';\n\nexport const qtiTransformManifest = (): {\n load: (uri: string) => Promise<typeof api>;\n assessmentTest: () => { href: string; identifier: string };\n} => {\n let xmlFragment: XMLDocument;\n\n const api = {\n async load(uri) {\n return new Promise<typeof api>(resolve => {\n loadXML(uri).then(xml => {\n xmlFragment = xml;\n return resolve(api);\n });\n });\n },\n parse(xmlString: string) {\n xmlFragment = parseXML(xmlString);\n },\n assessmentTest() {\n const el = xmlFragment.querySelector('resource[type=\"imsqti_test_xmlv3p0\"]');\n return { href: el.getAttribute('href'), identifier: el.getAttribute('identifier') };\n }\n };\n return api;\n};\n","/**\n * Returns an object with methods to load, parse and transform QTI tests.\n * @returns An object with methods to load, parse and transform QTI tests.\n * @example\n * const qtiTransformer = qtiTransformTest();\n * await qtiTransformer.load('https://example.com/test.xml');\n * const items = qtiTransformer.items();\n * const html = qtiTransformer.html();\n * const xml = qtiTransformer.xml();\n */\n\nimport { itemsFromTest, loadXML, parseXML, setLocation, toHTML } from './qti-transformers';\n\nexport type transformTestApi = {\n load: (uri: string) => Promise<transformTestApi>;\n parse: (xmlString: string) => transformTestApi;\n path: (location: string) => transformTestApi;\n fn: (fn: (xmlFragment: XMLDocument) => void) => transformTestApi;\n items: () => { identifier: string; href: string; category: string }[];\n html: () => string;\n xml: () => string;\n htmlDoc: () => DocumentFragment;\n xmlDoc: () => XMLDocument;\n};\n\nexport const qtiTransformTest = (): transformTestApi => {\n let xmlFragment: XMLDocument;\n\n const api: transformTestApi = {\n async load(uri) {\n return new Promise<transformTestApi>((resolve, _) => {\n loadXML(uri).then(xml => {\n xmlFragment = xml;\n\n api.path(uri.substring(0, uri.lastIndexOf('/')));\n return resolve(api);\n });\n });\n },\n parse(xmlString: string) {\n xmlFragment = parseXML(xmlString);\n return api;\n },\n path: (location: string): typeof api => {\n setLocation(xmlFragment, location);\n return api;\n },\n fn(fn: (xmlFragment: XMLDocument) => void) {\n fn(xmlFragment);\n return api;\n },\n items() {\n return itemsFromTest(xmlFragment);\n },\n html() {\n return new XMLSerializer().serializeToString(toHTML(xmlFragment));\n },\n xml(): string {\n return new XMLSerializer().serializeToString(xmlFragment);\n },\n htmlDoc() {\n return toHTML(xmlFragment);\n },\n xmlDoc(): XMLDocument {\n return xmlFragment;\n }\n };\n return api;\n};\n"],"mappings":"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","names":["xml","xml","xml"]}
@@ -26,47 +26,11 @@
26
26
  "name": "--qti-border-radius",
27
27
  "description": "The option border radius",
28
28
  "values": []
29
- },
30
- {
31
- "name": "--show-value",
32
- "description": "shows the current value while sliding",
33
- "values": []
34
- },
35
- {
36
- "name": "--show-ticks",
37
- "description": "shows the ticks according to steps",
38
- "values": []
39
- },
40
- {
41
- "name": "--show-bounds",
42
- "description": "shows value for lower and upper boundary",
43
- "values": []
44
29
  }
45
30
  ],
46
31
  "pseudoElements": [
47
32
  { "name": "::part(slot)", "description": "The choice elements" },
48
33
  { "name": "::part(prompt)", "description": "The prompt" },
49
- { "name": "::part(message)", "description": "The validation message" },
50
- {
51
- "name": "::part(slider)",
52
- "description": "-- slider inluding, bounds and ticks and value, use it for paddings and margins"
53
- },
54
- {
55
- "name": "::part(bounds)",
56
- "description": "-- div for bounds, containing two divs for with min, and max bounds value"
57
- },
58
- {
59
- "name": "::part(ticks)",
60
- "description": "-- div for ticks, use lineair gradient and exposed css variables for styling"
61
- },
62
- {
63
- "name": "::part(rail)",
64
- "description": "-- div for rail, style according to needs"
65
- },
66
- {
67
- "name": "::part(knob)",
68
- "description": "-- div, should be relative or absolute"
69
- },
70
- { "name": "::part(value)", "description": "-- div, containing value" }
34
+ { "name": "::part(message)", "description": "The validation message" }
71
35
  ]
72
36
  }
@@ -467,6 +467,16 @@
467
467
  ],
468
468
  "references": []
469
469
  },
470
+ {
471
+ "name": "qti-upload-interaction",
472
+ "description": "\n---\n\n\n### **Events:**\n - **qti-interaction-response**\n- **qti-register-interaction**",
473
+ "attributes": [
474
+ { "name": "response-identifier", "values": [] },
475
+ { "name": "disabled", "values": [] },
476
+ { "name": "readonly", "values": [] }
477
+ ],
478
+ "references": []
479
+ },
470
480
  {
471
481
  "name": "qti-outcome-processing",
472
482
  "description": "\n---\n",
@@ -619,6 +629,7 @@
619
629
  "name": "qti-match-interaction",
620
630
  "description": "\n---\n\n\n### **Events:**\n - **qti-register-interaction**\n- **qti-interaction-response**",
621
631
  "attributes": [
632
+ { "name": "class", "values": [] },
622
633
  { "name": "response-identifier", "values": [] },
623
634
  { "name": "min-associations", "values": [] },
624
635
  { "name": "max-associations", "values": [] },
@@ -685,16 +696,11 @@
685
696
  },
686
697
  {
687
698
  "name": "qti-slider-interaction",
688
- "description": "The SliderInteraction.Type (qti-slider-interaction) presents the candidate with a control for selecting a numerical value between a lower and upper bound.\n---\n\n\n### **Events:**\n - **qti-register-interaction** - emitted when the interaction wants to register itself\n- **qti-interaction-response** - emitted when the interaction changes\n\n### **Slots:**\n - _default_ - The default slot where <qti-simple-choice> must be placed.\n- **prompt** - slot where the prompt is placed.\n\n### **CSS Properties:**\n - **--show-value** - shows the current value while sliding _(default: undefined)_\n- **--show-ticks** - shows the ticks according to steps _(default: undefined)_\n- **--show-bounds** - shows value for lower and upper boundary _(default: undefined)_\n\n### **CSS Parts:**\n - **slider** - -- slider inluding, bounds and ticks and value, use it for paddings and margins\n- **bounds** - -- div for bounds, containing two divs for with min, and max bounds value\n- **ticks** - -- div for ticks, use lineair gradient and exposed css variables for styling\n- **rail** - -- div for rail, style according to needs\n- **knob** - -- div, should be relative or absolute\n- **value** - -- div, containing value",
699
+ "description": "\n---\n\n\n### **Events:**\n - **change**",
689
700
  "attributes": [
690
- { "name": "step-label", "values": [] },
691
- { "name": "reverse", "values": [] },
692
701
  { "name": "lower-bound", "values": [] },
693
702
  { "name": "upper-bound", "values": [] },
694
- { "name": "step", "values": [] },
695
- { "name": "response-identifier", "values": [] },
696
- { "name": "disabled", "values": [] },
697
- { "name": "readonly", "values": [] }
703
+ { "name": "step", "values": [] }
698
704
  ],
699
705
  "references": []
700
706
  },
@@ -851,15 +857,9 @@
851
857
  "references": []
852
858
  },
853
859
  {
854
- "name": "qti-player",
855
- "description": "`<qti-test>` is a custom element designed for rendering and interacting with QTI (Question and Test Interoperability) tests.\n\nThis component leverages several mixins to provide functionality for loading, navigating, processing, and displaying QTI test assessments.\n\n### Example Usage\n\nMinimal markup:\n```html\n<qti-test test=\"./path/to/assessment.xml\">\n <test-container></test-container>\n</qti-test>\n```\n\nWith navigation buttons:\n```html\n<qti-test >\n <test-container test-url=\"./path/to/assessment.xml\"></test-container>\n <div class=\"flex\">\n <test-prev></test-prev>\n <test-next></test-next>\n </div>\n</qti-test>\n```\n\nYou can use normal class names to style the elements.\nAnd you can use the `test-prev` and `test-next` elements to navigate through the test.\n---\n",
856
- "attributes": [
857
- {
858
- "name": "testURL",
859
- "description": "the relative location to the QTI assessment.xml file",
860
- "values": []
861
- }
862
- ],
860
+ "name": "qti-test",
861
+ "description": "`<qti-test>` is a custom element designed for rendering and interacting with QTI (Question and Test Interoperability) tests.\n\nThis component leverages several mixins to provide functionality for loading, navigating, processing, and displaying QTI test assessments.\n\n### Example Usage\n\nMinimal example including navigation:\n\n```html\n<qti-test>\n <test-container test-url=\"./path/to/assessment.xml\"></test-container>\n <nav class=\"flex\">\n <test-prev></test-prev>\n <test-next></test-next>\n </nav>\n</qti-test>\n```\n\nUse the following file structure\nA qti-test loads a QTI3.0 assessmenttest.xml file from a package folder.\n\n```plaintext\nRoot/\n├── index.html\n└── /assets/api/examples/\n ├── assessmenttest.xml\n └── imsmanifest.xml\n\n```\n\n### Test components\n\nUse test components inside the qti-test component for added functionality.\n### Test next\n`<test-next> | TestNext`\n\n### Test prev\n\n`<test-prev> | TestPrev`\n### Test components\n\nYou can use normal class names to style the elements.\nAnd you can use the `test-prev` and `test-next` elements to navigate through the test.\n---\n",
862
+ "attributes": [],
863
863
  "references": []
864
864
  },
865
865
  {
@@ -891,8 +891,14 @@
891
891
  },
892
892
  {
893
893
  "name": "test-container",
894
- "description": "`<test-container>` is a custom element designed for hosting the qti-assessment-test.\nThe `qti-assessment-test` will be placed inside the shadow DOM of this element.\n\n### Example Usage\nThe `test-container` element to hosts the visual representation of the items.\nYou can style the container by adding a class to the element.\n\n```html\n<qti-test testurl=\"./path/to/assessment.xml\">\n <test-container class=\"container bg-white m-2\"></test-container>\n</qti-test>\n```\n---\n",
895
- "attributes": [{ "name": "test-url", "values": [] }],
894
+ "description": "`<test-container>` is a custom element designed for hosting the qti-assessment-item.\nThe `qti-assessment-test` will be placed inside the shadow DOM of this element.\nThe element loads the item from the provided URL and renders it inside the shadow DOM.\n\n### Styling\nAdd a class to the element for styling.\n\n```html\n<qti-test>\n <test-container class=\"m-4 bg-white\" test-url=\"./path/to/item.xml\"></test-container>\n</qti-test>\n```\n---\n",
895
+ "attributes": [
896
+ {
897
+ "name": "test-url",
898
+ "description": "URL of the item to load",
899
+ "values": []
900
+ }
901
+ ],
896
902
  "references": []
897
903
  },
898
904
  {
@@ -906,10 +912,19 @@
906
912
  },
907
913
  {
908
914
  "name": "qti-item",
909
- "description": "\n---\n\n\n### **Events:**\n - **qti-item-connected**",
915
+ "description": "`<qti-item>` is a custom element designed for rendering a single `qti-assessment-item`.\nIt can also host some functionalities to interact with the item like scoring, showing feedback, etc.\nPlacing a mandatory `<item-container>` inside '<qti-item>' will load or parse the item and render it.\nSee `<item-container>` for more details.\n\n```html\n<qti-item>\n <item-container class=\"m-4 bg-white\" item-url=\"./path/to/item.xml\"></item-container>\n</qti-item>\n```\n---\n",
916
+ "attributes": [],
917
+ "references": []
918
+ },
919
+ {
920
+ "name": "item-container",
921
+ "description": "`<item-container>` is a custom element designed for hosting the qti-assessment-item.\nThe `qti-assessment-item` will be placed inside the shadow DOM of this element.\nThe element loads the item from the provided URL and renders it inside the shadow DOM.\n\n### Styling\nAdd a class to the element for styling.\n\n```html\n<qti-item>\n <item-container class=\"m-4 bg-white\" item-url=\"./path/to/item.xml\"></item-container>\n</qti-item>\n```\n---\n",
910
922
  "attributes": [
911
- { "name": "identifier", "values": [] },
912
- { "name": "href", "values": [] }
923
+ {
924
+ "name": "item-url",
925
+ "description": "URL of the item to load",
926
+ "values": []
927
+ }
913
928
  ],
914
929
  "references": []
915
930
  }
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20.0.0"
23
23
  },
24
- "version": "7.0.2",
24
+ "version": "7.0.4",
25
25
  "main": "dist/index.js",
26
26
  "type": "module",
27
27
  "exports": {
@@ -36,7 +36,8 @@
36
36
  },
37
37
  "./item.css": "./dist/item.css",
38
38
  "./cdn/index.js": "./cdn/index.js",
39
- "./cdn/index.global.js": "./cdn/index.global.js"
39
+ "./cdn/index.global.js": "./cdn/index.global.js",
40
+ "./react": "./dist//qti-components-jsx.d.ts"
40
41
  },
41
42
  "types": "./dist/qti-components/index.d.ts",
42
43
  "typesVersions": {
@@ -98,7 +99,8 @@
98
99
  "files": [
99
100
  "src/**/*.{ts}",
100
101
  "tsconfig.json",
101
- "!src/**/*.{test,spec,stories}.ts"
102
+ "!src/**/*.{test,spec,stories}.ts",
103
+ "package.json"
102
104
  ]
103
105
  },
104
106
  "cem": {
@@ -217,6 +219,7 @@
217
219
  "remark-gfm": "^4.0.0",
218
220
  "shadow-dom-testing-library": "^1.11.3",
219
221
  "storybook": "^8.4.7",
222
+ "storybook-addon-tag-badges": "^1.3.2",
220
223
  "stylelint": "^16.11.0",
221
224
  "stylelint-config-standard": "^36.0.1",
222
225
  "stylelint-prettier": "^5.0.2",
@@ -232,7 +235,7 @@
232
235
  "optionalDependencies": {
233
236
  "@rollup/rollup-linux-x64-gnu": "^4.28.1"
234
237
  },
235
- "customElements": "dist/custom-elements.json",
238
+ "customElements": "custom-elements.json",
236
239
  "bugs": {
237
240
  "url": "https://github.com/Citolab/qti-components/issues"
238
241
  },
package/readme.md ADDED
@@ -0,0 +1,55 @@
1
+ # @citolab/qti-components
2
+
3
+ <a href="https://www.repostatus.org/#wip"><img src="https://www.repostatus.org/badges/latest/wip.svg" alt="Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public." /></a>
4
+
5
+ [![npm version](https://badge.fury.io/js/%40citolab%2Fqti-components.svg)](https://badge.fury.io/js/%40citolab%2Fqti-components)
6
+ [![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://opensource.org/license/gpl-2-0/)
7
+
8
+ @citolab/qti-components is a web component library that can be used to render 1EdTech QTI items.
9
+ It's highly customizable and can be integrated in almost every web application.
10
+
11
+ ## Installation
12
+
13
+ ### CDN
14
+
15
+ Import the components with the CDN version
16
+
17
+ ```html
18
+ <script type="module">
19
+ import 'https://unpkg.com/@citolab/qti-components/cdn';
20
+ </script>
21
+ ```
22
+
23
+ ### NPM
24
+
25
+ Or use npm to install the package:
26
+
27
+ ```shell
28
+ npm install @citolab/qti-components
29
+ ```
30
+
31
+ To use @citolab/qti-components, you need to register the web components. By registering the web components, the browser knows how to display the item and create a fully functional test.
32
+
33
+ ```javascript
34
+ import '@citolab/qti-components';
35
+ ```
36
+
37
+ ## Storybook
38
+
39
+ Explore the complete description of supported QTI tags, examples, and the ability to interactively play with changing attributes of the QTI items in our [Storybook](https://qti-components.citolab.nl/).
40
+
41
+ ## Contributing
42
+
43
+ Contributions are welcome! Please follow these guidelines when contributing:
44
+
45
+ - Fork the repository and clone it to your local machine
46
+ - Create a new branch for your feature or bug fix
47
+ - Commit your changes with clear and concise messages
48
+ - Push your changes to your forked repository
49
+ - Open a pull request to the original repository
50
+
51
+ ## License
52
+
53
+ This project is licensed under the [GPLv3 License](LICENSE).
54
+
55
+ Please note that the licensing is GPLv3 if you want to use it in another way, feel free to ask!
package/README.md DELETED
@@ -1,85 +0,0 @@
1
- # @citolab/qti-components
2
-
3
- <a href="https://www.repostatus.org/#wip"><img src="https://www.repostatus.org/badges/latest/wip.svg" alt="Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public." /></a>
4
-
5
- [![npm version](https://badge.fury.io/js/%40citolab%2Fqti-components.svg)](https://badge.fury.io/js/%40citolab%2Fqti-components)
6
- [![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://opensource.org/license/gpl-2-0/)
7
-
8
- @citolab/qti-components is a web component library that can be used to render 1EdTech QTI items.
9
- It's highly customizable and can be integrated in almost every web application.
10
-
11
- ## Installation
12
-
13
- Use npm to install the package:
14
-
15
- ```shell
16
- npm install @citolab/qti-components
17
- ```
18
-
19
- ## Usage
20
-
21
- To use @citolab/qti-components, you need to register the web components and import the required CSS file. By registering the web components, the browser knows how to display the item and create a fully functional item.
22
-
23
- Here's a basic example:
24
-
25
- ```javascript
26
- import * as QTI from 'https://unpkg.com/@citolab/qti-components@latest/dist/index.js';
27
- ```
28
-
29
- ```css
30
- @import 'https://unpkg.com/@citolab/qti-components@latest/dist/index.css';'
31
- ```
32
-
33
- ```html
34
- <qti-assessment-item identifier="choice" title="Unattended Luggage" adaptive="false" time-dependent="false">
35
- <qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="identifier">
36
- <qti-correct-response>
37
- <qti-value>ChoiceA</qti-value>
38
- </qti-correct-response>
39
- </qti-response-declaration>
40
- <qti-outcome-declaration identifier="SCORE" cardinality="single" base-type="float">
41
- <qti-default-value>
42
- <qti-value>0</qti-value>
43
- </qti-default-value>
44
- </qti-outcome-declaration>
45
- <qti-item-body>
46
- <p>Look at the text in the picture.</p>
47
- <p>
48
- <img src="images/sign.png" alt="NEVER LEAVE LUGGAGE UNATTENDED" />
49
- </p>
50
- <qti-choice-interaction response-identifier="RESPONSE" shuffle="false" max-choices="1">
51
- <qti-prompt>What does it say?</qti-prompt>
52
- <qti-simple-choice identifier="ChoiceA">You must stay with your luggage at all times.</qti-simple-choice>
53
- <qti-simple-choice identifier="ChoiceB">Do not let someone else look after your luggage.</qti-simple-choice>
54
- <qti-simple-choice identifier="ChoiceC">Remember your luggage when you leave.</qti-simple-choice>
55
- </qti-choice-interaction>
56
- </qti-item-body>
57
- <qti-response-processing template="https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/match_correct.xml" />
58
- </qti-assessment-item>
59
- ```
60
-
61
- You can see a working example here [JSFiddle](https://jsfiddle.net/mrklein/s97Ld0gn).
62
-
63
- ## Storybook
64
-
65
- Explore the complete description of supported QTI tags, examples, and the ability to interactively play with changing attributes of the QTI items in our [Storybook](https://qti-components.citolab.nl/).
66
-
67
- ## Contributing
68
-
69
- Contributions are welcome! Please follow these guidelines when contributing:
70
-
71
- - Fork the repository and clone it to your local machine
72
- - Create a new branch for your feature or bug fix
73
- - Commit your changes with clear and concise messages
74
- - Push your changes to your forked repository
75
- - Open a pull request to the original repository
76
-
77
- ## License
78
-
79
- This project is licensed under the [GPLv3 License](LICENSE).
80
-
81
- Please note that the licensing is GPLv3 if you want to use it in another way, feel free to ask!
82
-
83
- ```
84
-
85
- ```