import json import os import logging import sqlite3 import re from gematria import calculate_gematria, strip_diacritics from deep_translator import GoogleTranslator logger = logging.getLogger(__name__) def get_sura_count() -> int: """Returns the total number of suras in the Quran.""" base_path = "texts/quran" # Count the number of JSON files in the quran directory try: files = [f for f in os.listdir(base_path) if f.endswith('.json')] return len(files) except FileNotFoundError: logger.error(f"Directory {base_path} not found.") return 114 # Default number of suras in the Quran def get_quran_text_and_map(): """ Loads all Quran text and creates a character-to-location map. This is a helper to avoid repeatedly loading and processing the text. """ # This could be cached in memory for performance if the app is long-running all_text = "" char_map = [] # List of (sura_id, sura_name, verse_id) for each character sura_count = get_sura_count() for sura_num in range(1, sura_count + 1): sura_str = f"{sura_num:03d}" file_path = f"texts/quran/{sura_str}.json" if not os.path.exists(file_path): continue try: with open(file_path, 'r', encoding='utf-8') as f: sura_data = json.load(f) except (json.JSONDecodeError, FileNotFoundError): continue sura_name = sura_data.get('name', f"Sura {sura_num}") if all_text: all_text += " " verses = sura_data.get('text', []) or sura_data.get('verse', {}) if isinstance(verses, dict): verses = [verses[key] for key in sorted(verses.keys())] for verse_idx, verse in enumerate(verses, 1): if verse: # Add original verse text to all_text all_text += verse + " " # For the char_map, use the cleaned version cleaned_verse = strip_diacritics(verse).replace(" ", "") for _ in cleaned_verse: char_map.append((sura_num, sura_name, verse_idx)) # Clean up the text for ELS: strip diacritics, remove any special characters, etc. clean_text = strip_diacritics(all_text) # The reference implementation is more aggressive in cleaning. clean_text = ''.join(c for c in clean_text if c.isalpha() or c.isspace()) text_no_spaces = clean_text.replace(" ", "") return text_no_spaces, char_map def get_first_els_result_quran(gematria_sum: int, tlang: str = "en", cached_process_func=None) -> dict: """ Gets the first ELS result from the Quran using the gematria sum as the step. This version is aligned with the logic from the reference 'daily_psalm' project. """ import hashlib logger.debug(f"Entering get_first_els_result_quran (reference logic) with gematria_sum: {gematria_sum}") # Caching logic remains similar cache_key = f"quran_els_ref_{gematria_sum}_{tlang}" cache_file = "els_cache.db" try: with sqlite3.connect(cache_file) as conn: cursor = conn.cursor() cursor.execute( "SELECT results FROM els_cache WHERE query_hash = ?", (hashlib.sha256(cache_key.encode()).hexdigest(),)) result = cursor.fetchone() if result: logger.info(f"Cache hit for Quran ELS query: {cache_key}") return json.loads(result[0]) except sqlite3.Error as e: logger.error(f"Database error checking cache: {e}") logger.info(f"Cache miss for Quran ELS query: {cache_key}, performing search") text_no_spaces, char_map = get_quran_text_and_map() logger.debug(f"Processed text length (for ELS): {len(text_no_spaces)}") logger.debug(f"First 100 chars of processed text: {text_no_spaces[:100]}") result = None # Start positions to try (first 100 positions for better coverage) for start_pos in range(min(100, len(text_no_spaces))): extracted = "" positions = [] pos = start_pos # Extract until the end of the text is reached while pos < len(text_no_spaces): extracted += text_no_spaces[pos] positions.append(pos) pos += gematria_sum if len(extracted) >= 3: # At least 3 characters first_pos = positions[0] last_pos = positions[-1] if first_pos < len(char_map) and last_pos < len(char_map): first_loc = char_map[first_pos] last_loc = char_map[last_pos] result = { "result_text": extracted, "source": "Quran", "start_position": start_pos, "step": gematria_sum, "start_sura": first_loc[0], "start_sura_name": first_loc[1], "start_verse": first_loc[2], "end_sura": last_loc[0], "end_sura_name": last_loc[1], "end_verse": last_loc[2], "positions": positions, "gematria": calculate_gematria(extracted) # Add gematria here } logger.debug(f"Found ELS result: {result}") break # Found a result, stop searching else: logger.warning(f"Character position mapping inconsistency: {first_pos}, {last_pos} vs {len(char_map)}") continue if result: try: with sqlite3.connect(cache_file) as conn: cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS els_cache ( query_hash TEXT PRIMARY KEY, function_name TEXT, args TEXT, kwargs TEXT, results TEXT ) ''') cursor.execute( "INSERT OR REPLACE INTO els_cache (query_hash, function_name, args, kwargs, results) VALUES (?, ?, ?, ?, ?)", (hashlib.sha256(cache_key.encode()).hexdigest(), "get_first_els_result_quran_ref", json.dumps([gematria_sum]), json.dumps({"tlang": tlang}), json.dumps(result))) conn.commit() logger.debug("Cached Quran ELS results in database.") except sqlite3.Error as e: logger.error(f"Database error caching results: {e}") logger.debug(f"Exiting get_first_els_result_quran, returning: {result}") return result def find_shortest_sura_match(gematria_sum, db_file='abjad.db'): """ Finds the shortest Sura (Aya) entry in the database with matching gematria sum. Parameters: - gematria_sum (int): The gematria sum to match. - db_file (str): Path to the database file. Returns: - dict or None: Dictionary with match details or None if no match found. """ logger.debug(f"Finding shortest sura match for gematria sum: {gematria_sum}") try: with sqlite3.connect(db_file) as conn: cursor = conn.cursor() cursor.execute(''' SELECT words, book, chapter, verse FROM results WHERE gematria_sum = ? ORDER BY LENGTH(words) ASC LIMIT 1 ''', (gematria_sum,)) result = cursor.fetchone() if result: logger.debug(f"Shortest sura match found: {result}") return { "words": result[0], "book": result[1], "chapter": result[2], "verse": result[3] } logger.debug("No matching sura found.") return None except sqlite3.Error as e: logger.error(f"Database error: {e}") return None def create_quran_display_iframe(sura, verse=None): """ Creates an iframe HTML string for Quran.com. Parameters: - sura (str or int): The sura name or number. - verse (str or int, optional): The verse number. Returns: - str: An iframe HTML string for displaying the specified Quran passage. """ # Format URL based on parameters if verse: url = f"https://quran.com/{sura}/{verse}" else: url = f"https://quran.com/{sura}" # Create iframe HTML iframe = f'' return iframe def initialize_quran_database(db_file='abjad.db', max_phrase_length=3): """ Initialize the database with Quran verses and their gematria sums. Parameters: - db_file (str): Path to the database file. - max_phrase_length (int): Maximum number of words to include in phrases. Returns: - bool: True if initialization was successful, False otherwise. """ logger.info(f"Initializing Quran database {db_file} with max phrase length {max_phrase_length}") try: # Create database and tables if they don't exist with sqlite3.connect(db_file) as conn: cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS results ( id INTEGER PRIMARY KEY AUTOINCREMENT, book TEXT, chapter INTEGER, verse INTEGER, words TEXT, gematria_sum INTEGER ) ''') # Check if there are already Quran entries in the database cursor.execute("SELECT COUNT(*) FROM results WHERE book != 'Psalms'") count = cursor.fetchone()[0] if count > 0: logger.info(f"Database already contains {count} Quran entries. Skipping initialization.") return True # Process Quran files logger.info("Processing Quran files...") quran_base_path = "texts/quran" # Get list of all Quran JSON files quran_files = [] try: for filename in os.listdir(quran_base_path): if filename.endswith('.json'): quran_files.append(os.path.join(quran_base_path, filename)) except FileNotFoundError: logger.error(f"Quran texts directory not found: {quran_base_path}") return False # Process each sura file for file_path in sorted(quran_files): try: with open(file_path, 'r', encoding='utf-8') as f: sura_data = json.load(f) # Extract sura details sura_name = sura_data.get('name', os.path.basename(file_path)) sura_number = int(os.path.basename(file_path).split('.')[0]) verses = sura_data.get('text', []) logger.debug(f"Processing Sura {sura_number}: {sura_name} with {len(verses)} verses") # Process each verse for verse_idx, verse_text in enumerate(verses, 1): if not verse_text: continue # Split verse into words words = verse_text.split() # Generate phrases of different lengths for length in range(1, min(max_phrase_length + 1, len(words) + 1)): for i in range(len(words) - length + 1): phrase = ' '.join(words[i:i+length]) # Calculate gematria value gematria_sum = calculate_gematria(strip_diacritics(phrase.lower())) # Skip if gematria sum is 0 if gematria_sum == 0: continue # Insert into database cursor.execute(''' INSERT INTO results (book, chapter, verse, words, gematria_sum) VALUES (?, ?, ?, ?, ?) ''', (sura_name, sura_number, verse_idx, phrase, gematria_sum)) except Exception as e: logger.error(f"Error processing {file_path}: {str(e)}") # Commit changes conn.commit() # Count total entries cursor.execute("SELECT COUNT(*) FROM results WHERE book != 'Psalms'") count = cursor.fetchone()[0] logger.info(f"Successfully initialized Quran database with {count} entries") return True except sqlite3.Error as e: logger.error(f"Database error during initialization: {str(e)}") return False except Exception as e: logger.error(f"Error initializing database: {str(e)}") return False