mcpField
A
academic-citation-manager

academic-citation-manager

github.com/wentorai生产力30757 次安装19 周前更新
代码仓库

关于 academic-citation-manager

面向学术写作的引用管理技能,以 BibTeX 为规范数据源,支持 DOI 等来源的元数据获取、格式转换、去重和校验。适用于 LaTeX、Markdown 和自动化文档流程。

主要功能

  1. 01从 DOI 等来源获取引文元数据
  2. 02转换多种引用格式
  3. 03检测并合并重复条目
  4. 04通过 CrossRef 和 OpenAlex 校验
  5. 05生成格式化参考文献

适用场景

  1. 01整理不同来源的参考文献
  2. 02按期刊要求切换引用格式
  3. 03检查 DOI、年份和重复条目

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wentorai/research-plugins --skill academic-citation-manager

安装后在 Claude Code 中输入/academic-citation-manager确认已加载。

详细介绍

学术引文管理器

管理多种格式的学术引文(BibTeX、APA 第 7 版、MLA 第 9 版、Chicago、Vancouver、IEEE),支持通过 DOI 自动检索、格式转换、去重和验证。此技能涵盖引文的完整生命周期,从初始收集到最终的稿件格式设置。

概述

引文管理是学术写作中长期存在的一大难题。研究人员从多个来源(数据库、PDF、同事、网页)收集参考文献,以不同格式存储,并且必须按各目标期刊要求的特定样式输出。引文错误——作者姓名拼写错误、年份有误、DOI 无效、格式不一致——是稿件被编辑直接拒稿和受到审稿人批评的常见原因。

此技能提供了一套全面的引文管理工作流,功能超越了图形界面参考文献管理器。它可以在数秒内通过 DOI 检索完整元数据、转换任意引文格式、检测并合并重复条目、根据 CrossRef 和 OpenAlex 数据库验证条目,并生成符合任何主流引文样式的格式规范的参考文献列表。

此方案基于文本且可脚本化,非常适合与 LaTeX 工作流、Markdown 写作流程和自动化文档生成集成。所有引文数据均以标准 BibTeX 格式作为规范来源存储,并可按需转换为其他格式,以满足特定稿件的要求。

引文检索

通过 DOI
import requests

def get_bibtex_from_doi(doi):
    """Retrieve BibTeX entry from a DOI via CrossRef."""
    url = f"https://doi.org/{doi}"
    headers = {"Accept": "application/x-bibtex"}
    response = requests.get(url, headers=headers, allow_redirects=True)
    if response.status_code == 200:
        return response.text
    return None

# Example
bibtex = get_bibtex_from_doi("10.1038/s41586-021-03819-2")
print(bibtex)
# @article{Jumper_2021,
#   title={Highly accurate protein structure prediction with AlphaFold},
#   author={Jumper, John and Evans, Richard and ...},
#   journal={Nature},
#   volume={596},
#   pages={583--589},
#   year={2021},
#   publisher={Springer}
# }
通过 OpenAlex
def get_citation_from_openalex(work_id):
    """Retrieve citation data from OpenAlex API."""
    url = f"https://api.openalex.org/works/{work_id}"
    headers = {"User-Agent": "ResearchPlugins/1.0 (https://wentor.ai)"}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        data = response.json()
        return format_as_bibtex(data)
    return None

def format_as_bibtex(oa_data):
    """Convert OpenAlex data to BibTeX."""
    authorships = oa_data.get("authorships", [])
    author_str = " and ".join(a["author"]["display_name"] for a in authorships)
    first_author = authorships[0]["author"]["display_name"].split()[-1] if authorships else "Unknown"
    year = str(oa_data.get("publication_year", ""))
    key = f"{first_author}_{year}"

    venue = oa_data.get("primary_location", {}) or {}
    journal = (venue.get("source") or {}).get("display_name", "")

    return f"""@article{{{key},
  title={{{oa_data.get('title', '')}}},
  author={{{author_str}}},
  year={{{year}}},
  journal={{{journal}}},
  doi={{{oa_data.get('doi', '')}}}
}}"""
通过 arXiv ID
def get_bibtex_from_arxiv(arxiv_id):
    """Retrieve BibTeX from arXiv."""
    import feedparser
    url = f"http://export.arxiv.org/api/query?id_list={arxiv_id}"
    feed = feedparser.parse(url)
    if feed.entries:
        entry = feed.entries[0]
        authors = " and ".join(a["name"] for a in entry.authors)
        first_author = entry.authors[0]["name"].split()[-1]
        year = entry.published[:4]
        return f"""@article{{{first_author}_{year},
  title={{{entry.title.replace(chr(10), ' ')}}},
  author={{{authors}}},
  year={{{year}}},
  journal={{arXiv preprint arXiv:{arxiv_id}}},
  url={{https://arxiv.org/abs/{arxiv_id}}}
}}"""
    return None

格式转换

BibTeX 转 APA 第 7 版
def bibtex_to_apa7(entry):
    """Convert a parsed BibTeX entry to APA 7th edition format."""
    authors = format_apa_authors(entry["author"])
    year = entry.get("year", "n.d.")
    title = entry["title"]
    journal = entry.get("journal", "")
    volume = entry.get("volume", "")
    issue = entry.get("number", "")
    pages = entry.get("pages", "")
    doi = entry.get("doi", "")

    # Article format
    citation = f"{authors} ({year}). {title}. "
    if journal:
        citation += f"*{journal}*"
        if volume:
            citation += f", *{volume}*"
        if issue:
            citation += f"({issue})"
        if pages:
            citation += f", {pages}"
        citation += "."
    if doi:
        citation += f" https://doi.org/{doi}"

    return citation

