// ==UserScript== // @name 学习通万能题目 提取器 // @namespace http://tampermonkey.net/ // @version 5.0.1 // @description 自动提取学习通作业/考试/章节测验题目并一键导出Word。加强章节识别、标题去噪与去重、修复填空题排版、去掉题干重复编号并输出显式章节标题。) // @author kkkxfr // @match *://*.chaoxing.com/exam-ans/exam/test/reVersionPaperMarkContentNew* // @match *://*.chaoxing.com/exam-ans/exam/test/look* // @match *://*.chaoxing.com/mooc-ans/work/selectWorkQuestionYiPiYue* // @match *://*.mooc1.chaoxing.com/mooc-ans/mooc2/work/view?* // @match *://*.chaoxing.com/work/doHomeWorkNew* // @match *://*.mooc-ans/work/doHomeWorkNew* // @match *://*.mooc1-api.chaoxing.com/mooc-ans/mooc2/work* // @match *://*.chaoxing.com/api/selectWorkQuestionYiPiYue* // @match *://*mooc2-ans.chaoxing.com/mooc2-ans/mycourse* // @grant GM_setClipboard // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // ==/UserScript== (function(){ 'use strict'; const AUTO_EXTRACT_KEY = 'chaoxing_auto_extract_enabled'; /* ---------- 基础辅助 ---------- */ function normalizeText(s){ if(!s) return ''; return String(s).replace(/\u00A0/g,' ').replace(/\r?\n/g,' ').replace(/\s+/g,' ').trim(); } function getCleanText(el){ if(!el) return ''; const c = el.cloneNode(true); c.querySelectorAll('script, style, .mark_letter, .Cy_ulTop').forEach(x => x.remove()); let html = c.innerHTML || ''; html = html.replace(/
]*>/gi, '\n').replace(/
]*>/gi, '\n');
const tmp = document.createElement('div');
tmp.innerHTML = html;
return normalizeText(tmp.textContent || tmp.innerText || '');
}
function xmlEscape(s){ if(!s) return ''; return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); }
function stripLeadingLabels(s){
if(!s) return '';
let t = normalizeText(s);
while(/^(?:答案解析|正确答案|参考答案|答案|解析)[::\s]*/i.test(t)){
t = t.replace(/^(?:答案解析|正确答案|参考答案|答案|解析)[::\s]*/i, '');
}
return t.trim();
}
function pullLeadingScore(stem){
if(!stem) return {score:'', rest: ''};
const s = stem.trim();
const m = s.match(/^[,,]?\s*[\((]?\s*([0-9]+(?:\.[0-9]+)?)\s*分\s*[\)\)]?\s*(.*)$/);
if(m){ return { score: m[1] + ' 分', rest: (m[2]||'').trim() }; }
return { score: '', rest: s };
}
/* ---------- 标题清洗(去噪、标准化) ---------- */
function sanitizeSectionTitleRaw(title){
if(!title) return '';
let t = normalizeText(title);
// 去掉前导分数字样: "0分 "、"(100分)"、"(0分)" 等
t = t.replace(/^[((]?\s*[\d0-9]+\s*分[))]?\s*/i, '');
// 去掉答题卡里可能带的前缀(如 "未评分 "、"已批改")
t = t.replace(/^(?:未评分|未批改|已批改|已做|0分|0分:)\s*/i, '');
// 全角数字 -> 半角
t = t.replace(/[\uFF10-\uFF19]/g, c => String.fromCharCode(c.charCodeAt(0) - 0xFF10 + 48));
// 规范化括号和空白(把各种形式的 "共 N 题" 统一)
t = t.replace(/(\s*共\s*(\d+)\s*题\s*)/g, '(共 $1 题)');
t = t.replace(/\(\s*共\s*(\d+)\s*题\s*\)/g, '(共 $1 题)');
// 去掉重复的括号片段(保留第一次)
t = t.replace(/(([^)]+))\s*\1+/g, '$1');
// 移除行首与行尾多余的符号
t = t.replace(/^[#\-\:\s ]+|[#\-\:\s ]+$/g, '');
t = t.trim();
return t;
}
/* ---------- 页面标题 ---------- */
function getPageTitle(){
const selectors=[
'#workTitle', '.work_title', 'h3.mark_title', '.courseName', '.course-title',
'.mark_title', '.title', '.detailsHead', 'h1', 'h2', 'h3', '.fanyaMarking_left.whiteBg'
];
for(const sel of selectors){
try{
const els = document.querySelectorAll(sel);
for(const el of els){
const t = normalizeText(el.innerText||el.textContent||'');
if(t && t.length > 2 &&
!/^(?:error|404|登录|未找到|提示|查看已批阅|作业详情)$/i.test(t) &&
!/[((]\s*(?:单选|多选|判断|填空)/.test(t) &&
!/^[0-9]+[\.、\s]/.test(t)
){
return t.split(/\r?\n/)[0];
}
}
}catch(e){}
}
const docTitle = normalizeText(document.title||'');
if(docTitle && docTitle.length > 2 && !/查看已批阅|作业详情/i.test(docTitle)) return docTitle;
return '试卷';
}
/* ---------- 题目/选项定位 ---------- */
function findOptionContainers(qNode){
const sels = ['.mark_letter','.Cy_ulTop','.Zy_ulTop','.options','.option-list','.optionsList'];
const found = [];
for(const s of sels){
const el = qNode.querySelector(s);
if(el) found.push(el);
}
const uls = Array.from(qNode.querySelectorAll('ul,ol'));
uls.forEach(u=>{
if((u.className && /ul|options|Cy_ulTop|Zy_ulTop|mark_letter/.test(u.className)) || (u.querySelector('li') && !u.textContent.includes('我的答案'))) {
if(!found.includes(u)) found.push(u);
}
});
return found;
}
/* ---------- 答案提取 ---------- */
function extractCorrectAnswerFromNode(qNode, qType){
if(!qNode) return null;
const isFill = !!(qType && /填空|简答|问答|填充|主观|主观题/i.test(qType));
const strictAnsRe = /(?:正确答案|参考答案|答案)[::\s]*([A-Da-d对错√×]+)/i;
const strictMyRe = /(?:我的答案|My Answer)[::\s]*([A-Da-d对错√×]+)/i;
const looseAnsRe = /(?:正确答案|参考答案|答案)[::\s]*([\s\S]{1,200})/i;
const looseMyRe = /(?:我的答案|My Answer)[::\s]*([\s\S]{1,200})/i;
const rightEls = Array.from(qNode.querySelectorAll('.rightAnswerContent'));
if(rightEls.length){
const texts = rightEls.map(el => getCleanText(el)).filter(Boolean);
if(texts.length) return texts.join('\n');
}
const selectors = ['.mark_key','.mark_answer','.right_answer','.answerRight','.Py_answer','.answer-content','.answer'];
const collected = [];
for(const s of selectors){
const els = Array.from(qNode.querySelectorAll(s));
for(const el of els){
const t = getCleanText(el);
if(!t || /未提取|未作答|暂无/.test(t)) continue;
if(isFill){
const m = t.match(looseAnsRe);
if(m && m[1]) { collected.push(m[1].trim()); continue; }
collected.push(t.trim());
} else {
const m = t.match(strictAnsRe);
if(m && m[1]) { collected.push(m[1].trim()); continue; }
const short = t.replace(/\s+/g,'');
if(/^[A-D]+$/i.test(short)) collected.push(short);
}
}
}
if(collected.length) return collected.join('\n');
let clone = qNode.cloneNode(true);
clone.querySelectorAll('ul, ol, .mark_letter, .Cy_ulTop, .options, .option-list').forEach(el => el.remove());
clone.querySelectorAll('.mark_name, .Cy_TItle, .Qtitle, h3').forEach(el => el.remove());
const remainingText = normalizeText(clone.innerText || clone.textContent || '');
if(remainingText){
if(isFill){
const exactMatch = remainingText.match(looseAnsRe);
if(exactMatch && exactMatch[1]) return exactMatch[1].trim();
} else {
const exactMatch = remainingText.match(strictAnsRe);
if(exactMatch && exactMatch[1]) return exactMatch[1].trim();
}
}
if(isFill){
const myMatch = remainingText.match(looseMyRe);
if(myMatch && myMatch[1]) return myMatch[1].trim();
} else {
const myMatch = remainingText.match(strictMyRe);
if(myMatch && myMatch[1]) return myMatch[1].trim();
}
try {
const inputs = Array.from(qNode.querySelectorAll('input[type="text"], input[type="hidden"], textarea, input[type="search"]'));
const inputVals = inputs.map(inp => {
return (inp.value || inp.getAttribute('value') || (inp.dataset && (inp.dataset.answer || inp.dataset.right)) || inp.getAttribute('data-answer') || inp.getAttribute('data-right') || inp.getAttribute('placeholder') || '').trim();
}).filter(v => v);
if(inputVals.length) return inputVals.join(' / ');
} catch(e){}
const blankSelectors = [
'.fill-blank', '.fillblank', '.blank-input', '.blank', '.filling_answer', '.fill-answer',
'.blank-list', '.answerBlank', '.answer_blank', '.textAnswer', '.answer-input', '.answerValue'
];
for(const bs of blankSelectors){
const els = qNode.querySelectorAll(bs);
if(els && els.length){
const vals = Array.from(els).map(e => {
if(e.tagName === 'INPUT' || e.tagName === 'TEXTAREA') {
return (e.value || e.getAttribute('value') || '').trim();
}
const txt = getCleanText(e) || (e.dataset && (e.dataset.answer || e.dataset.right)) || '';
return String(txt).trim();
}).filter(Boolean);
if(vals.length) return vals.join(' / ');
}
}
const dataAnswerEl = qNode.querySelector('[data-answer], [data-right], [data-true-answer], [data-key]');
if(dataAnswerEl){
const v = (dataAnswerEl.getAttribute('data-answer') || dataAnswerEl.getAttribute('data-right') || dataAnswerEl.getAttribute('data-true-answer') || dataAnswerEl.getAttribute('data-key') || '').trim();
if(v) return v;
}
try {
const candidates = qNode.querySelectorAll('span, label');
for(const c of candidates){
const txt = getCleanText(c);
if(!txt) continue;
const m = txt.match(/^(?:答案|正确答案|参考答案|\:)\s*([\s\S]{1,200})/i);
if(m && m[1]) return m[1].trim();
}
} catch(e){}
return null;
}
/* ---------- 规范化 ---------- */
function normalizeAnswerString(s, qType){
if(!s) return '';
s = normalizeText(s);
const blanks = s.match(/(?:\(\s*\d+\s*\)|(\s*\d+\s*))[\s\S]*?(?=(?:\(\s*\d+\s*\)|(\s*\d+\s*)|$))/g);
if(blanks && blanks.length > 1) return blanks.map(b => normalizeText(b)).join(' ');
const letters = s.match(/[A-D]/gi);
if(letters){
const upper = letters.map(x => x.toUpperCase());
const uniq = Array.from(new Set(upper));
if((qType === '单选题' || qType === '单选') && uniq.length > 1) {
const strictMatch = s.match(/(?:正确答案|答案)[::\s]*([A-D])/i);
if(strictMatch) return strictMatch[1].toUpperCase();
if(uniq.length > 2) return '';
return uniq.sort().join('');
}
return uniq.sort().join('');
}
if(/^(对|正确|true|√)$/i.test(s)) return '对';
if(/^(错|错误|false|×)$/i.test(s)) return '错';
return s;
}
/* ---------- 拆分多空 ---------- */
function splitNumberedBlanks(s){
if(!s) return s;
s = String(s).trim();
s = s.replace(/\u00A0/g,' ').replace(/\r?\n/g,'\n').replace(/[ \t\v\f]+/g,' ');
if(/\(\s*\d+\s*\)/.test(s)){
const regex = /\(\s*(\d+)\s*\)\s*([\s\S]*?)(?=(?:\(\s*\d+\s*\)|$))/g;
let m; const parts = [];
while((m = regex.exec(s)) !== null){
const idx = parseInt(m[1], 10);
let content = String(m[2] || '').trim();
content = content.replace(/^\s+|\s+$/g,'').replace(/\r?\n+/g,' ');
content = content.replace(/\s{2,}/g,' ');
parts.push({ idx, content });
}
if(parts.length){
parts.sort((a,b)=> a.idx - b.idx);
return parts.map(p => `(${p.idx}) ${p.content}`).join('\n');
}
}
const m1 = s.match(/^\(?\s*1\s*\)?\s*([\s\S]+)$/);
if(m1){
let rest = m1[1].trim();
if(rest){
let parts = rest.split(/\s{2,}|\/|;|;|,|,|\|/).map(p => p.trim()).filter(Boolean);
if(parts.length <= 1){
const tokens = rest.split(/\s+/).map(t => t.trim()).filter(Boolean);
if(tokens.length > 1 && tokens.every(t => /^[A-Za-z0-9\-_]+$/.test(t)) ){
parts = tokens;
}
}
if(parts.length > 1){
return parts.map((p, idx) => `(${idx+1}) ${p.trim()}`).join('\n');
}
}
}
return s;
}
/* ---------- 合并为单行显示 ---------- */
function renderAnswerInline(ans){
if(!ans) return '';
return String(ans).replace(/\r?\n+/g,' ').replace(/\s{2,}/g,' ').trim();
}
/* ---------- 更鲁棒章节识别 ---------- */
function findNearestSectionTitle(node, root){
try{
const headerSels = [
'.type_tit', '.Cy_TItle1', 'h1', 'h2', 'h3', 'h4', '.markTitle',
'.typeTitle', '.mark_section', '.section-title', '.question-type-title', '.headline'
];
let cur = node;
for(let i=0;i<20;i++){
if(!cur || cur === document.body) break;
let ps = cur.previousElementSibling;
while(ps){
for(const sel of headerSels){
if(ps.matches && ps.matches(sel)){
const t = normalizeText(ps.innerText||ps.textContent||'');
if(t && !/^\d+[.、]/.test(t) && !/^[((]\s*\d+\s*分\s*[))]$/.test(t) && t.length < 200) return t;
}
}
if(ps.querySelector){
for(const sel of headerSels){
const sub = ps.querySelector(sel);
if(sub){
const t = normalizeText(sub.innerText||sub.textContent||'');
if(t && !/^\d+[.、]/.test(t) && !/^[((]\s*\d+\s*分\s*[))]$/.test(t) && t.length < 200) return t;
}
}
}
ps = ps.previousElementSibling;
}
cur = cur.parentElement;
}
const containerCandidates = ['.mark_item', '.mark_item_box', '.mark_table', '.fanyaMarking_left', '.mark_table .mark_item'];
for(const cand of containerCandidates){
const container = node.closest && node.closest(cand);
if(container){
for(const sel of headerSels){
const hd = container.querySelector(sel);
if(hd){
const t = normalizeText(hd.innerText||hd.textContent||'');
if(t && !/^[((]\s*\d+\s*分\s*[))]$/.test(t) && t.length < 200) return t;
}
}
}
}
if(root){
const allHeaders = Array.from(root.querySelectorAll(headerSels.join(',')));
let candidate = '';
for(const h of allHeaders){
if(h.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING){
const t = normalizeText(h.innerText||h.textContent||'');
if(t && !/^\d+[.、]/.test(t) && !/^[((]\s*\d+\s*分\s*[))]$/.test(t) && t.length < 200){
candidate = t;
}
}
}
if(candidate) return candidate;
}
try{
const allPossible = Array.from(document.querySelectorAll(headerSels.join(',')));
const nodeRect = node.getBoundingClientRect ? node.getBoundingClientRect() : null;
if(nodeRect && allPossible.length){
let best = null; let bestTop = -Infinity;
for(const h of allPossible){
const r = h.getBoundingClientRect ? h.getBoundingClientRect() : null;
if(!r) continue;
if(r.top < nodeRect.top && r.top > bestTop){
const t = normalizeText(h.innerText||h.textContent||'');
if(t && t.length < 300) { best = t; bestTop = r.top; }
}
}
if(best) return best;
}
}catch(e){}
}catch(e){}
return '';
}
/* ---------- 题目解析(含清理题干开头序号) ---------- */
function parseQuestionNode(node, root){
try{
const titleEl = node.querySelector('.mark_name, .Cy_TItle, .Qtitle, .question-title, .marking-title') || node;
let stemRaw = '';
if(titleEl === node) {
let clone = node.cloneNode(true);
clone.querySelectorAll('.mark_letter, .Cy_ulTop, .Zy_ulTop, .options, ul, ol, .Py_answer, .mark_fill, .mark_answer, .rightAnswerContent, .right_answer, .mark_key, .answer, .answer-content, .fill-blank, .stuAnswerContent').forEach(el => el.remove());
stemRaw = getCleanText(clone);
} else {
stemRaw = getCleanText(titleEl);
}
let stem = normalizeText(stemRaw);
stem = stem.replace(/(我的答案|My Answer)[::\s]*.*$/i, '');
stem = stem.replace(/^\d+[\.\、\s]*【.*?】\s*/, '');
stem = stem.replace(/^\d+[\.\、]\s*/, '');
// 移除开头处各种括号内的题型标注(支持多次连续的括号)
stem = stem.replace(/^(?:\s*[\(\(\【\[]\s*(?:单选题|单选|选择题|选择|多选题|多选|判断题|判断|填空题|填空|论述题|论述|简答题|简答|问答题|问答|主观题|主观|材料题|操作题)\s*[\)\)\】\]]\s*)+/i, '');
stem = stem.trim();
(function stripLeadingNums(){
let changed = true;
while(changed){
changed = false;
const m = stem.match(/^\s*(?:\(*\s*\d+\s*[\)\.\、\)]|\(\s*\d+\s*)|\d+[\.\、])\s*/);
if(m){ stem = stem.slice(m[0].length); changed = true; }
const m2 = stem.match(/^\s*\d+[\.\、]\s*/);
if(m2){ stem = stem.slice(m2[0].length); changed = true; }
}
stem = stem.replace(/^\s+/, '');
})();
const wholeTxt = (node.innerText||node.textContent||'').toLowerCase();
// 优先识别论述/简答/主观类题型
let type = '单选题';
if(/论述|简答|问答|主观|论述题|简答题|问答题|主观题/i.test(wholeTxt)) {
type = '简答题';
} else if(/多选|multiple|multi/i.test(wholeTxt)) {
type = '多选题';
} else if(/判断|是非|对错|true|false/i.test(wholeTxt)) {
type = '判断题';
} else if(/填空|空格|填充|填空题/i.test(wholeTxt)) {
type = '填空题';
} else {
const dt = node.getAttribute && (node.getAttribute('data-type') || node.getAttribute('qtype') || '');
if(dt && /multi|checkbox|多选/i.test(dt)) type = '多选题';
}
const isFill = /填空|简答|问答|填充|主观|主观题/i.test(type);
const options = [];
if(!isFill){
const containers = findOptionContainers(node);
if(containers.length > 0){
let chosen = containers.find(c => c.querySelector && c.querySelector('li'));
if(!chosen) chosen = containers[0];
if(chosen){
const lis = Array.from(chosen.querySelectorAll('li'));
const items = lis.length ? lis : Array.from(chosen.children);
items.forEach((li, idx)=>{
let label = (li.querySelector('.mark_letter_span')?.innerText || li.querySelector('i.fl')?.innerText || '').replace(/[^A-Za-z0-9]/g,'').trim();
let clone = li.cloneNode(true);
if(clone.querySelector('.mark_letter_span')) clone.querySelector('.mark_letter_span').remove();
if(clone.querySelector('i.fl')) clone.querySelector('i.fl').remove();
let text = normalizeText(clone.textContent || clone.innerText || '');
const m = text.match(/^[\(\[]?\s*([A-Da-d])[\.\、\)\]\s]+/);
if(!label && m){ label = m[1].toUpperCase(); text = text.replace(/^[\(\[]?\s*([A-Da-d])[\.\、\)\]\s]+/,'').trim(); }
if(!label) label = String.fromCharCode(65 + (idx % 26));
if(text.length > 0 && text !== '查看解析') options.push({ key: label.toUpperCase(), text: text });
});
}
}
}
let answerRaw = extractCorrectAnswerFromNode(node, type) || '';
let answer = normalizeAnswerString(answerRaw, type);
answer = stripLeadingLabels(answer);
let answerSplit = splitNumberedBlanks(answer);
let answerInline = renderAnswerInline(answerSplit);
const analysisEl = node.querySelector('.analysisDiv, .analysis, .py_analyse, .Py_addpy .pingyu, .explain, .analysisTxt');
let analysis = analysisEl ? getCleanText(analysisEl) : '';
analysis = stripLeadingLabels(analysis);
const sectionTitle = findNearestSectionTitle(node, root) || '';
return { type, stem, options, answer: answerSplit, answerInline, analysis, sectionTitle };
} catch(e){
console.error('parseQuestionNode error', e);
return null;
}
}
/* ---------- 构建试卷对象 ---------- */
function buildStructuredPaperFromDOM(){
const root = document.querySelector('.fanyaMarking') || document.querySelector('.mark_table') || document.body;
let explicitSectionTitle = '';
(function(){
try{
const candidateSelectors = '.type_tit, .Cy_TItle1, .typeTitle, .mark_section, h2, h3';
const pageTitle = getPageTitle();
let explicitEl = root.querySelector('.mark_table .type_tit, .mark_item .type_tit, ' + candidateSelectors);
if(explicitEl && normalizeText(explicitEl.innerText || explicitEl.textContent || '') === normalizeText(pageTitle)){
const rightCard = document.querySelector('.topicNumber_checkbox, .topicNumber .topicNumber_checkbox, #topicNumberScroll .topicNumber_checkbox');
if(rightCard && normalizeText(rightCard.innerText || rightCard.textContent || '')){
explicitEl = rightCard;
} else {
const alt = root.querySelector('.mark_item .type_tit, .mark_table h2, .mark_item h2, ' + candidateSelectors);
if(alt) explicitEl = alt;
}
}
if(!explicitEl){
explicitEl = root.querySelector(candidateSelectors);
if(!explicitEl){
const rn = document.querySelector('.topicNumber_checkbox, .topicNumber .topicNumber_checkbox, #topicNumberScroll .topicNumber_checkbox');
if(rn) explicitEl = rn;
}
}
if(explicitEl){
const t = normalizeText(explicitEl.innerText || explicitEl.textContent || '');
const cleaned = sanitizeSectionTitleRaw(t);
if(cleaned && cleaned !== sanitizeSectionTitleRaw(pageTitle)) explicitSectionTitle = cleaned;
}
}catch(e){}
})();
const selectors = ['.questionLi','.TiMu','.question-item','.mark_question','.exam-question','.paper-question','.Ques','.questionBox','.marBom60','.singleQuesId'];
const nodeSet = new Set();
selectors.forEach(sel=> document.querySelectorAll(sel).forEach(n=> { if(n && root.contains(n)) nodeSet.add(n); }));
if(nodeSet.size === 0){
Array.from(root.querySelectorAll('*')).forEach(n=>{
try{
if(!(n instanceof HTMLElement)) return;
const len = (n.innerText||'').length;
const hasStem = !!n.querySelector('.mark_name, .Cy_TItle, .qtContent, h3');
if(hasStem || (len>12 && n.querySelector('ul') && n.innerText.includes('A'))) nodeSet.add(n);
}catch(e){}
});
}
const arr = Array.from(nodeSet).sort((a,b)=> (a.compareDocumentPosition(b) & 2) ? 1 : -1);
const parsedQuestions = [];
const seenFp = new Set();
for(const node of arr){
const q = parseQuestionNode(node, root);
if(!q) continue;
const fp = ( (q.stem||'') + '||' + ((q.options||[]).map(o=>o.text).join('||')) + '||' + (q.answerInline||'') ).slice(0,2000);
if(seenFp.has(fp)) continue;
seenFp.add(fp);
parsedQuestions.push(q);
}
const sectionsMap = new Map();
const pageTitle = getPageTitle();
for(const q of parsedQuestions){
const key = q.sectionTitle && q.sectionTitle.trim() ? q.sectionTitle.trim() : '未分组';
if(!sectionsMap.has(key)) sectionsMap.set(key, []);
sectionsMap.get(key).push(q);
}
if(sectionsMap.size === 1 && sectionsMap.has('未分组')){
if(explicitSectionTitle){
sectionsMap.set(explicitSectionTitle, sectionsMap.get('未分组'));
} else {
const localHeader = root.querySelector('.type_tit, .Cy_TItle1, .typeTitle, .markTitle, h2, h3');
const headerText = localHeader ? sanitizeSectionTitleRaw(normalizeText(localHeader.innerText || localHeader.textContent || '')) : '';
if(headerText && headerText.length > 0 && headerText !== sanitizeSectionTitleRaw(pageTitle)){
sectionsMap.set(headerText, sectionsMap.get('未分组'));
} else {
sectionsMap.set(pageTitle, sectionsMap.get('未分组'));
}
}
sectionsMap.delete('未分组');
}
const entries = Array.from(sectionsMap.entries());
if(entries.length > 1 && entries[0][0] === '未分组'){
const ungroupList = entries[0][1] || [];
const nextList = entries[1][1] || [];
entries[1][1] = ungroupList.concat(nextList);
entries.splice(0, 1);
}
const sections = [];
for(const [title, qlist] of entries){
sections.push({ title, qlist });
}
let counter = 1;
const paper = { title: pageTitle, sections: [] };
for(const s of sections){
const sec = { title: s.title, questions: [] };
for(const q of s.qlist){ q.no = counter++; sec.questions.push(q); }
paper.sections.push(sec);
}
paper.explicitSectionTitle = explicitSectionTitle || '';
return paper;
}
/* ---------- DOCX / 文本输出 ---------- */
function buildContentTypes(){ return '