feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user