// ==UserScript== // @name CNKI 论文下载(正文/摘要作者加逗号,关键词加分号) // @namespace https://www.0x99.top // @version 1.1.1 // @description 教育网兼容:正文中作者用中文逗号无空格分隔,关键词用中文分号分隔;摘要独立格式化 // @author zmrbak // @match https://kns.cnki.net/reader/xml* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // ---------- 工具函数 ---------- function getAuthors() { const container = document.getElementById('1000001'); if (!container) return []; const items = container.querySelectorAll('li'); const authors = []; for (let li of items) { const link = li.querySelector('a'); let name = link ? link.innerText.trim() : li.innerText.trim(); if (name && !name.includes('大学') && !name.includes('学院')) { authors.push(name); } } return [...new Set(authors)]; } function getKeywords() { const container = document.getElementById('1000007'); if (!container) return []; const links = container.querySelectorAll('a'); const keywords = []; for (let link of links) { let kw = link.innerText.trim(); if (kw) keywords.push(kw); } return [...new Set(keywords)]; } // 在正文中为作者添加中文逗号(无空格)分隔 function addCommaToAuthors(text, authors) { if (authors.length < 2) return text; let result = text; for (let i = 0; i < authors.length - 1; i++) { const a1 = authors[i]; const a2 = authors[i+1]; const regex = new RegExp(a1 + a2, 'g'); result = result.replace(regex, a1 + ',' + a2); // 中文逗号,无空格 } return result; } // 在正文中为关键词添加中文分号(无空格)分隔 function addSemicolonToKeywords(text, keywords) { if (keywords.length < 2) return text; let result = text; for (let i = 0; i < keywords.length - 1; i++) { const kw1 = keywords[i]; const kw2 = keywords[i+1]; const regex = new RegExp(kw1 + kw2, 'g'); result = result.replace(regex, kw1 + ';' + kw2); // 中文分号,无空格 } return result; } // 获取正文(并自动添加作者逗号和关键词分号) function getMainContent() { const CONTENT_XPATH = '/html/body/div[3]/div/div[2]/div[2]/div/div/div[3]/div'; let node = document.evaluate(CONTENT_XPATH, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; let content = ''; if (node && node.innerText && node.innerText.trim().length > 100) { content = node.innerText.trim(); } else { const container = document.querySelector('#paperRead'); content = container ? (container.innerText || '').trim() : ''; } if (!content) return ''; const authors = getAuthors(); const keywords = getKeywords(); let processed = content; if (authors.length >= 2) { processed = addCommaToAuthors(processed, authors); } if (keywords.length >= 2) { processed = addSemicolonToKeywords(processed, keywords); } return processed; } // ---------- 摘要格式化(独立提取) ---------- function getTitle() { const TITLE_XPATH = '/html/body/div[3]/div/div[2]/div[2]/div/div/div[3]/div/div[2]/div[1]/div[1]/h1'; let node = document.evaluate(TITLE_XPATH, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (node) return node.innerText.trim(); const h1 = document.querySelector('h1.Chapter'); return h1 ? h1.innerText.trim() : '未知标题'; } function getSourceAndDateStr() { const META_DIV_XPATH = '/html/body/div[3]/div/section/div/div[1]/div'; try { const metaDiv = document.evaluate(META_DIV_XPATH, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (!metaDiv) return ''; let sourceType = ''; const typeSpan = metaDiv.querySelector('span'); if (typeSpan) { const m = typeSpan.innerText.match(/[((](.+?)[))]/); if (m) sourceType = m[1]; } let sourceName = ''; const nameLink = metaDiv.querySelector('a.xmlhref'); if (nameLink) { sourceName = nameLink.innerText.trim(); } else if (sourceType) { const divText = metaDiv.innerText; const h1Title = metaDiv.querySelector('h1.xmltitle'); if (h1Title) { let rest = divText.replace(h1Title.innerText, ''); const regex = new RegExp(`[((]${sourceType}[))]\\s*([^\\d]+)`); const m = rest.match(regex); if (m && m[1]) sourceName = m[1].trim(); } } const emList = metaDiv.querySelectorAll('em.xmlem'); let year = emList[0]?.innerText.trim() || ''; let issue = emList[1]?.innerText.trim() || ''; let result = sourceName || sourceType; if (!result) return ''; if (year) result += `,${year}${issue}`; return result; } catch(e) { return ''; } } function getAbstractText() { const paras = document.querySelectorAll('.para'); for (let para of paras) { const text = para.innerText || ''; if (text.includes('摘要:')) { return text.replace(/^摘要[::]\s*/, '').trim().replace(/\s+/g, ' '); } } const abs = document.querySelector('.abstract, .abstract-text, [class*="abstract"]'); return abs ? abs.innerText.trim() : '无摘要内容'; } function getFormattedAbstract() { const title = getTitle(); const authors = getAuthors(); // 摘要中的作者使用英文逗号+空格(更符合学术习惯,不强制去掉空格) const authorStr = authors.length ? authors.join(', ') : '未知作者'; const sourceDate = getSourceAndDateStr(); const abstractText = getAbstractText(); const keywords = getKeywords(); const keywordStr = keywords.length ? keywords.join(';') : ''; let firstLine = `${authorStr}. ${title}`; if (sourceDate) firstLine += `,${sourceDate}`; let result = `${firstLine}\n摘要: ${abstractText}`; if (keywordStr) result += `\n关键词: ${keywordStr}`; return result; } // ---------- 参考文献 ---------- function getReferenceText() { const REF_XPATH = '/html/body/div[3]/div/div[2]/div[3]/section/div[2]/div[1]/ul'; let ul = document.evaluate(REF_XPATH, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (!ul) ul = document.querySelector('.references-box ul'); if (!ul) return ''; const refs = []; for (let li of ul.querySelectorAll('li')) { let raw = li.innerText || ''; if (raw.trim()) refs.push(parseReferenceItem(raw)); } return refs.join('\n'); } function parseReferenceItem(raw) { let text = raw.replace(/^\[\d+\]\s*/, ''); text = text.replace(/\s*\([A-Za-z\s,.;:()]+\)\s*$/, ''); const regex = /^(.+?)\.\s+(.+?)\[([A-Z]+)\]([^0-9]*)(\d{4})/; const match = text.match(regex); if (match) { let author = match[1].trim(); let title = match[2].trim(); let sourceType = match[3]; let sourceDetail = match[4].trim(); let year = match[5]; return `${author}. ${title}. [${sourceType}]. ${sourceDetail} ${year}.`.replace(/\s+/g, ' ').trim(); } return text.trim(); } // ---------- 通用函数 ---------- function getBaseTitle() { let title = getTitle(); if (!title || title === '未知标题') { const d = new Date(); title = `${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}_${d.getHours()}-${d.getMinutes()}-${d.getSeconds()}`; } return title.replace(/[\\/:*?"<>|]/g, '').slice(0, 100); } function saveAsTextFile(content, filename) { if (!content || content.trim() === '') { alert('未找到目标文本'); return false; } const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); return true; } function showNotice(msg, isErr) { const div = document.createElement('div'); div.textContent = msg; div.style.cssText = `position:fixed; bottom:20px; right:20px; background:${isErr?'#f44336':'#4caf50'}; color:#fff; padding:6px 12px; border-radius:4px; font-size:12px; z-index:10001; opacity:0.9;`; document.body.appendChild(div); setTimeout(() => div.remove(), 3000); } function createButton(text, onClick) { const btn = document.createElement('button'); btn.textContent = text; btn.style.cssText = `padding:6px 16px; font-size:14px; font-weight:bold; color:#fff; background:#2c7da0; border:none; border-radius:30px; cursor:pointer; transition:0.2s; box-shadow:0 1px 3px rgba(0,0,0,0.2); white-space:nowrap;`; btn.onmouseover = () => btn.style.backgroundColor = '#1f5e7a'; btn.onmouseout = () => btn.style.backgroundColor = '#2c7da0'; btn.addEventListener('click', onClick); return btn; } function addButtons() { const old = document.getElementById('cnki-buttons-container'); if (old) old.remove(); ['cnki-save-content-btn', 'cnki-save-ref-btn', 'cnki-save-abstract-btn'].forEach(id => { const b = document.getElementById(id); if (b) b.remove(); }); const container = document.createElement('div'); container.id = 'cnki-buttons-container'; container.style.cssText = `position:fixed; top:12px; left:50%; transform:translateX(-50%); display:flex; gap:12px; z-index:10000; background:transparent; padding:4px 0;`; const contentBtn = createButton('📄 保存正文', () => { const content = getMainContent(); const filename = `${getBaseTitle()}.txt`; const ok = saveAsTextFile(content, filename); showNotice(ok ? `✅ 正文已保存: ${filename}` : '⚠️ 未找到正文'); }); contentBtn.id = 'cnki-save-content-btn'; const refBtn = createButton('📚 保存参考文献', () => { const refs = getReferenceText(); const filename = `${getBaseTitle()}_参考文献.txt`; const ok = saveAsTextFile(refs, filename); showNotice(ok ? `✅ 参考文献已保存: ${filename}` : '⚠️ 未找到参考文献'); }); refBtn.id = 'cnki-save-ref-btn'; const abstractBtn = createButton('📝 保存摘要', () => { const abs = getFormattedAbstract(); const filename = `${getBaseTitle()}_摘要.txt`; const ok = saveAsTextFile(abs, filename); showNotice(ok ? `✅ 摘要已保存: ${filename}` : '⚠️ 未找到摘要'); }); abstractBtn.id = 'cnki-save-abstract-btn'; container.appendChild(contentBtn); container.appendChild(refBtn); container.appendChild(abstractBtn); document.body.appendChild(container); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', addButtons); } else { addButtons(); } })();