feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets

This commit is contained in:
2026-10-09 15:26:58 +05:00
parent 7f91366201
commit 0b12e4583b
244 changed files with 611461 additions and 1617 deletions
+243
View File
@@ -0,0 +1,243 @@
/// Канонический реестр культурных ветвей человечества, через которые
/// толкуется каждый знак. Порядок списка — порядок показа в «учебнике».
class CultureBranch {
final String id;
final String name;
final String emoji;
final String subtitle;
/// Стемы/ключевые слова (в нижнем регистре), по которым ветвь узнаётся в тексте.
final List<String> stems;
const CultureBranch({
required this.id,
required this.name,
required this.emoji,
required this.subtitle,
required this.stems,
});
/// Dart-овский `\b` не понимает кириллицу, поэтому граница слова
/// задаётся явным lookbehind по буквам.
RegExp get matcher => RegExp(
'(?<![a-zа-яё])(?:${stems.map(RegExp.escape).join('|')})',
caseSensitive: false,
unicode: true,
);
bool matches(String text) => matcher.hasMatch(text.toLowerCase());
}
const List<CultureBranch> kCultureBranches = [
CultureBranch(
id: 'sumer',
name: 'Шумер и Месопотамия',
emoji: '🏺',
subtitle: 'Клинопись • Ану, Энлиль, Инанна, Уту',
stems: ['шумер', 'аккад', 'месопотам', 'вавилон', 'ассири', 'клинопис', 'уту', 'инанн', 'иштар', 'энлил', 'энки', 'мардук', 'тиамат', 'ануннак', 'гильгамеш', 'эрешкигаль', 'mūšu', 'ilū'],
),
CultureBranch(
id: 'egypt',
name: 'Древний Египет',
emoji: '🪲',
subtitle: 'Нил и Дуат • Ра, Осирис, Исида, Маат',
stems: ['египет', 'египт', 'фараон', 'апоп', 'нут ', 'нут,', 'дуат', 'осирис', 'исид', 'тот ', 'маат', 'анубис', 'сехмет', 'хепри', 'атум', 'птах', 'ра ', 'ра,', 'ра.', 'гор '],
),
CultureBranch(
id: 'judaic',
name: 'Иудейская традиция и Каббала',
emoji: '✡️',
subtitle: 'Танах, Талмуд, Зоар • Древо Сфирот',
stems: ['иудей', 'иудаизм', 'еврей', 'каббал', 'танах', 'тора ', 'торы', 'талмуд', 'зоар', 'библ', 'бытие', 'бытия', 'сфир', 'цимцум', 'иврит', 'йом', 'эрев', 'лайла', 'хошех', 'эйн-соф', 'эйн соф', 'шхин'],
),
CultureBranch(
id: 'greco_roman',
name: 'Античность (Греция и Рим)',
emoji: '🏛️',
subtitle: 'Гесиод, Гомер, Платон • Олимп и Хаос',
stems: ['греци', 'греческ', 'грек', 'эллин', 'римск', 'рима', 'латин', 'античн', 'зевс', 'нюкт', 'гемер', 'эфир', 'юпитер', 'аполлон', 'гелиос', 'гомер', 'гесиод', 'платон', 'персефон', 'гермес', 'dies irae'],
),
CultureBranch(
id: 'celtic',
name: 'Кельты и друиды',
emoji: '🌿',
subtitle: 'Огам, Самайн, Белтайн • Иной мир сидов',
stems: ['кельт', 'огам', 'друид', 'самайн', 'белтайн', 'имболк', 'лугнас', 'ирланд', 'валлий', 'бригит', 'сиды', 'туата'],
),
CultureBranch(
id: 'norse',
name: 'Скандинавия и германцы',
emoji: 'ᚱ',
subtitle: 'Эдды, руны • Один, Иггдрасиль, Рагнарёк',
stems: ['скандинав', 'германск', 'норвеж', 'исланд', 'эдд', 'руна ', 'руны', 'рун ', 'одина', 'одину', 'иггдрасил', 'рагнар', 'асгард', 'вальхал', 'фрейя', 'локи', 'норны', 'дагаз'],
),
CultureBranch(
id: 'slavic',
name: 'Славянская традиция',
emoji: '☀️',
subtitle: 'Явь, Навь, Правь • Велес, Перун, Макошь',
stems: ['славян', 'древнерус', 'явь', 'навь', 'нави ', 'правь', 'велес', 'перун', 'макош', 'мокош', 'сварог', 'даждьбог', 'ярил', 'купал', 'коляд', 'стрибог', 'домов', 'леший', 'русалк'],
),
CultureBranch(
id: 'vedic',
name: 'Ведическая Индия',
emoji: '🕉️',
subtitle: 'Веды и Упанишады • Рита, карма, атман',
stems: ['индийск', 'индуи', 'ведическ', 'веды', 'вед ', 'ригвед', 'упанишад', 'санскрит', 'дхарм', 'карм', 'брахман', 'атман', 'сурья', 'агни', 'индра', 'шива', 'вишну', 'кали ', 'рита ', 'тантр'],
),
CultureBranch(
id: 'persian',
name: 'Персия и зороастризм',
emoji: '🔥',
subtitle: 'Авеста • Ахура-Мазда и Ахриман, Аша',
stems: ['персид', 'персы', 'иран', 'зороастр', 'заратустр', 'авест', 'ахура', 'ахриман', 'митр'],
),
CultureBranch(
id: 'china',
name: 'Древний Китай',
emoji: '☯️',
subtitle: 'Дао, Инь-Ян, У-син • Книга Перемен',
stems: ['китай', 'дао', 'инь', 'ян ', 'ян,', 'ян.', 'ян:', 'и цзин', 'и-цзин', 'гексаграмм', 'триграмм', 'конфуци', 'лао-цзы', 'лао цзы', 'чжуан', 'у-син', 'у син', 'иероглиф'],
),
CultureBranch(
id: 'japan',
name: 'Япония и синто',
emoji: '⛩️',
subtitle: 'Кодзики • Аматэрасу, ками, дзэн',
stems: ['япон', 'синто', 'аматэрасу', 'ками ', 'кодзики', 'дзэн', 'идзанаги', 'идзанами'],
),
CultureBranch(
id: 'islamic',
name: 'Ислам и суфизм',
emoji: '🌙',
subtitle: 'Коран, суфийская поэзия • Руми, Ибн Араби',
stems: ['ислам', 'суфи', 'суфий', 'арабск', 'коран', 'лайл ', 'лайл,', 'мирадж', 'лайлят', 'тасаввуф', 'руми', 'дервиш', 'ибн араби', 'геомант', 'рамл', 'тетраграмм'],
),
CultureBranch(
id: 'christian',
name: 'Христианская мистика',
emoji: '✝️',
subtitle: 'Отцы-пустынники, Апокалипсис • Логос',
stems: ['христиан', 'евангел', 'апокалипс', 'отшельник', 'пустынник', 'монах', 'литург', 'daemon meridianus', 'акеди', 'страшный суд', 'иисус'],
),
CultureBranch(
id: 'mesoamerica',
name: 'Мезоамерика и Анды',
emoji: '🌞',
subtitle: 'Ацтеки, майя, инки • Кецалькоатль, Тонатиу',
stems: ['ацтек', 'майя', 'мезоамерик', 'тонатиу', 'кецалькоатл', 'тескатлипок', 'попол-вух', 'попол вух', 'инки', 'инков'],
),
CultureBranch(
id: 'oceania',
name: 'Полинезия и Океания',
emoji: '🌺',
subtitle: 'По и Ао • Ранги и Папа, мана, табу',
stems: ['полинези', 'маори', 'гавай', 'pō', 'ao)', 'ранги', 'тангароа'],
),
CultureBranch(
id: 'hermetic',
name: 'Алхимия и герметизм',
emoji: '⚗️',
subtitle: 'Изумрудная скрижаль • Нигредо → Рубедо',
stems: ['алхими', 'гермет', 'нигредо', 'альбедо', 'цитринитас', 'рубедо', 'атанор', 'трисмегист', 'великое делание', 'философский камень', 'геоманти', 'геомант', 'puer', 'rubeus', 'albus'],
),
];
CultureBranch? cultureBranchByName(String name) {
final lower = name.toLowerCase().trim();
for (final b in kCultureBranches) {
if (b.name.toLowerCase() == lower || b.id == lower) return b;
}
for (final b in kCultureBranches) {
if (b.matches(' $lower ')) return b;
}
return null;
}
/// Раскладывает текст толкования на культурные ветви: каждый абзац,
/// где ветвь упомянута, попадает в её раздел (абзац может принадлежать
/// нескольким культурам — это и есть материал для сравнения).
Map<String, String> extractCulturalBranches(String text) {
final result = <String, String>{};
if (text.trim().isEmpty) return result;
final paragraphs = text
.split(RegExp(r'\r?\n'))
.map((p) => p.trim())
.where((p) => p.isNotEmpty)
.toList();
for (final branch in kCultureBranches) {
final hits = paragraphs.where((p) => branch.matches(' $p ')).toList();
if (hits.isNotEmpty) result[branch.name] = hits.join('\n\n');
}
return result;
}
/// Сортирует произвольную карту культур в каноническом порядке реестра.
List<MapEntry<String, String>> orderedCultures(Map<String, String> cultures) {
int rank(String name) {
final b = cultureBranchByName(name);
return b == null ? 999 : kCultureBranches.indexOf(b);
}
final entries = cultures.entries.where((e) => e.value.trim().isNotEmpty).toList()
..sort((a, b) => rank(a.key).compareTo(rank(b.key)));
return entries;
}
/// Разбирает блок «## КУЛЬТУРЫ» на ветви по подзаголовкам
/// (`### Шумер и Месопотамия`, `**Древний Египет**`, `Кельты:` …).
Map<String, String> parseCulturesBlock(String block) {
final result = <String, String>{};
final lines = block.replaceAll('\r\n', '\n').split('\n');
String? current;
final buf = StringBuffer();
void flush() {
final text = buf.toString().trim();
final key = current;
if (key != null && text.isNotEmpty) {
result[key] = result.containsKey(key) ? '${result[key]}\n\n$text' : text;
}
buf.clear();
}
String? headingOf(String raw) {
final line = raw.trim();
if (line.isEmpty || line.length > 90) return null;
final looksLikeHeading = line.startsWith('#') ||
(line.startsWith('**') && line.endsWith('**')) ||
RegExp(r'^[-•*]?\s*\*\*[^*]+\*\*\s*[:—–-]?\s*$').hasMatch(line) ||
(line.endsWith(':') && line.length < 60);
if (!looksLikeHeading) return null;
final clean = line
.replaceAll(RegExp(r'^[#\s•*-]+'), '')
.replaceAll(RegExp(r'[*:—–\s-]+$'), '')
.replaceAll('*', '')
.trim();
final branch = cultureBranchByName(clean);
return branch?.name ?? (clean.isNotEmpty ? clean : null);
}
for (final line in lines) {
final h = headingOf(line);
if (h != null) {
flush();
current = h;
} else {
buf.writeln(line);
}
}
flush();
return result;
}
/// Обратная операция для редактора: карта культур → markdown-блок.
String formatCulturesBlock(Map<String, String> cultures) {
final sb = StringBuffer();
for (final e in orderedCultures(cultures)) {
sb.writeln('### ${e.key}');
sb.writeln(e.value.trim());
sb.writeln();
}
return sb.toString().trim();
}