import 'dart:convert'; import 'dart:io'; import 'package:xml/xml.dart'; import 'package:flutter/foundation.dart'; // Copy of book abbreviations from BibleXmlParser const Map _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 _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 data = json.decode(jsonFile.readAsStringSync()); // 2. Load and Parse all XMLs final Map 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; 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 _processList(List list, Map xmlDocs) async { for (var item in list) { final String reference = item['reference']; Map 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? _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(); }