paper_reader_structure.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. """论文结构化解析 —— 章节分段、图表位置检测与结构树构建."""
  2. from __future__ import annotations
  3. import re
  4. from typing import Any
  5. from .paper_reader_context import (
  6. extract_references_section_raw_from_pdf_text,
  7. reference_strings_for_resolve_fallback,
  8. )
  9. _REF_HEADER = re.compile(
  10. r"(?is)(?:^|\n)[\s#]*(?:references|reference\s+list|bibliography|cited\s+references|引用文献|参考文献)\s*[::]?\s*(?:\n+|$)",
  11. )
  12. _CHAPTER_LINE = re.compile(
  13. r"(?m)^(?:\s|#)*(?:(?P<num>\d+(?:\.\d+){0,2})\.?\s+)?(?P<h>"
  14. r"Abstract|ABSTRACT|Introduction|INTRODUCTION|Related\s+Work|RELATED\s+WORK|"
  15. r"Background|BACKGROUND|Preliminar(?:y|ies)|Problem\s+Formulation|"
  16. r"Methodology|Method|Methods|Model|Models|Approach|Architecture|Framework|"
  17. r"Experiment(?:s)?|EXPERIMENTS|Implementation|Evaluation|Results?|RESULTS|Analysis|ANALYSIS|"
  18. r"Discussion|DISCUSSION|Ablation|Ablations|Comparison|Comparisons|"
  19. r"Conclusion|CONCLUSIONS?|Limitations?|LIMITATIONS|Future\s+Work|Broader\s+Impact|"
  20. r"Appendix|APPENDIX|Supplementary|Acknowledg(?:e)?ments?|ACKNOWLEDG|"
  21. r"摘要|引言|简介|预备|问题表述|相关工作|背景|方法|模型|架构|框架|"
  22. r"实验|实现|评估|结果|分析|讨论|消融|对比|结论|局限|未来工作|附录|补充|致谢"
  23. r")(?:\s*[.::#])?\s*$",
  24. re.I,
  25. )
  26. def _slug_heading(h: str) -> str:
  27. s = re.sub(r"[^\w\u4e00-\u9fff]+", "_", (h or "").strip().lower())
  28. s = re.sub(r"_+", "_", s).strip("_")
  29. return (s[:48] or "sec")
  30. def _split_chapters(
  31. text: str,
  32. *,
  33. max_chapter_chars: int,
  34. max_chapters: int,
  35. ) -> list[dict[str, Any]]:
  36. t = (text or "").strip()
  37. if not t:
  38. return []
  39. matches = list(_CHAPTER_LINE.finditer(t))
  40. if not matches:
  41. return [
  42. {
  43. "id": "document",
  44. "heading": "(未识别到标准章节标题)",
  45. "text": t[:max_chapter_chars],
  46. "truncated": len(t) > max_chapter_chars,
  47. }
  48. ]
  49. out: list[dict[str, Any]] = []
  50. p0 = matches[0].start()
  51. if p0 > 40:
  52. pre = t[:p0].strip()
  53. if len(pre) >= 24:
  54. out.append(
  55. {
  56. "id": "preamble",
  57. "heading": "(文首)",
  58. "text": pre[:max_chapter_chars],
  59. "truncated": len(pre) > max_chapter_chars,
  60. }
  61. )
  62. for i, m in enumerate(matches):
  63. if len(out) >= max_chapters:
  64. break
  65. start = m.end()
  66. end = matches[i + 1].start() if i + 1 < len(matches) else len(t)
  67. heading = (m.group("h") or "section").strip()
  68. body = t[start:end].strip()
  69. if not body:
  70. continue
  71. slug = _slug_heading(heading)
  72. out.append(
  73. {
  74. "id": f"{slug}_{i}",
  75. "heading": heading,
  76. "text": body[:max_chapter_chars],
  77. "truncated": len(body) > max_chapter_chars,
  78. }
  79. )
  80. return out
  81. def parse_pdf_merged_text_to_json(
  82. merged_text: str,
  83. *,
  84. max_chapter_chars: int = 12000,
  85. max_chapters: int = 24,
  86. max_ref_entries: int = 80,
  87. ) -> dict[str, Any]:
  88. t = (merged_text or "").strip()
  89. if not t:
  90. return {
  91. "version": 1,
  92. "chapters": [],
  93. "references": {"raw": "", "entries": [], "entry_count": 0},
  94. }
  95. m = _REF_HEADER.search(t)
  96. head_for_chapters = t[: m.start()].strip() if m else t
  97. ref_raw = extract_references_section_raw_from_pdf_text(t)
  98. entries = reference_strings_for_resolve_fallback(ref_raw, max_strings=max_ref_entries) if ref_raw else []
  99. chapters = _split_chapters(
  100. head_for_chapters,
  101. max_chapter_chars=max_chapter_chars,
  102. max_chapters=max_chapters,
  103. )
  104. return {
  105. "version": 1,
  106. "chapters": chapters,
  107. "references": {
  108. "raw": (ref_raw or "")[:28000],
  109. "raw_truncated": len(ref_raw or "") > 28000,
  110. "entry_count": len(entries),
  111. "entries": entries,
  112. },
  113. }