|
|
@@ -26,6 +26,7 @@ app = typer.Typer(add_completion=False, no_args_is_help=True)
|
|
26
|
26
|
|
|
27
|
27
|
_TABLE_ROW: re.Pattern = re.compile(r"^\|.+\|$")
|
|
28
|
28
|
_UNESCAPE_TEXT: re.Pattern = re.compile(r"\\text\{([^}]+)\}")
|
|
|
29
|
+_TABLE_CONTINUATION: re.Pattern = re.compile(r"Продолжение\s+таблицы|Окончание\s+таблицы", re.IGNORECASE)
|
|
29
|
30
|
|
|
30
|
31
|
|
|
31
|
32
|
def _clean_formula(text: str) -> str:
|
|
|
@@ -79,6 +80,22 @@ def _last_table_block(blocks: list[dict]) -> tuple[dict | None, int]:
|
|
79
|
80
|
return None, -1
|
|
80
|
81
|
|
|
81
|
82
|
|
|
|
83
|
+def _is_continuation_text(block: dict) -> bool:
|
|
|
84
|
+ content = (block.get("content", "") or "").strip()
|
|
|
85
|
+ return bool(content) and not _is_table_block(block) and bool(_TABLE_CONTINUATION.search(content))
|
|
|
86
|
+
|
|
|
87
|
+
|
|
|
88
|
+def _first_table_block(blocks: list[dict]) -> tuple[dict | None, int]:
|
|
|
89
|
+ """Первая таблица, пропуская префикс «Продолжение таблицы». Возвращает (блок, индекс)."""
|
|
|
90
|
+ for i, block in enumerate(blocks):
|
|
|
91
|
+ if _is_continuation_text(block):
|
|
|
92
|
+ continue
|
|
|
93
|
+ if _is_table_block(block):
|
|
|
94
|
+ return block, i
|
|
|
95
|
+ break
|
|
|
96
|
+ return None, -1
|
|
|
97
|
+
|
|
|
98
|
+
|
|
82
|
99
|
def _merge_split_tables(pages: list[dict], names: list[str]) -> int:
|
|
83
|
100
|
"""Объединяет split tables, охватывающие несколько соседних страниц (in-place)."""
|
|
84
|
101
|
merged_count = 0
|
|
|
@@ -94,7 +111,7 @@ def _merge_split_tables(pages: list[dict], names: list[str]) -> int:
|
|
94
|
111
|
j = i + 1
|
|
95
|
112
|
while j < len(pages):
|
|
96
|
113
|
blocks_b = pages[j].get("blocks", pages[j].get("parsing_res_list", []))
|
|
97
|
|
- first_b = blocks_b[0] if blocks_b and _is_table_block(blocks_b[0]) else None
|
|
|
114
|
+ first_b, first_b_idx = _first_table_block(blocks_b)
|
|
98
|
115
|
if first_b is None or _table_ncols(first_b) != ncols_a:
|
|
99
|
116
|
break
|
|
100
|
117
|
|
|
|
@@ -114,7 +131,13 @@ def _merge_split_tables(pages: list[dict], names: list[str]) -> int:
|
|
114
|
131
|
break
|
|
115
|
132
|
|
|
116
|
133
|
last_a["content"] = (last_a.get("content", "") or "").rstrip() + "\n" + tail_rows
|
|
117
|
|
- del blocks_b[0]
|
|
|
134
|
+ # Удалить префикс «Продолжение таблицы» и саму таблицу
|
|
|
135
|
+ del blocks_b[first_b_idx]
|
|
|
136
|
+ for idx in range(first_b_idx - 1, -1, -1):
|
|
|
137
|
+ if _is_continuation_text(blocks_b[idx]):
|
|
|
138
|
+ del blocks_b[idx]
|
|
|
139
|
+ else:
|
|
|
140
|
+ break
|
|
118
|
141
|
merged_count += 1
|
|
119
|
142
|
logger.info("Таблицы объединены: %s + %s (%d колонок)", names[i], names[j], ncols_a)
|
|
120
|
143
|
j += 1
|
|
|
@@ -124,12 +147,21 @@ def _merge_split_tables(pages: list[dict], names: list[str]) -> int:
|
|
124
|
147
|
return merged_count
|
|
125
|
148
|
|
|
126
|
149
|
|
|
|
150
|
+def _is_page_num_block(block: dict) -> bool:
|
|
|
151
|
+ return block.get("label") == "text" and bool(PAGE_NUM.match((block.get("content", "") or "").strip()))
|
|
|
152
|
+
|
|
|
153
|
+
|
|
127
|
154
|
def _filter_page_numbers(blocks: list[dict]) -> list[dict]:
|
|
128
|
|
- dropped = [b for b in blocks if b.get("label") == "text" and PAGE_NUM.match((b.get("content", "") or "").strip())]
|
|
|
155
|
+ dropped: list[dict] = []
|
|
|
156
|
+ result = list(blocks)
|
|
|
157
|
+ while result and _is_page_num_block(result[0]):
|
|
|
158
|
+ dropped.append(result.pop(0))
|
|
|
159
|
+ while result and _is_page_num_block(result[-1]):
|
|
|
160
|
+ dropped.append(result.pop())
|
|
129
|
161
|
if dropped:
|
|
130
|
162
|
nums = ", ".join((b["content"] or "").strip() for b in dropped)
|
|
131
|
|
- return [b for b in blocks if b not in dropped], nums
|
|
132
|
|
- return blocks, ""
|
|
|
163
|
+ return result, nums
|
|
|
164
|
+ return result, ""
|
|
133
|
165
|
|
|
134
|
166
|
|
|
135
|
167
|
def _process_raw(raw_path: Path) -> dict | None:
|
|
|
@@ -155,6 +187,7 @@ def _html_to_text(content: str) -> str:
|
|
155
|
187
|
return content
|
|
156
|
188
|
from bs4 import BeautifulSoup
|
|
157
|
189
|
|
|
|
190
|
+ content = re.sub(r"<br\s*/?>", " ", content, flags=re.IGNORECASE)
|
|
158
|
191
|
soup = BeautifulSoup(content, "html.parser")
|
|
159
|
192
|
for math_tag in soup.find_all("math"):
|
|
160
|
193
|
display = math_tag.get("display", "") == "block"
|