/**
* 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, '
');
}