Source

noor / noor / quran.py

Full commit
import os.path
import sys
import xml.sax.handler

import noor.utils


class Quran(object):

    def __init__(self, text, replacements=[]):
        self._init_data()
        self.text = text
        self.replacements = replacements

    def _init_data(self):
        datafile = os.path.join(_get_data_path(), 'quran-data.xml')
        handler = _DataHandler()
        xml.sax.parse(datafile, handler)
        self.data = handler.result

    @property
    @noor.utils.cacheit
    def suras(self):
        result = []
        for index, sura in sorted(self.data['sura'].items()):
            result.append(Sura(self, sura))
        return result

    def _sura_ayas(self, sura):
        lineno = int(sura.data['start'])
        ayas = int(sura.data['ayas'])
        start = self._find_line_start(lineno)
        end = self._find_line_start(ayas, initial=start) - 1
        text = self.text[start:end]
        for pattern, goal in self.replacements:
            text = text.replace(pattern, goal)
        sura_ayas = text.splitlines()
        if sura.number != 1:
            self._fix_extra_in_the_name(sura_ayas)
        return sura_ayas

    def _fix_extra_in_the_name(self, sura_ayas):
        if not self.suras:
            return
        in_the_name = self.get_in_the_name()
        if sura_ayas[0].startswith(in_the_name):
            sura_ayas[0] = sura_ayas[0][len(in_the_name):]

    def _find_line_start(self, lineno, initial=0):
        index = initial
        for i in range(lineno):
            try:
                index = self.text.index('\n', index) + 1
            except ValueError:
                return len(self.text)
        return index

    def get_in_the_name(self):
        result = self.suras[0].ayas[0]
        if ord(result[0]) == 65279:
            return result[1:]
        return result

    def _sura_juz(self, sura):
        number = sura.number
        result = []
        for index, start in enumerate(self.juz_starts):
            if start[0] < number:
                continue
            if start[0] == number:
                if not result and start[1] > 1:
                    result.append(index)
                result.append(index + 1)
            if start[0] > number:
                if not result:
                    result.append(index)
                break
        if not result:
            result.append(30)
        return result

    @property
    @noor.utils.cacheit
    def juz_starts(self):
        result = []
        for index, juz in sorted(self.data['juz'].items()):
            result.append((int(juz['sura']), int(juz['aya'])))
        return result

    @property
    @noor.utils.cacheit
    def sajdas(self):
        result = {}
        for index, sajda in sorted(self.data['sajda'].items()):
            kind = 'major'
            if sajda['type'] == 'recommended':
                kind = 'minor'
            result[(int(sajda['sura']), int(sajda['aya']))] = kind
        return result


class Sura(object):

    def __init__(self, quran, data):
        self.quran = quran
        self.data = data

    @property
    @noor.utils.cacheit
    def juz(self):
        return self.quran._sura_juz(self)

    @property
    @noor.utils.cacheit
    def ayas(self):
        return self.quran._sura_ayas(self)

    @property
    def number(self):
        return int(self.data['index'])

    @property
    def name(self):
        return self.data['name']


def _get_data_path():
    noordir = os.path.dirname(sys.modules['noor'].__file__)
    return os.path.join(noordir, 'data')

def quran_from_path(path=None, **kwds):
    if path is None:
        path = os.path.join(_get_data_path(), 'quran-text.txt')
    quran_file = open(path, 'rb')
    data = quran_file.read()
    quran_file.close()
    return Quran(data.decode('utf-8'), **kwds)

def get_aya(quran, sura, aya):
    return quran.suras[sura - 1].ayas[aya - 1]

class _DataHandler(xml.sax.handler.ContentHandler):

    def __init__(self):
        self.result = {}

    def startElement(self, name, attrs):
        attrs = dict(attrs)
        if 'index' in attrs:
            values = self.result.setdefault(name, {})
            values[int(attrs['index'])] = attrs