// ==UserScript== // @name 组卷网试卷提取 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 从组卷网提取试卷内容并导出为Word,支持图片处理 // @icon https://toolb.cn/favicon/zujuan.xkw.com // @author pansoul // @license MIT // @match https://zujuan.xkw.com/zujuan/* // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_download // @grant GM_xmlhttpRequest // @connect staticzujuan.xkw.com // @connect cdn*.xkw.com // @downloadURL https://update.greasyfork.icu/scripts/542406/%E7%BB%84%E5%8D%B7%E7%BD%91%E8%AF%95%E5%8D%B7%E6%8F%90%E5%8F%96.user.js // @updateURL https://update.greasyfork.icu/scripts/542406/%E7%BB%84%E5%8D%B7%E7%BD%91%E8%AF%95%E5%8D%B7%E6%8F%90%E5%8F%96.meta.js // ==/UserScript== (function() { 'use strict'; GM_addStyle(` .xkw-exporter { position: fixed; top: 100px; right: 20px; z-index: 9999; background: #4285f4; color: #fff; padding: 10px 18px 10px 42px; /* 预留左侧 icon 空间 */ border-radius: 6px; box-shadow: 0 4px 10px rgba(0,0,0,0.25); font-family: Arial, sans-serif; font-size: 14px; cursor: pointer; transition: background 0.25s; } .xkw-exporter::before { content: ''; position: absolute; left: 12px; top: 50%; width: 18px; height: 18px; transform: translateY(-50%); background: url('https://toolb.cn/favicon/zujuan.xkw.com') no-repeat center/contain; } .xkw-exporter:hover { background: #3367d6; } .xkw-exporter-panel { position: fixed; top: 150px; right: 20px; z-index: 9998; background: #ffffff; border-radius: 8px; box-shadow: 0 8px 20px rgba(0,0,0,0.15); padding: 20px 22px 18px; width: 330px; display: none; font-family: 'Microsoft YaHei', Arial, sans-serif; } .xkw-exporter-panel h3 { margin: 0 0 12px 0; font-size: 18px; border-bottom: 1px solid #e0e0e0; padding-bottom: 10px; } .xkw-exporter-panel button { display: block; width: 100%; margin: 10px 0; padding: 10px 0; background: #4285f4; color: #fff; border: none; border-radius: 4px; font-size: 15px; cursor: pointer; transition: background 0.25s; } .xkw-exporter-panel button:hover { background: #3367d6; } .xkw-exporter-panel .close { position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 18px; } .xkw-exporter-panel .options { margin: 10px 0; } .xkw-exporter-panel .options label { display: block; margin: 5px 0; } .xkw-exporter-progress { position: fixed; top: 200px; right: 20px; z-index: 9997; background: rgba(0,0,0,0.7); color: white; padding: 10px; border-radius: 4px; display: none; } `); window.addEventListener('load', function() { setTimeout(initExporter, 1000); }); function initExporter() { const exporterBtn = document.createElement('div'); exporterBtn.className = 'xkw-exporter'; exporterBtn.textContent = '导出试卷'; exporterBtn.addEventListener('click', toggleExporterPanel); document.body.appendChild(exporterBtn); const exporterPanel = document.createElement('div'); exporterPanel.className = 'xkw-exporter-panel'; exporterPanel.innerHTML = ` ×

试卷导出工具

