Files
999/scripts/clean_and_rebuild_cooper.py

149 lines
7.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
import json
from pathlib import Path
import fitz
BASE_DIR = Path('/home/alexives/Desktop/StudioProjects/999')
EXT_SYMBOLS_PATH = BASE_DIR / 'assets/data/oracle_external_symbols.json'
PASSPORTS_PATH = BASE_DIR / 'assets/data/oracle_passports.json'
CATALOG_PATH = BASE_DIR / 'assets/data/oracle_illustrations_catalog.json'
COOPER_PDF = Path('/home/alexives/Desktop/newsources/Kuper_Dzh_Entsiklopedia_simvolov.pdf')
ILLUST_DIR = BASE_DIR / 'assets/illustrations'
def dehyphenate(text: str) -> str:
# Fix soft hyphens, hyphen-minus, en-dash, em-dash followed by newline
text = re.sub(r'([А-Яа-яA-Za-zЁё]+)[-\xad\u2010\u2011\u2012\u2013\u2014]\s*\n\s*([А-Яа-яA-Za-zЁё]+)', r'\1\2', text)
# Collapse irregular whitespace inside line
return text
def parse_cooper():
print("📖 Парсинг Дж. Купер («Энциклопедия символов»)...")
doc = fitz.open(str(COOPER_PDF))
# 1. Сначала извлечем иллюстрации с их номерами и подписями
# В Купере подписи имеют вид: "Рис. 12. Текст..." или "Рис.12.\nТекст..."
page_illustrations = {} # page_no -> list of {file, caption, num}
for pno in range(6, len(doc)-1):
txt = doc[pno].get_text()
# Поиск подписей к рисункам
fig_matches = re.finditer(r'Рис\.\s*(\d+)[\.:]?\s*\n?([^\n]+(?:\n[^\n]+)?)', txt)
imgs = doc[pno].get_images()
extracted_on_page = []
for f_idx, fm in enumerate(fig_matches):
f_num = fm.group(1)
raw_caption = fm.group(2).replace('\n', ' ').strip()
# Убираем обрезки
clean_caption = re.sub(r'\s+', ' ', raw_caption)[:120].strip()
# Если на странице есть изображения
if f_idx < len(imgs):
xref = imgs[f_idx][0]
try:
base = doc.extract_image(xref)
w, h = base["width"], base["height"]
ext = base["ext"]
if w >= 150 and h >= 150:
fname = f"cooper_fig_{f_num}_p{pno+1}.{ext}"
out_path = ILLUST_DIR / fname
if not out_path.exists():
with open(out_path, "wb") as f_out:
f_out.write(base["image"])
extracted_on_page.append({
"id": f"cooper_fig_{f_num}",
"file": f"assets/illustrations/{fname}",
"caption": f"Рис. {f_num}. {clean_caption}",
"page": pno + 1,
"width": w,
"height": h,
"source": "Дж. Купер · «Энциклопедия символов» (1995)"
})
except Exception:
pass
if extracted_on_page:
page_illustrations[pno] = extracted_on_page
print(f" ✓ Извлечено {sum(len(v) for v in page_illustrations.values())} канонических иллюстраций Купера")
# 2. Теперь парсим словарные статьи
# Каждая статья начинается с названия в начале строки с точкой или тире
entries = {} # term -> {text, illustrations, page}
cur_term = None
cur_lines = []
cur_page = 0
stop_words = {
'энциклопедия символов', 'иллюстрации', 'содержание', 'указатель',
'латинские термины', 'библиография', 'введение', 'предисловие',
'рис', 'табл', 'приложение', 'часть'
}
for pno in range(6, len(doc)-1):
raw_txt = doc[pno].get_text()
clean_txt = dehyphenate(raw_txt)
lines = [l.strip() for l in clean_txt.splitlines() if l.strip()]
for l in lines:
# Игнорируем номера страниц и одиночные колонтитулы
if l.isdigit() or len(l) <= 1:
continue
# Проверяем, не заголовок ли статьи:
# Например: "Абрикос. Текст...", "Агнец. Текст...", "Белый орел. Текст..."
# Заголовок начинается с заглавной буквы, до 35 символов, заканчивается точкой или тире
m = re.match(r'^([А-ЯЁ][А-Яа-яЁё\s\-«»\(\)]{1,32}?)(?:\.|\s+—|\s+-)\s*(.*)', l)
is_valid_header = False
if m:
cand_term = m.group(1).strip()
rest = m.group(2).strip()
norm = cand_term.lower()
# Фильтрация мусора: не колонтитул с номером, не стоп-слово, не целое предложение (<=3 слов)
words = norm.split()
if (1 <= len(words) <= 3 and
norm not in stop_words and
not any(c.isdigit() for c in norm) and
not norm.startswith('см.') and
not norm.startswith('рис.') and
not norm.startswith('в ') and
not norm.startswith('у ') and
not norm.startswith('от ')):
is_valid_header = True
if is_valid_header:
if cur_term and cur_lines:
full_text = re.sub(r'\s+', ' ', ' '.join(cur_lines)).strip()
if len(full_text) >= 30:
entries[cur_term] = {
'text': full_text,
'page': cur_page,
'illustrations': page_illustrations.get(cur_page, [])
}
cur_term = cand_term.capitalize()
cur_page = pno
cur_lines = [rest] if rest else []
else:
if cur_term:
# Игнорируем строки-подписи рисунков в теле статьи
if not l.startswith('Рис.'):
cur_lines.append(l)
if cur_term and cur_lines:
full_text = re.sub(r'\s+', ' ', ' '.join(cur_lines)).strip()
if len(full_text) >= 30:
entries[cur_term] = {
'text': full_text,
'page': cur_page,
'illustrations': page_illustrations.get(cur_page, [])
}
print(f" ✓ Распознано {len(entries)} чистых словарных статей Купера!")
return entries
if __name__ == '__main__':
entries = parse_cooper()
sample = list(entries.keys())[:25]
print("Первые 25 чистых символов Купера:", sample)