/// Канонический реестр культурных ветвей человечества, через которые /// толкуется каждый знак. Порядок списка — порядок показа в «учебнике». class CultureBranch { final String id; final String name; final String emoji; final String subtitle; /// Стемы/ключевые слова (в нижнем регистре), по которым ветвь узнаётся в тексте. final List 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( '(? matcher.hasMatch(text.toLowerCase()); } const List 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 extractCulturalBranches(String text) { final result = {}; 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> orderedCultures(Map 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 parseCulturesBlock(String block) { final result = {}; 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 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(); }