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)