# -*- coding: utf-8 -*- """Inject a collapsible 题目讲解 panel (题目 / 孩子代码 / 答案 / 解析) into each student's OJ section on the two CSP04 class pages (0830 & 1400). Additive only. Usage: python inject_prob_details.py ... """ import sys, os, json, html, re, copy from pathlib import Path ROOT = Path(__file__).resolve().parent.parent.parent.parent TMP = ROOT / '.claude' / 'memory' / 'temp' CODE = json.load(open(TMP / 'student_code_CSP04_L01.json', encoding='utf-8')) META = json.load(open(TMP / 'problems_meta.json', encoding='utf-8')) # display prefix -> numeric pid (as used in fetched codes) PIDNUM = {v.split(' ')[0]: k for k, v in CODE['pid_map'].items()} PROB_ORDER = ['S0401P1', 'S0401P2', 'S0401P3', 'S0401P4', 'S0401P5'] STATUS_TXT = {'AC': '✅ 已通过 AC', 'WA': '❌ 答案错误 WA', 'CE': '❌ 编译错误 CE', 'RE': '❌ 运行错误 RE', 'TLE': '❌ 超时 TLE', 'PE': '⚠️ 格式错误 PE', 'MLE': '❌ 超内存 MLE'} CSS_BLOCK = ''' /* ===== 题目讲解(题目/孩子代码/答案/解析) ===== */ .oj-explains{margin-top:16px;} .oj-explains-title{font-size:0.9em;font-weight:600;color:var(--ink);margin-bottom:8px;display:flex;align-items:center;gap:6px;} .prob-detail{border:1px solid var(--border);border-radius:10px;margin-bottom:8px;overflow:hidden;background:var(--cream);} .prob-head{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;font-size:0.85em;} .prob-head:hover{background:var(--paper-warm);} .prob-title{font-weight:600;color:var(--ink);} .prob-sub{font-size:0.74em;color:var(--ink-soft);flex:1;text-align:right;} .prob-caret{font-size:0.85em;color:var(--ink-soft);transition:transform 0.2s ease;} .prob-detail.open .prob-caret{transform:rotate(90deg);} .prob-body{display:none;padding:12px 14px;border-top:1px solid var(--border);} .prob-detail.open .prob-body{display:block;} .prob-sec{margin-bottom:12px;} .prob-sec:last-child{margin-bottom:0;} .prob-sec-h{font-size:0.78em;font-weight:700;color:var(--ink-soft);margin-bottom:5px;} .prob-sec-c{font-size:0.83em;line-height:1.75;color:var(--ink-soft);background:var(--paper);border-radius:8px;padding:8px 11px;} .prob-code{background:#1b2733;color:#e6eef7;border-radius:8px;padding:10px 12px;font-size:0.76em;line-height:1.6;overflow:auto;white-space:pre;max-height:240px;font-family:Consolas,'Courier New',monospace;} /* 未提交 -> 课后作业(不展开、不做分析) */ .prob-detail.homework{background:var(--paper-warm);border-style:dashed;} .prob-detail.homework .prob-head{cursor:default;} .prob-detail.homework .prob-sub{color:#a8842c;font-weight:600;} ''' JS_BLOCK = '\n function toggleProb(el){el.classList.toggle("open");var c=el.querySelector(".prob-caret");if(c)c.textContent=el.classList.contains("open")?"▼":"▶";}\n' def esc(t): return html.escape(str(t)) def build_panels(student): panels = ['
', '
📖 题目讲解(已提交的题 · 题目 / 孩子代码 / 参考答案 / 解析)
'] codes = CODE['students'].get(student, {}) for pid in PROB_ORDER: meta = META.get(pid) if not meta: continue entry = codes.get(PIDNUM.get(pid, ''), {}) code = entry.get('code', '') status = entry.get('status', '') attempts = entry.get('attempts', 0) if code: sub = '%s · %d次提交' % (STATUS_TXT.get(status, status), attempts) panels.append( '
' '
' '%s' '%s' '▶' '
' '
' '
📋 题目
%s
' '
👦 孩子的代码
%s
' '
✅ 参考答案
%s
' '
💡 解析
%s
' '
' % (esc(meta['key']), sub, esc(meta['statement']), esc(code), esc(meta['ref_code']), esc(meta['key_points']))) else: # 未提交 -> 只标为课后作业,不做分析 panels.append( '
' '
' '%s' '📚 未提交 · 课后作业' '
' % esc(meta['key'])) panels.append('
') return '\n'.join(panels) def find_oj_section(html, start): """Return end index (after the matching ) for the oj-section starting at `start`.""" n = len(html) depth = 1 i = start + len('
0 and i < n: o = html.find('', i) if c == -1: return -1 if o != -1 and o < c: depth += 1 i = o + 4 else: depth -= 1 i = c + 6 return i def inject_style(html, css): idx = html.rfind('') if idx == -1: return html return html[:idx] + css + html[idx:] def inject_script(html, js): idx = html.rfind('') if idx == -1: return html return html[:idx] + js + html[idx:] def process(html): # inject CSS/JS once if '.prob-detail' not in html: html = inject_style(html, CSS_BLOCK) if 'function toggleProb' not in html: html = inject_script(html, JS_BLOCK) # inject panels per student oj-section (insert RIGHT-TO-LEFT so earlier # positions stay valid after the html string changes length) pat = re.compile(r'
') matches = [(m.group(1), m.start()) for m in pat.finditer(html)] for name, start in reversed(matches): end = find_oj_section(html, start) if end == -1: continue closing = end - 6 # position of the
that closes oj-section panels = build_panels(name) html = html[:closing] + panels + '\n' + html[closing:] return html def main(files): for p in files: orig = open(p, encoding='utf-8').read() out = process(orig) changed = out != orig open(p, 'w', encoding='utf-8').write(out) print(('CHANGED ' if changed else 'same ') + os.path.basename(p)) if __name__ == '__main__': main(sys.argv[1:])