UNPKG

@webhare/moodle-webservice

Version:

Moodle Web Service API client with intellisense and typechecking

183 lines (182 loc) 8.81 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const node_html_parser_1 = require("node-html-parser"); const MoodleQuestion_1 = __importDefault(require("./MoodleQuestion")); const types_1 = require("../types"); const debug_1 = __importDefault(require("debug")); class MoodleQMultiChoice { //TODO: Replace with Custom Error class in the future static _error(message) { return new Error(message); } static _couldNotFind(thing) { return MoodleQMultiChoice._error(`Could not find ${thing} in HTML data.`); } static _removeChoiceNumber(label) { return label.slice(3); } static _extractChoiceLabel(choiceElement) { MoodleQMultiChoice._debug(`Extracting label from choice HTML element...`); const labelElement = choiceElement.querySelector('div[data-region="answer-label"]'); if (!labelElement) throw MoodleQMultiChoice._couldNotFind(`label element`); const label = MoodleQMultiChoice._removeChoiceNumber(labelElement.text); MoodleQMultiChoice._debug(`Successfully extracted label: ${label} from choice HTML element.`); return label; } static _extractChoiceInputElement(choiceElement) { MoodleQMultiChoice._debug(`Extracting input element from choice HTML element...`); const inputElement = choiceElement.querySelector('input[type="radio"]'); if (!inputElement) throw MoodleQMultiChoice._couldNotFind("input element"); MoodleQMultiChoice._debug(`Successfully extracted input element from choice HTML element.`); return inputElement; } static _extractChoice(choiceElement) { const inputElement = MoodleQMultiChoice._extractChoiceInputElement(choiceElement); const label = MoodleQMultiChoice._extractChoiceLabel(choiceElement); const isChosen = inputElement.getAttribute("checked") === "checked"; const value = Number(inputElement.getAttribute("value")); //TODO: Find what images look like and extract them as well. const choice = { label, value }; return { choice, isChosen }; } static _extractChoices(parsedHTML) { let chosen; const choices = []; const choiceElements = parsedHTML.querySelectorAll(".answer > div"); if (choiceElements.length === 0) throw MoodleQMultiChoice._couldNotFind("choice elements"); for (let i = 0; i < choiceElements.length; i++) { const choiceElement = choiceElements[i]; MoodleQMultiChoice._debug(`Extracting choice number: ${i}...`); const { choice, isChosen } = MoodleQMultiChoice._extractChoice(choiceElement); choices.push(choice); MoodleQMultiChoice._debug(`Successfully extracted choice number: ${i}.`); if (isChosen) { MoodleQMultiChoice._debug(`Found selected answer: #${choice.value}.`); chosen = choice.value; } } return { choices, chosen }; } static _extractAnswerFromChoices(choices, chosen) { const answer = choices.find((choice) => choice.value === chosen); if (answer) { MoodleQMultiChoice._debug(`Successfully retrieved answer from choices.`); } else { MoodleQMultiChoice._debug(`Could not find answer within choices... possible bug here.`); } return answer; } static _extractAnswerLabelFromHTML(parsedHTML) { const answerBoxText = parsedHTML.querySelector(".rightanswer")?.text; const answer = /(is|are)[ ]?:[ ]?([\s\S]*)/.exec(answerBoxText ?? "")?.[2]; if (answer) MoodleQMultiChoice._debug(`Successfully extracted answer label from HTML, label: <${answer}>.`); else MoodleQMultiChoice._debug(`Could not find answer label in HTML, possible bug here.`); return answer; } static _parseSettings(settings) { return JSON.parse(settings); } static _checkCompatibility(question) { if (question.type !== types_1.QuestionTypes.MultiChoice) throw MoodleQMultiChoice._error("Trying to parse a question that is not multichoice!"); } static _removeHTML(question) { const noHTMLQuestion = { ...question }; delete noHTMLQuestion.html; return noHTMLQuestion; } static toUpdate(question) { const answer = question.answer?.value; MoodleQMultiChoice._debug(`Successfully converted multichoice question <${question.instance}> to update object.`); return { instance: question.instance, slot: question.slot, answer, flagged: question.flagged ? 1 : 0, sequencecheck: question.sequencecheck, }; } /**Copies answer from source and returns a new question object with the correct answer * if no answer is found, the destination question is returned as is. */ static cheatFrom(destination, source) { for (const destChoice of destination.choices) { const typedAnswer = source.answer; if (destChoice.label === typedAnswer.label) { return { ...destination, chosen: destChoice.value, answer: { label: typedAnswer.label, value: destChoice.value, }, }; } } return destination; } static match(questionA, questionB) { //TODO: find a stricter criteria for finding matching questions. MoodleQMultiChoice._debug(`Matching multichoice questions <${questionA.instance}:${questionA.slot}> and <${questionB.instance}:${questionB.slot}>...`); const match = questionA.text === questionB.text; MoodleQMultiChoice._debug(match ? `Questions <${questionA.instance}:${questionA.slot}> and <${questionB.instance}:${questionB.slot}> match!` : `Questions <${questionA.instance}:${questionA.slot}> and <${questionB.instance}:${questionB.slot}> do not match.`); return match; } static parse(question) { MoodleQMultiChoice._debug(`Parsing multichoice question #${question.slot}...`); MoodleQMultiChoice._checkCompatibility(question); const settings = MoodleQMultiChoice._parseSettings(question.settings); const parsedHTML = (0, node_html_parser_1.parse)(question.html); // Accessing 'private' members. // Since there is really no such thing as 'private' members in javascript const instance = MoodleQuestion_1.default["_extractInstance"](parsedHTML); const text = MoodleQuestion_1.default["_extractText"](parsedHTML); const { choices, chosen } = MoodleQMultiChoice._extractChoices(parsedHTML); //Only answered questions have a state let answer; if (question.state) { switch (question.state) { case types_1.QuestionStates.GradedRight: MoodleQMultiChoice._debug(`Question is already graded right, getting the chosen answer...`); answer = MoodleQMultiChoice._extractAnswerFromChoices(choices, chosen); break; default: MoodleQMultiChoice._debug(`Question is graded wrong or given up on, extracting answer from HTML...`); const answerLabel = MoodleQMultiChoice._extractAnswerLabelFromHTML(parsedHTML); const answerValue = choices.find((choice) => choice.label === answerLabel)?.value; if (answerValue) MoodleQMultiChoice._debug(`Succesfully extracted answer value from HTML, value: <${answerValue}>.`); else MoodleQMultiChoice._debug(`Could not extract answer value from HTML, possible bug here.`); answer = { label: answerLabel, value: answerValue }; break; } } else MoodleQMultiChoice._debug(`Question is not graded, cannot extract answer.`); MoodleQMultiChoice._debug(`Successfully parsed multichoice question #${question.slot}...`); return { ...MoodleQMultiChoice._removeHTML(question), mark: question.mark ? Number(question.mark) : undefined, settings, instance, text, choices, chosen, answer, }; } } MoodleQMultiChoice._debug = (0, debug_1.default)("moodle:helper:question:multichoice"); exports.default = MoodleQMultiChoice;