Files
999/lib/models/culture_branch.dart
T

244 lines
12 KiB
Dart
Raw 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.
/// Канонический реестр культурных ветвей человечества, через которые
/// толкуется каждый знак. Порядок списка — порядок показа в «учебнике».
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();
}