Files
Tracker/tool/optimize_assets.dart

283 lines
8.2 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:xml/xml.dart';
import 'package:flutter/foundation.dart';
// Copy of book abbreviations from BibleXmlParser
const Map<String, String> _bookAbbreviations = {
'genesis': 'Gen',
'exodus': 'Exod',
'leviticus': 'Lev',
'numbers': 'Num',
'deuteronomy': 'Deut',
'joshua': 'Josh',
'judges': 'Judg',
'ruth': 'Ruth',
'1 samuel': '1Sam',
'2 samuel': '2Sam',
'1 kings': '1Kgs',
'2 kings': '2Kgs',
'1 chronicles': '1Chr',
'2 chronicles': '2Chr',
'ezra': 'Ezra',
'nehemiah': 'Neh',
'esther': 'Esth',
'job': 'Job',
'psalm': 'Ps',
'proverbs': 'Prov',
'ecclesiastes': 'Eccl',
'song of solomon': 'Song',
'isaiah': 'Isa',
'jeremiah': 'Jer',
'lamentations': 'Lam',
'ezekiel': 'Ezek',
'daniel': 'Dan',
'hosea': 'Hos',
'joel': 'Joel',
'amos': 'Amos',
'obadiah': 'Obad',
'jonah': 'Jonah',
'micah': 'Mic',
'nahum': 'Nah',
'habakkuk': 'Hab',
'zephaniah': 'Zeph',
'haggai': 'Hag',
'zechariah': 'Zech',
'malachi': 'Mal',
'matthew': 'Matt',
'mark': 'Mark',
'luke': 'Luke',
'john': 'John',
'acts': 'Acts',
'romans': 'Rom',
'1 corinthians': '1Cor',
'2 corinthians': '2Cor',
'galatians': 'Gal',
'ephesians': 'Eph',
'philippians': 'Phil',
'colossians': 'Col',
'1 thessalonians': '1Thess',
'2 thessalonians': '2Thess',
'1 timothy': '1Tim',
'2 timothy': '2Tim',
'titus': 'Titus',
'philemon': 'Phlm',
'hebrews': 'Heb',
'james': 'Jas',
'1 peter': '1Pet',
'2 peter': '2Pet',
'1 john': '1John',
'2 john': '2John',
'3 john': '3John',
'jude': 'Jude',
'revelation': 'Rev',
};
// Map of translations to filenames
final Map<String, String> _translationFiles = {
'esv': 'assets/bible_xml/ESV.xml',
'niv': 'assets/bible_xml/NIV.xml',
'nkjv': 'assets/bible_xml/NKJV.xml',
'nlt': 'assets/bible_xml/NLT.xml',
'nasb': 'assets/bible_xml/NASB.xml',
'kjv': 'assets/bible_xml/KJV.xml',
'msg': 'assets/bible_xml/MSG.xml',
};
void main() async {
debugPrint('Starting asset optimization...');
// 1. Load the base JSON
final File jsonFile = File('assets/scriptures.json');
if (!jsonFile.existsSync()) {
debugPrint('Error: assets/scriptures.json not found.');
return;
}
final Map<String, dynamic> data = json.decode(jsonFile.readAsStringSync());
// 2. Load and Parse all XMLs
final Map<String, XmlDocument> xmlDocs = {};
for (var entry in _translationFiles.entries) {
final key = entry.key;
final path = entry.value;
final file = File(path);
if (file.existsSync()) {
debugPrint('Parsing $key from $path...');
try {
xmlDocs[key] = XmlDocument.parse(file.readAsStringSync());
} catch (e) {
debugPrint('Error parsing $path: $e');
}
} else {
debugPrint('Warning: $path not found.');
}
}
// 3. Process the JSON structure
for (var phase in ['menstrual', 'follicular', 'ovulation', 'luteal']) {
if (data[phase] != null) {
await _processList(data[phase] as List, xmlDocs);
}
}
if (data['husband'] != null) {
await _processList(data['husband'] as List, xmlDocs);
}
if (data['womanhood'] != null) {
await _processList(data['womanhood'] as List, xmlDocs);
}
if (data['contextual'] != null) {
final contextualMap = data['contextual'] as Map<String, dynamic>;
for (var key in contextualMap.keys) {
await _processList(contextualMap[key] as List, xmlDocs);
}
}
// 4. Write the optimized JSON
final outputFile = File('assets/scriptures_optimized.json');
outputFile.writeAsStringSync(json.encode(data)); // Minified
// outputFile.writeAsStringSync(const JsonEncoder.withIndent(' ').convert(data)); // Pretty print
debugPrint(
'Optimization complete. Wrote to assets/scriptures_optimized.json');
}
Future<void> _processList(List list, Map<String, XmlDocument> xmlDocs) async {
for (var item in list) {
final String reference = item['reference'];
Map<String, dynamic> verses = item['verses'] ?? {};
// Parse reference
final parts = _parseReference(reference);
if (parts == null) {
debugPrint('Skipping invalid reference: $reference');
continue;
}
// Look up for each translation
for (var entry in _translationFiles.entries) {
final transKey = entry.key; // esv, niv, etc.
// If already has text, skip (or overwrite? Let's overwrite to ensure consistency,
// but the original JSON had manual entries. The user wants to use the XMLs.
// Let's only fill if missing or if we want to enforce XML source.)
// Strategy: Fill if we have XML data.
if (xmlDocs.containsKey(transKey)) {
final text = _getVerseFromXml(xmlDocs[transKey]!, parts['book']!,
int.parse(parts['chapter']!), int.parse(parts['verse']!));
if (text != null) {
verses[transKey] = text;
} else {
debugPrint('Warning: Could not find $reference in $transKey');
}
}
}
item['verses'] = verses;
}
}
Map<String, String>? _parseReference(String reference) {
final parts = reference.split(' ');
if (parts.length < 2) return null;
String book = parts.sublist(0, parts.length - 1).join(' ').toLowerCase();
String chapterVerse = parts.last;
final cvParts = chapterVerse.split(':');
if (cvParts.length < 2) {
return null; // Only handles simple Chapter:Verse for now
}
return {
'book': book,
'chapter': cvParts[0],
// Handle cases like "1-2" by just taking the first one?
// The current parser Logic only takes one verse number.
// If the reference is "Psalm 23:1-2", the XML parser expects a single integer.
// We should probably just parse the first verse or handle ranges?
// For this fix, let's just parse the start verse to prevent crashing on int.parse
'verse': cvParts[1].split('-')[0],
};
}
String? _getVerseFromXml(
XmlDocument document, String bookName, int chapterNum, int verseNum) {
// Standardize book name for lookup
String lookupBookName =
_bookAbbreviations[bookName.toLowerCase()] ?? bookName;
// Use root element to avoid full document search
XmlElement root = document.rootElement;
// -- Find Book --
// Try Schema 1: Direct child of root
var bookElement = root.findElements('BIBLEBOOK').firstWhere(
(element) {
final nameAttr = element.getAttribute('bname');
return nameAttr?.toLowerCase() == lookupBookName.toLowerCase() ||
nameAttr?.toLowerCase() == bookName.toLowerCase();
},
orElse: () => XmlElement(XmlName('notfound')),
);
// Try Schema 2 if not found
if (bookElement.name.local == 'notfound') {
bookElement = root.findElements('b').firstWhere(
(element) {
final nameAttr = element.getAttribute('n');
return nameAttr?.toLowerCase() == lookupBookName.toLowerCase() ||
nameAttr?.toLowerCase() == bookName.toLowerCase();
},
orElse: () => XmlElement(XmlName('notfound')),
);
}
if (bookElement.name.local == 'notfound') {
return null;
}
// -- Find Chapter --
// Try Schema 1: Direct child of book
var chapterElement = bookElement.findElements('CHAPTER').firstWhere(
(element) => element.getAttribute('cnumber') == chapterNum.toString(),
orElse: () => XmlElement(XmlName('notfound')),
);
// Try Schema 2 if not found
if (chapterElement.name.local == 'notfound') {
chapterElement = bookElement.findElements('c').firstWhere(
(element) => element.getAttribute('n') == chapterNum.toString(),
orElse: () => XmlElement(XmlName('notfound')),
);
}
if (chapterElement.name.local == 'notfound') {
return null;
}
// -- Find Verse --
// Try Schema 1: Direct child of chapter
var verseElement = chapterElement.findElements('VERS').firstWhere(
(element) => element.getAttribute('vnumber') == verseNum.toString(),
orElse: () => XmlElement(XmlName('notfound')),
);
// Try Schema 2 if not found
if (verseElement.name.local == 'notfound') {
verseElement = chapterElement.findElements('v').firstWhere(
(element) => element.getAttribute('n') == verseNum.toString(),
orElse: () => XmlElement(XmlName('notfound')),
);
}
if (verseElement.name.local == 'notfound') {
return null;
}
// Extract the text content of the verse
return verseElement.innerText.trim();
}