/** * 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, '''); } /* Para innerHTML: escapa HTML e renderiza Markdown inline. Suporta: **negrito**, *itálico*, `código`, \n→
*/ function md(s) { return String(s ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/`(.+?)`/g, '$1') .replace(/\n/g, '
'); }