diff options
Diffstat (limited to 'static/quiz-parser.js')
| -rw-r--r-- | static/quiz-parser.js | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/static/quiz-parser.js b/static/quiz-parser.js new file mode 100644 index 0000000..c62beb5 --- /dev/null +++ b/static/quiz-parser.js @@ -0,0 +1,144 @@ +/** + * Quiz Parser - Converte sintaxe customizada em JSON estruturado + * Sintaxe suportada: + * ? Enunciado da questão + * - Opção A / - Opção B + * = Opção correta (incluída nas opções automaticamente se ausente) + * > Comentário (pode repetir; linha vazia separa parágrafos) + * + * ? Questão aberta + * [ Resposta de referência (pode repetir) + * > Comentário + * + * --- ou linha em branco separam questões + * # linha ignorada + */ + +function parseQuizBody(raw) { + if (!raw || !raw.trim()) return []; + const lines = raw.replace(/\r\n/g, '\n').split('\n'); + const result = []; + let enunciado = '', tipo = '', opcoes = [], gabarito = '', referencia = '', comentario = ''; + + function flush() { + if (!enunciado.trim()) return; + const q = { type: tipo || 'mc', text: enunciado.trim() }; + if (tipo === 'mc') { + q.options = opcoes; + q.answer = gabarito; + } else { + q.reference = referencia.trim(); + } + if (comentario.trim()) q.comment = comentario.trim(); + result.push(q); + enunciado = ''; + tipo = ''; + opcoes = []; + gabarito = ''; + referencia = ''; + comentario = ''; + } + + for (const rawLine of lines) { + const line = rawLine.replace(/\r$/, '').trimEnd(); + if (/^---+\s*$/.test(line)) { + flush(); + continue; + } + if (line.trim() === '') continue; + const mark = line[0]; + const content = line.length > 1 ? line.slice(2) : ''; + switch (mark) { + case '#': + break; + case '?': + flush(); + enunciado = content; + break; + case '-': + opcoes.push(content); + tipo = 'mc'; + break; + case '=': + gabarito = content; + tipo = 'mc'; + if (content && !opcoes.includes(content)) opcoes.push(content); + break; + case '[': + referencia += (referencia ? '\n' : '') + content; + tipo = 'open'; + break; + case '>': + comentario += (comentario ? '\n' : '') + content; + break; + default: + if (enunciado && !tipo) enunciado += '\n' + line; + else if (tipo === 'open' && referencia) referencia += '\n' + line; + } + } + flush(); + return result; +} + +/* ── UTILITÁRIOS BASE ── */ +const LETRAS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + +function shuffle(arr) { + const a = [...arr]; + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [a[i], a[j]] = [a[j], a[i]]; + } + return a; +} + +function prepareQuizQuestions(questions) { + const letterCounts = []; + let previousCorrectIndex = -1; + return questions.map((q) => { + if (!q || q.type !== 'mc' || !Array.isArray(q.options) || q.options.length < 2) { + return q; + } + const answerIndex = q.options.findIndex((o) => o === q.answer); + if (answerIndex < 0) { + return { ...q, options: shuffle(q.options) }; + } + const optionCount = q.options.length; + const availableIndexes = Array.from({ length: optionCount }, (_, i) => i); + const lowestCount = Math.min(...availableIndexes.map((i) => letterCounts[i] || 0)); + let candidates = availableIndexes.filter((i) => (letterCounts[i] || 0) === lowestCount); + if (optionCount > 1 && candidates.length > 1) { + candidates = candidates.filter((i) => i !== previousCorrectIndex); + } + const correctIndex = shuffle(candidates)[0] ?? 0; + const otherOptions = shuffle(q.options.filter((_, i) => i !== answerIndex)); + const arrangedOptions = [...otherOptions]; + arrangedOptions.splice(correctIndex, 0, q.options[answerIndex]); + letterCounts[correctIndex] = (letterCounts[correctIndex] || 0) + 1; + previousCorrectIndex = correctIndex; + return { ...q, options: arrangedOptions }; + }); +} + +/* Para uso em atributos HTML (onclick="...") */ +function esc(s) { + return String(s ?? '') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +/* Para innerHTML: escapa HTML e renderiza Markdown inline. + Suporta: **negrito**, *itálico*, `código`, \n→<br> */ +function md(s) { + return String(s ?? '') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') + .replace(/\*(.+?)\*/g, '<em>$1</em>') + .replace(/`(.+?)`/g, '<code>$1</code>') + .replace(/\n/g, '<br>'); +} |