def format_apa_authors(author_string):
    """Format author names in APA style: Last, F. M."""
    authors = [a.strip() for a in author_string.split(" and ")]
    formatted = []
    for author in authors:
        parts = author.split(", ") if ", " in author else author.rsplit(" ", 1)[::-1]
        if len(parts) >= 2:
            last = parts[0]
            firsts = parts[1].split()
            initials = " ".join(f"{f[0]}." for f in firsts)
            formatted.append(f"{last}, {initials}")
        else:
            formatted.append(parts[0])

    if len(formatted) == 1:
        return formatted[0]
    elif len(formatted) == 2:
        return f"{formatted[0]}, & {formatted[1]}"
    elif len(formatted) <= 20:
        return ", ".join(formatted[:-1]) + f", & {formatted[-1]}"
    else:
        return ", ".join(formatted[:19]) + f", ... {formatted[-1]}"
格式示例

同一条参考文献的不同样式:

BibTeX:

@article{Jumper_2021,
  title={Highly accurate protein structure prediction with AlphaFold},
  author={Jumper, John and Evans, Richard and Pritzel, Alexander},
  journal={Nature},
  volume={596},
  pages={583--589},
  year={2021},
  doi={10.1038/s41586-021-03819-2}
}

APA 第 7 版: Jumper, J., Evans, R., & Pritzel, A. (2021). Highly accurate protein structure prediction with AlphaFold. Nature, 596, 583-589. https://doi.org/10.1038/s41586-021-03819-2

MLA 第 9 版: Jumper, John, Richard Evans, and Alexander Pritzel. "Highly Accurate Protein Structure Prediction with AlphaFold." Nature, vol. 596, 2021, pp. 583-89.

Chicago(作者-日期制): Jumper, John, Richard Evans, and Alexander Pritzel. 2021. "Highly Accurate Protein Structure Prediction with AlphaFold." Nature 596: 583-89.

Vancouver: Jumper J, Evans R, Pritzel A. Highly accurate protein structure prediction with AlphaFold. Nature. 2021;596:583-9.

IEEE: J. Jumper, R. Evans, and A. Pritzel, "Highly accurate protein structure prediction with AlphaFold," Nature, vol. 596, pp. 583-589, 2021.

去重

检测重复条目
from difflib import SequenceMatcher

def find_duplicates(bib_entries, threshold=0.85):
    """Find duplicate BibTeX entries by title similarity."""
    duplicates = []
    titles = [(key, normalize_title(entry["title"]))
              for key, entry in bib_entries.items()]

    for i in range(len(titles)):
        for j in range(i + 1, len(titles)):
            similarity = SequenceMatcher(
                None, titles[i][1], titles[j][1]
            ).ratio()
            if similarity >= threshold:
                duplicates.append({
                    "entry_a": titles[i][0],
                    "entry_b": titles[j][0],
                    "similarity": similarity
                })
    return duplicates

def normalize_title(title):
    """Normalize title for comparison."""
    import re
    title = title.lower()
    title = re.sub(r'[{}\\]', '', title)  # Remove LaTeX formatting
    title = re.sub(r'[^a-z0-9\s]', '', title)  # Remove punctuation
    title = ' '.join(title.split())  # Normalize whitespace
    return title

def merge_duplicates(entry_a, entry_b):
    """Merge two duplicate entries, preferring the more complete one."""
    merged = {}
    all_fields = set(list(entry_a.keys()) + list(entry_b.keys()))
    for field in all_fields:
        val_a = entry_a.get(field, "")
        val_b = entry_b.get(field, "")
        # Prefer the longer (more complete) value
        merged[field] = val_a if len(str(val_a)) >= len(str(val_b)) else val_b
    return merged

验证

CrossRef 验证
def validate_citation(doi):
    """Validate a citation against CrossRef metadata."""
    url = f"https://api.crossref.org/works/{doi}"
    response = requests.get(url)
    if response.status_code != 200:
        return {"valid": False, "error": "DOI not found in CrossRef"}

    data = response.json()["message"]
    return {
        "valid": True,
        "title": data.get("title", [None])[0],
        "authors": [f"{a.get('family', '')}, {a.get('given', '')}"
                    for a in data.get("author", [])],
        "year": data.get("published-print", {}).get("date-parts", [[None]])[0][0],
        "journal": data.get("container-title", [None])[0],
        "type": data.get("type", "unknown")
    }
常见引文错误
错误检测方法修复方法
缺少 DOI检查 doi 字段是否为空按标题查询 CrossRef
年份错误与 CrossRef 数据对比使用 CrossRef 年份
作者姓名变体使用 ORCID 进行模糊匹配统一为 ORCID 姓名
重复条目标题相似度 > 85%合并为单个条目
URL 无效HTTP HEAD 请求返回 4xx/5xx更新或移除 URL
条目不完整缺少该样式要求的字段通过 DOI 检索

与写作工具集成

LaTeX
% In document preamble
\usepackage[backend=biber,style=apa]{biblatex}
\addbibresource{references.bib}

% In text
\textcite{Jumper_2021} showed that...
As demonstrated by previous work \parencite{Jumper_2021}...

% At end of document
\printbibliography
Pandoc Markdown
Previous work [@Jumper_2021] showed that...

## References
pandoc paper.md --citeproc --bibliography=references.bib \
  --csl=apa.csl -o paper.pdf

参考资料

评论

支持 Markdown