`; document.body.appendChild(exporterPanel); const progressDiv = document.createElement('div'); progressDiv.className = 'xkw-exporter-progress'; progressDiv.innerHTML = '处理中...'; document.body.appendChild(progressDiv); exporterPanel.querySelector('.close').addEventListener('click', function() { exporterPanel.style.display = 'none'; }); document.getElementById('export-word').addEventListener('click', function() { exportPaper('word'); }); } function toggleExporterPanel() { const panel = document.querySelector('.xkw-exporter-panel'); panel.style.display = panel.style.display === 'none' || panel.style.display === '' ? 'block' : 'none'; } function showProgress(message) { const progress = document.querySelector('.xkw-exporter-progress'); progress.textContent = message; progress.style.display = 'block'; } function hideProgress() { const progress = document.querySelector('.xkw-exporter-progress'); progress.style.display = 'none'; } function extractPaperContent() { showProgress('正在提取试卷内容...'); const downloadImages = document.getElementById('download-images').checked; const formatEquations = document.getElementById('format-equations').checked; const paperTitle = document.querySelector('.paper-title .main-title')?.textContent.trim() || '未命名试卷'; const paperContent = { title: paperTitle, sections: [], options: { includeAnswers: false, includeExplanations: false, downloadImages, formatEquations } }; let questionGlobalIndex = 1; const questionTypes = document.querySelectorAll('.ques-type'); questionTypes.forEach((typeSection, typeIndex) => { const currentSection = { type: '', index: '', questions: [] }; const questions = typeSection.querySelectorAll('.ques-item'); questions.forEach((question, qIndex) => { let qContent = question.querySelector('.exam-item__cnt')?.innerHTML.trim() || ''; if (!qContent) { return; } const qNumber = `${questionGlobalIndex}.`; questionGlobalIndex++; { const temp = document.createElement('div'); temp.innerHTML = qContent; const idx = temp.querySelector('.quesindex'); if (idx) idx.parentNode.removeChild(idx); qContent = temp.innerHTML; } qContent = qContent.replace(/^\s*(?:<[^>]+>\s*)*\d+\s*[..、]?\s*/, ''); const options = []; const optionsTable = question.querySelector('table[name="optionsTable"]'); if (optionsTable) { const optionRows = optionsTable.querySelectorAll('tr'); optionRows.forEach(row => { const cells = row.querySelectorAll('td'); cells.forEach(cell => { options.push(cell.innerHTML); }); }); const tempDiv = document.createElement('div'); tempDiv.innerHTML = qContent; const contentOptionsTable = tempDiv.querySelector('table[name="optionsTable"]'); if (contentOptionsTable) { contentOptionsTable.parentNode.removeChild(contentOptionsTable); } qContent = tempDiv.innerHTML; } const images = []; if (downloadImages) { const imgElements = question.querySelectorAll('img'); imgElements.forEach(img => { if (img.src && !img.src.startsWith('data:')) { images.push({ src: img.src, alt: img.alt || '', width: img.width || 0, height: img.height || 0 }); } }); } // 不再获取答案和解析 const answer = ''; const explanation = ''; currentSection.questions.push({ number: qNumber, content: qContent, options: options, answer: answer, explanation: explanation, images: images }); }); if (currentSection.questions.length > 0) { const typeName = typeSection.querySelector('.questypename')?.textContent.trim() || `题型${typeIndex + 1}`; const rawTypeIndex = typeSection.querySelector('.questypeindex b')?.textContent.trim() || `${typeIndex + 1}、`; const typeIndex2 = rawTypeIndex.replace(/^\d+[、\.]\s*\d+[、\.]/, match => { const firstNum = match.match(/^\d+/)[0]; return `${firstNum}、`; }); currentSection.type = typeName; currentSection.index = typeIndex2; paperContent.sections.push(currentSection); } }); hideProgress(); return paperContent; } function paperToHTML(paperContent) { let html = ` ${paperContent.title}
${paperContent.title}
`; const nonEmptySections = paperContent.sections.filter(section => section.questions && section.questions.length > 0); nonEmptySections.forEach((section, sectionIndex) => { html += `
${section.index} ${section.type}
`; section.questions.forEach((question, qIndex) => { html += `
`; html += `
${question.number} ${cleanHTML(question.content)}
`; if (question.options.length > 0) { html += `
`; const optionRows = []; for (let i = 0; i < question.options.length; i += 2) { const row = [question.options[i]]; if (i + 1 < question.options.length) { row.push(question.options[i + 1]); } optionRows.push(row); } html += ``; optionRows.forEach(row => { html += ``; row.forEach(option => { html += ``; }); if (row.length === 1) { html += ``; } html += ``; }); html += `
${cleanHTML(option)}
`; html += `
`; } // 不再显示答案和解析 html += `
`; if ((qIndex + 1) % 10 === 0 && qIndex < section.questions.length - 1) { html += `
`; } }); if (sectionIndex < nonEmptySections.length - 1) { html += `
`; } }); html += ` `; return html; } function paperToText(paperContent) { let text = `${paperContent.title}\n\n`; const nonEmptySections = paperContent.sections.filter(section => section.questions && section.questions.length > 0); nonEmptySections.forEach(section => { let sectionTitle = section.index + ' ' + section.type; sectionTitle = sectionTitle.replace(/^(\d+)[、\.]\s*\d+[、\.]/, '$1、'); text += `${sectionTitle}\n\n`; section.questions.forEach(question => { let questionNumber = question.number; questionNumber = questionNumber.replace(/^(\d+)[..、]\s*\d+[..、]/, '$1.'); text += `${questionNumber} ${stripHTML(question.content)}\n`; if (question.options.length > 0) { question.options.forEach((option, index) => { text += ` ${stripHTML(option)}\n`; }); } // 不再显示答案和解析 text += `\n`; }); text += `\n`; }); return text; } async function processImages(paperContent) { if (!paperContent.options.downloadImages) return paperContent; showProgress('正在处理图片...'); const imagePromises = []; const imageMap = new Map(); paperContent.sections.forEach(section => { section.questions.forEach(question => { question.images.forEach(img => { if (!imageMap.has(img.src)) { imageMap.set(img.src, null); const promise = fetchImageAsBase64(img.src) .then(base64 => { imageMap.set(img.src, base64); }) .catch(err => { console.error(`Failed to fetch image: ${img.src}`, err); }); imagePromises.push(promise); } }); }); }); await Promise.all(imagePromises); paperContent.sections.forEach(section => { section.questions.forEach(question => { const imgRegex = /]+src="([^"]+)"[^>]*>/g; question.content = question.content.replace(imgRegex, (match, src) => { const base64 = imageMap.get(src); if (base64) { return match.replace(src, base64); } return match; }); question.options = question.options.map(option => { return option.replace(imgRegex, (match, src) => { const base64 = imageMap.get(src); if (base64) { return match.replace(src, base64); } return match; }); }); // 不再处理答案和解析中的图片 }); }); hideProgress(); return paperContent; } function fetchImageAsBase64(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'arraybuffer', onload: function(response) { try { let binary = ''; const bytes = new Uint8Array(response.response); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } let mimeType = 'image/jpeg'; if (url.endsWith('.png')) { mimeType = 'image/png'; } else if (url.endsWith('.gif')) { mimeType = 'image/gif'; } else if (url.endsWith('.svg')) { mimeType = 'image/svg+xml'; } const base64 = 'data:' + mimeType + ';base64,' + btoa(binary); resolve(base64); } catch (e) { reject(e); } }, onerror: function(error) { reject(error); } }); }); } async function exportPaper(format) { let paperContent = extractPaperContent(); if (format === 'word' && paperContent.options.downloadImages) { paperContent = await processImages(paperContent); } const fileName = `${paperContent.title}_${new Date().toISOString().slice(0, 10)}`; showProgress(`正在导出为${format.toUpperCase()}格式...`); switch (format) { case 'word': const html = paperToHTML(paperContent); const blob = new Blob([html], {type: 'text/html'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${fileName}.doc`; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 100); break; case 'txt': const text = paperToText(paperContent); const txtBlob = new Blob([text], {type: 'text/plain'}); const txtUrl = URL.createObjectURL(txtBlob); const txtLink = document.createElement('a'); txtLink.href = txtUrl; txtLink.download = `${fileName}.txt`; document.body.appendChild(txtLink); txtLink.click(); document.body.removeChild(txtLink); setTimeout(() => URL.revokeObjectURL(txtUrl), 100); break; case 'json': const json = JSON.stringify(paperContent, null, 2); const jsonBlob = new Blob([json], {type: 'application/json'}); const jsonUrl = URL.createObjectURL(jsonBlob); const jsonLink = document.createElement('a'); jsonLink.href = jsonUrl; jsonLink.download = `${fileName}.json`; document.body.appendChild(jsonLink); jsonLink.click(); document.body.removeChild(jsonLink); setTimeout(() => URL.revokeObjectURL(jsonUrl), 100); break; } hideProgress(); alert(`已成功导出为${format.toUpperCase()}格式!`); } function cleanHTML(html) { if (!html) return ''; return html.replace(/)<[^<]*)*<\/script>/gi, ''); } function stripHTML(html) { if (!html) return ''; const temp = document.createElement('div'); temp.innerHTML = html; return temp.textContent || temp.innerText || ''; } function formatEquations(html) { if (!html) return ''; return html; } })();