跳到主要内容

论文与报告精读摘要

收藏0

把用户提供的论文、文章和报告整理成有出处的摘要,比较多份材料的结论、证据与局限。

让 AI 安装

github最新版本 0.1.1更新于 2026-10-21下载 2 次2 个版本

scripts/extract_citations.py

9.8 KB
1#!/usr/bin/env python3
2"""
3research-summarizer: Citation Extractor
4
5Extract and format citations from text documents. Detects DOIs, URLs,
6author-year patterns, and numbered references. Outputs in APA, IEEE,
7Chicago, Harvard, or MLA format.
8
9Usage:
10 python scripts/extract_citations.py document.txt
11 python scripts/extract_citations.py document.txt --format ieee
12 python scripts/extract_citations.py document.txt --format apa --output json
13 python scripts/extract_citations.py --stdin < document.txt
14"""
15
16import argparse
17import json
18import re
19import sys
20from collections import OrderedDict
21
22
23# --- Citation Detection Patterns ---
24
25PATTERNS = {
26 "doi": re.compile(
27 r"(?:https?://doi\.org/|doi:\s*)(10\.\d{4,}/[^\s,;}\]]+)", re.IGNORECASE
28 ),
29 "url": re.compile(
30 r"https?://[^\s,;}\])\"'>]+", re.IGNORECASE
31 ),
32 "author_year": re.compile(
33 r"(?:^|\(|\s)([A-Z][a-z]+(?:\s(?:&|and)\s[A-Z][a-z]+)?(?:\set\sal\.?)?)\s*\((\d{4})\)",
34 ),
35 "numbered_ref": re.compile(
36 r"^\[(\d+)\]\s+(.+)$", re.MULTILINE
37 ),
38 "footnote": re.compile(
39 r"^\d+\.\s+([A-Z].+?(?:\d{4}).+)$", re.MULTILINE
40 ),
41}
42
43
44def extract_dois(text):
45 """Extract DOI references."""
46 citations = []
47 for match in PATTERNS["doi"].finditer(text):
48 doi = match.group(1).rstrip(".")
49 citations.append({
50 "type": "doi",
51 "doi": doi,
52 "raw": match.group(0).strip(),
53 "url": f"https://doi.org/{doi}",
54 })
55 return citations
56
57
58def extract_urls(text):
59 """Extract URL references (excluding DOI URLs already captured)."""
60 citations = []
61 for match in PATTERNS["url"].finditer(text):
62 url = match.group(0).rstrip(".,;)")
63 if "doi.org" in url:
64 continue
65 citations.append({
66 "type": "url",
67 "url": url,
68 "raw": url,
69 })
70 return citations
71
72
73def extract_author_year(text):
74 """Extract author-year citations like (Smith, 2023) or Smith & Jones (2021)."""
75 citations = []
76 for match in PATTERNS["author_year"].finditer(text):
77 author = match.group(1).strip()
78 year = match.group(2)
79 citations.append({
80 "type": "author_year",
81 "author": author,
82 "year": year,
83 "raw": f"{author} ({year})",
84 })
85 return citations
86
87
88def extract_numbered_refs(text):
89 """Extract numbered reference list entries like [1] Author. Title..."""
90 citations = []
91 for match in PATTERNS["numbered_ref"].finditer(text):
92 num = match.group(1)
93 content = match.group(2).strip()
94 citations.append({
95 "type": "numbered",
96 "number": int(num),
97 "content": content,
98 "raw": f"[{num}] {content}",
99 })
100 return citations
101
102
103def deduplicate(citations):
104 """Remove duplicate citations based on raw text."""
105 seen = OrderedDict()
106 for c in citations:
107 key = c.get("doi") or c.get("url") or c.get("raw", "")
108 key = key.lower().strip()
109 if key and key not in seen:
110 seen[key] = c
111 return list(seen.values())
112
113
114def classify_source(citation):
115 """Classify citation as primary, secondary, or tertiary."""
116 raw = citation.get("content", citation.get("raw", "")).lower()
117 if any(kw in raw for kw in ["meta-analysis", "systematic review", "literature review", "survey of"]):
118 return "secondary"
119 if any(kw in raw for kw in ["textbook", "encyclopedia", "handbook", "dictionary"]):
120 return "tertiary"
121 return "primary"
122
123
124# --- Formatting ---
125
126def format_apa(citation):
127 """Format citation in APA 7 style."""
128 if citation["type"] == "doi":
129 return f"https://doi.org/{citation['doi']}"
130 if citation["type"] == "url":
131 return f"Retrieved from {citation['url']}"
132 if citation["type"] == "author_year":
133 return f"{citation['author']} ({citation['year']})."
134 if citation["type"] == "numbered":
135 return citation["content"]
136 return citation.get("raw", "")
137
138
139def format_ieee(citation):
140 """Format citation in IEEE style."""
141 if citation["type"] == "doi":
142 return f"doi: {citation['doi']}"
143 if citation["type"] == "url":
144 return f"[Online]. Available: {citation['url']}"
145 if citation["type"] == "author_year":
146 return f"{citation['author']}, {citation['year']}."
147 if citation["type"] == "numbered":
148 return f"[{citation['number']}] {citation['content']}"
149 return citation.get("raw", "")
150
151
152def format_chicago(citation):
153 """Format citation in Chicago style."""
154 if citation["type"] == "doi":
155 return f"https://doi.org/{citation['doi']}."
156 if citation["type"] == "url":
157 return f"{citation['url']}."
158 if citation["type"] == "author_year":
159 return f"{citation['author']}. {citation['year']}."
160 if citation["type"] == "numbered":
161 return citation["content"]
162 return citation.get("raw", "")
163
164
165def format_harvard(citation):
166 """Format citation in Harvard style."""
167 if citation["type"] == "doi":
168 return f"doi:{citation['doi']}"
169 if citation["type"] == "url":
170 return f"Available at: {citation['url']}"
171 if citation["type"] == "author_year":
172 return f"{citation['author']} ({citation['year']})"
173 if citation["type"] == "numbered":
174 return citation["content"]
175 return citation.get("raw", "")
176
177
178def format_mla(citation):
179 """Format citation in MLA 9 style."""
180 if citation["type"] == "doi":
181 return f"doi:{citation['doi']}."
182 if citation["type"] == "url":
183 return f"{citation['url']}."
184 if citation["type"] == "author_year":
185 return f"{citation['author']}. {citation['year']}."
186 if citation["type"] == "numbered":
187 return citation["content"]
188 return citation.get("raw", "")
189
190
191FORMATTERS = {
192 "apa": format_apa,
193 "ieee": format_ieee,
194 "chicago": format_chicago,
195 "harvard": format_harvard,
196 "mla": format_mla,
197}
198
199
200# --- Demo Data ---
201
202DEMO_TEXT = """
203Recent studies in product management have shown significant shifts in methodology.
204According to Smith & Jones (2023), agile adoption has increased by 47% since 2020.
205Patel et al. (2022) found that cross-functional teams deliver 2.3x faster.
206
207Several frameworks have been proposed:
208[1] Cagan, M. Inspired: How to Create Tech Products Customers Love. Wiley, 2018.
209[2] Torres, T. Continuous Discovery Habits. Product Talk LLC, 2021.
210[3] Gothelf, J. & Seiden, J. Lean UX. O'Reilly Media, 2021. doi: 10.1234/leanux.2021
211
212For further reading, see https://www.svpg.com/articles/ and the meta-analysis
213by Chen (2024) on product discovery effectiveness.
214
215Related work: doi: 10.1145/3544548.3581388
216"""
217
218
219def run_extraction(text, fmt, output_mode):
220 """Run full extraction pipeline."""
221 all_citations = []
222 all_citations.extend(extract_dois(text))
223 all_citations.extend(extract_author_year(text))
224 all_citations.extend(extract_numbered_refs(text))
225 all_citations.extend(extract_urls(text))
226
227 citations = deduplicate(all_citations)
228
229 for c in citations:
230 c["classification"] = classify_source(c)
231
232 formatter = FORMATTERS.get(fmt, format_apa)
233
234 if output_mode == "json":
235 result = {
236 "format": fmt,
237 "total": len(citations),
238 "citations": [],
239 }
240 for i, c in enumerate(citations, 1):
241 result["citations"].append({
242 "index": i,
243 "type": c["type"],
244 "classification": c["classification"],
245 "formatted": formatter(c),
246 "raw": c.get("raw", ""),
247 })
248 print(json.dumps(result, indent=2))
249 else:
250 print(f"Citations ({fmt.upper()}) — {len(citations)} found\n")
251 primary = [c for c in citations if c["classification"] == "primary"]
252 secondary = [c for c in citations if c["classification"] == "secondary"]
253 tertiary = [c for c in citations if c["classification"] == "tertiary"]
254
255 for label, group in [("Primary Sources", primary), ("Secondary Sources", secondary), ("Tertiary Sources", tertiary)]:
256 if group:
257 print(f"### {label}")
258 for i, c in enumerate(group, 1):
259 print(f" {i}. {formatter(c)}")
260 print()
261
262 return citations
263
264
265def main():
266 parser = argparse.ArgumentParser(
267 description="research-summarizer: Extract and format citations from text"
268 )
269 parser.add_argument("file", nargs="?", help="Input text file (omit for demo)")
270 parser.add_argument(
271 "--format", "-f",
272 choices=["apa", "ieee", "chicago", "harvard", "mla"],
273 default="apa",
274 help="Citation format (default: apa)",
275 )
276 parser.add_argument(
277 "--output", "-o",
278 choices=["text", "json"],
279 default="text",
280 help="Output mode (default: text)",
281 )
282 parser.add_argument(
283 "--stdin",
284 action="store_true",
285 help="Read from stdin instead of file",
286 )
287 args = parser.parse_args()
288
289 if args.stdin:
290 text = sys.stdin.read()
291 elif args.file:
292 try:
293 with open(args.file, "r", encoding="utf-8") as f:
294 text = f.read()
295 except FileNotFoundError:
296 print(f"Error: File not found: {args.file}", file=sys.stderr)
297 sys.exit(1)
298 except IOError as e:
299 print(f"Error reading file: {e}", file=sys.stderr)
300 sys.exit(1)
301 else:
302 print("No input file provided. Running demo...\n")
303 text = DEMO_TEXT
304
305 run_extraction(text, args.format, args.output)
306
307
308if __name__ == "__main__":
309 main()

文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。