pax_global_header00006660000000000000000000000064130526275520014521gustar00rootroot0000000000000052 comment=432ac6140e7ace0c809abfebc4c53e062b939d99 thawab-4.1/000077500000000000000000000000001305262755200126335ustar00rootroot00000000000000thawab-4.1/Makefile000066400000000000000000000073661305262755200143070ustar00rootroot00000000000000APPNAME=thawab DESTDIR?=/ DATADIR?=$(DESTDIR)/usr/share SOURCES=$(wildcard *.desktop.in) TARGETS=${SOURCES:.in=} ECHO := echo MAKE := make PYTHON := python2 INSTALL := install INTLTOOL_MERGE := intltool-merge RM := $(shell which rm | egrep '/' | sed 's/\s//g') GTK_UPDATE_ICON_CACHE := $(shell which gtk-update-icon-cache) UPDATE_DESKTOP_DATABASE := $(shell which update-desktop-database) all: $(TARGETS) icons icons: @for i in 96 72 64 48 36 32 24 22 16; do \ convert -background none $(APPNAME).svg -resize $${i}x$${i} $(APPNAME)-$${i}.png; \ done pos: $(MAKE) -C po all install: locale @$(ECHO) "*** Installing..." @$(PYTHON) setup.py install -O2 --root $(DESTDIR) @$(ECHO) "Copying: $(APPNAME).desktop -> $(DATADIR)/applications/" @$(INSTALL) -d $(DATADIR)/applications/ @$(INSTALL) -d $(DATADIR)/$(APPNAME)/ @$(INSTALL) -m 0644 $(APPNAME).desktop $(DATADIR)/applications/ @$(INSTALL) -m 0644 -D $(APPNAME).svg $(DATADIR)/icons/hicolor/scalable/apps/$(APPNAME).svg; @for i in 96 72 64 48 36 32 24 22 16; do \ $(INSTALL) -d $(DATADIR)/icons/hicolor/$${i}x$${i}/apps; \ $(INSTALL) -m 0644 -D $(APPNAME)-$${i}.png $(DATADIR)/icons/hicolor/$${i}x$${i}/apps/$(APPNAME).png; \ done @$(RM) -rf build @$(DESTDIR)/$(UPDATE_DESKTOP_DATABASE) --quiet $(DATADIR)/applications &> /dev/null || : @$(DESTDIR)/$(GTK_UPDATE_ICON_CACHE) --quiet $(DATADIR)/icons/hicolor &> /dev/null || : uninstall: @$(ECHO) "*** Uninstalling..." @$(ECHO) "- Removing: $(DATADIR)/applications/$(APPNAME).desktop" @$(RM) -f $(DATADIR)/applications/$(APPNAME).desktop @$(ECHO) "- Removing: $(DESTDIR)/usr/share/locale/*/LC_MESSAGES/$(APPNAME).mo" @$(RM) -f $(DESTDIR)/usr/share/locale/*/LC_MESSAGES/$(APPNAME).mo @$(ECHO) "- Removing: $(DESTDIR)/usr/bin/$(APPNAME)" @$(RM) -f $(DESTDIR)/usr/bin/$(APPNAME)-gtk @$(RM) -f $(DESTDIR)/usr/bin/$(APPNAME)-server @$(ECHO) "- Removing: $(DESTDIR)/usr/lib/python*/*-packages/Thawab" @$(RM) -rf $(DESTDIR)/usr/lib/python*/*-packages/Thawab @$(ECHO) "- Removing: $(DESTDIR)/usr/lib/python*/*-packages/$(APPNAME)*" @$(RM) -rf $(DESTDIR)/usr/lib/python*/*-packages/$(APPNAME)* @$(ECHO) "- Removing: $(DESTDIR)/usr/share/$(APPNAME)" @$(RM) -rf $(DESTDIR)/usr/share/$(APPNAME) @$(ECHO) "- Removing: $(DESTDIR)/usr/*/share/locale/*/LC_MESSAGES/$(APPNAME).mo" @$(RM) -f $(DESTDIR)/usr/*/share/locale/*/LC_MESSAGES/$(APPNAME).mo @$(ECHO) "- Removing: $(DESTDIR)/usr/*/bin/$(APPNAME)" @$(RM) -f $(DESTDIR)/usr/*/bin/$(APPNAME)-gtk @$(RM) -f $(DESTDIR)/usr/*/bin/$(APPNAME)-server @$(ECHO) "- Removing: $(DESTDIR)/usr/*/lib/python*/*-packages/Thawab" @$(RM) -rf $(DESTDIR)/usr/*/lib/python*/*-packages/Thawab @$(ECHO) "- Removing: $(DESTDIR)/usr/*/lib/python*/*-packages/$(APPNAME)*" @$(RM) -rf $(DESTDIR)/usr/*/lib/python*/*-packages/$(APPNAME)* @$(ECHO) "- Removing: $(DESTDIR)/usr/*/share/$(APPNAME)" @$(RM) -rf $(DESTDIR)/usr/*/share/$(APPNAME) @$(RM) -f $(DATADIR)/icons/hicolor/scalable/apps/$(APPNAME).svg @$(RM) -f $(DATADIR)/icons/hicolor/*/apps/$(APPNAME).png; @$(DESTDIR)/$(UPDATE_DESKTOP_DATABASE) --quiet $(DATADIR)/applications &> /dev/null || : @$(DESTDIR)/$(GTK_UPDATE_ICON_CACHE) --quiet $(DATADIR)/icons/hicolor &> /dev/null || : %.desktop: %.desktop.in pos intltool-merge -d po $< $@ clean: @$(ECHO) "*** Cleaning..." @$(MAKE) -C po clean @$(ECHO) "- Removing: $(TARGETS)" @$(RM) -f $(TARGETS) @$(ECHO) "- Removing: locale build" @$(RM) -rf locale build @$(ECHO) "- Removing: *.pyc" @$(RM) -f *.pyc @$(ECHO) "- Removing: */*.pyc" @$(RM) -f */*.pyc @$(ECHO) "- Removing: $(APPNAME)-*.png" @$(RM) -f $(APPNAME)-*.png @$(ECHO) "- Removing Cache directories" @$(RM) -f thawab-data/user.db @$(RM) -rf thawab-data/cache @$(RM) -rf thawab-data/index @$(RM) -rf thawab-data/tmp @$(RM) -rf thawab-data/db @$(RM) -rf thawab-data/conf thawab-4.1/TODO000066400000000000000000000003621305262755200133240ustar00rootroot00000000000000بسم الله الرحمن الرحيم - fix indixing after clear cache - fix indexing repete jobs in current session ( reload search cache ) - add close button to chlid windows ( dialogs ) ....... Done - fix tabs title - fix FIXME lines thawab-4.1/Thawab/000077500000000000000000000000001305262755200140415ustar00rootroot00000000000000thawab-4.1/Thawab/__init__.py000066400000000000000000000000001305262755200161400ustar00rootroot00000000000000thawab-4.1/Thawab/asyncIndex.py000066400000000000000000000070231305262755200165220ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The async threaded indexing class of thawab Copyright © 2010, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ from Queue import Queue from threading import Thread, Lock from time import sleep class AsyncIndex(): def __init__(self, searchEngine, queueSize = 0, workers = 1): """ if number of workers>1 then queued jobs need not be executed in order """ self.searchEngine = searchEngine self.workers_n = workers self.running = 0 self.lock = Lock() # used to report running tasks correctly self._q = Queue(queueSize) self.start() # we enqueue jobs like this #for item in source(): self._q.put(item) def queueIndexNew(self): """ index all non-indexed """ self.searchEngine.indexingStart() for n in self.searchEngine.th.getKitabList(): vr = self.searchEngine.getIndexedVersion(n) if not vr: self.queue("indexKitab", n) def queue(self, method, *args, **kw): """ examples: queue("indexNew"); queue("indexKitab","kitab_name"); """ self._q.put((method, args, kw)) def start(self): self.keepworking = True self.end_when_done = False self.started = False # here we create our thread pool of workers for i in range(self.workers_n): t = Thread(target = self._worker) t.setDaemon(True) t.start() # sleep to make sure all threads are waiting for jobs (inside loop) while not self.started: sleep(0.25) def jobs(self, with_running = True): """ return number of queued jobs. """ if with_running: return self._q.qsize()+self.running else: return self._q.qsize() def join(self): """ block till queued jobs are done. """ return self._q.join() def cancelQueued(self): self.keepworking = False self._q.join() self.started = False def endWhenDone(self): self.end_when_done = True self._q.join() self.started = False def _worker(self): while self.keepworking: self.started = True # get a job from queue or block sleeping till one is available item = self._q.get(not self.end_when_done) if item: self.lock.acquire() self.running += 1 self.lock.release() method, args, kw = item f = getattr(self.searchEngine, method) f(*args,**kw) if self._q.qsize() == 0: self.searchEngine.indexingEnd() self._q.task_done() self.lock.acquire() self.running -= 1 self.lock.release() elif self._q.empty(): if self.end_when_done: self.keepworking = False thawab-4.1/Thawab/baseSearchEngine.py000066400000000000000000000226601305262755200176070ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Copyright © 2009, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ from meta import metaVrr from okasha.utils import strverscmp from tags import * # TODO: use flags in meta cache object to indicate if indexing was started for some kitab so that if something wrong happend while indexing we can drop index of that kitab class BaseSearchEngine: def __init__(self, th, multithreading = False): self.th = th self.multithreading = multithreading def getIndexedVersion(self, name): """ return a Version-Release string if in index, otherwise return None """ raise NotImplementedError def queryIndex(self, queryString): """ return an interatable of fields dict this method must be overridden in implementation specific way """ raise NotImplementedError def indexingStart(self): """ should be called before any sequence of indexing Ops, reindexAll() calls this method automatically """ pass def indexingEnd(self): """ should be called after a sequence of indexing Ops, reindexAll() calls this method automatically """ pass def reload(self): """ called after commiting changes to index (eg. adding or dropping from index) """ pass def dropKitabIndex(self, name): """ drop search index for a given Kitab name you need to call indexingStart() before this and indexingEnd() after it this method must be overridden in implementation specific way """ raise NotImplementedError def addDocumentToIndex(self, name, vrr, nodeIdNum, title, content, tags): """ this method must be overridden in implementation specific way """ raise NotImplementedError def dropAll(self): raise NotImplementedError # NOTE: the following implementation is buggy, since there could be documents index but no longer exists #t = [] #self.indexingStart() #for i in self.th.getManagedUriList(): self.dropKitabIndex(i) #self.indexingEnd() def dropChanged(self): """ drop index for all indexed kutub that got changed (updated or downgraded) this is useful if followed by indexNew no need you need to call indexingStart() indexingEnd() around this """ self.indexingStart() m = self.th.getMeta() for n in self.th.getKitabList(): vr = self.getIndexedVersion(n) if vr and vr != metaVrr(m.getLatestKitab(n)): self.dropKitabIndex(n) self.indexingEnd() def dropOld(self): """ drop index for all indexed kutub that got updated this is useful if followed by indexNew no need you need to call indexingStart() indexingEnd() around this """ self.indexingStart() m = self.th.getMeta() for n in self.th.getKitabList(): vr = self.getIndexedVersion(n) if vr and strverscmp(vr,metaVrr(m.getLatestKitab(n))) > 0: self.dropKitabIndex(n) self.indexingEnd() def indexNew(self): """ index all non-indexed no need to call indexingStart() indexingEnd() around this """ self.indexingStart() for n in self.th.getKitabList(): vr = self.getIndexedVersion(n) if not vr: self.indexKitab(n) self.indexingEnd() def refresh(self): """ drop changed then index them along with new unindexed. no need to call indexingStart() indexingEnd() around this """ self.dropChanged() self.indexNew() def reindexAll(self): """ no need to call indexingStart() indexingEnd() around this """ self.dropAll() # FIXME: should be dropAll() then usual index not reindex t = [] self.indexingStart() for n in self.th.getKitabList(): self.indexKitab(n) # if threading is supported by indexer it would look like #if self.multithreading: # for i in self.getManagedUriList(): # t.append(threading.Thread(target=self.indexKitab,args=(i,))) # t[-1].start() # for i in t: i.join() self.indexingEnd() def reindexKitab(self, name): """ you need to call indexingStart() before this and indexingEnd() after it """ self.dropKitabIndex(name) self.indexKitab(name) def __ix_nodeStart(self, node, name, vrr, iix): # NOTE: benchmarks says append then join is faster than s += "foo" tags = node.getTags() tag_flags = node.getTagFlags() # create new consuming main indexing fields [ie. headers] # TODO: let loadToc use TAG_FLAGS_HEADER instead of hard-coding 'header' #if node.getTagsByFlagsMask(TAG_FLAGS_HEADER): # NOTE: for consistency, header is the only currentely allowed tag having TAG_FLAGS_HEADER if tag_flags & TAG_FLAGS_HEADER: iix.main_f_node_idnums.append(node.idNum) iix.main_f_content_index.append(len(iix.contents)) iix.main_f_tags_index.append(len(iix.tags)) # create new sub non-consuming indexing fields if tag_flags & TAG_FLAGS_IX_FIELD: iix.sub_f_node_idnums.append(node.idNum) iix.sub_f_content_index.append(len(iix.contents)) iix.sub_f_tags_index.append(len(iix.tags)) # TODO: check for nodes that are not supposed to be indexed TAG_FLAGS_IX_SKIP # append ix contents iix.contents.append(node.getContent()) # TODO: append extra padding space if TAG_FLAGS_PAD_CONTENT # append ix tags iix.tags.extend(map(lambda t: tags[t] == None and t or u'.'.join((t,tags[t])), node.getTagsByFlagsMask(TAG_FLAGS_IX_TAG))) def __ix_nodeEnd(self, node, name, vrr, iix): # index extra sub fields if any if iix.sub_f_node_idnums and iix.sub_f_node_idnums[-1] == node.idNum: n = iix.sub_f_node_idnums.pop() i = iix.sub_f_content_index.pop() j = iix.sub_f_tags_index.pop() c = u"".join(iix.contents[i:]) T = u" ".join(iix.tags[j:]) del iix.tags[j:] k = iix.main_f_content_index[-1] # the nearest header title index N = iix.main_f_node_idnums[-1] # the nearest header node.idNum # NOTE: the above two lines means that a sub ix fields should be children of some main field (header) t = iix.contents[k] self.addDocumentToIndex(unicode(name), vrr, N, t, c, T) # index consuming main indexing fields if any if iix.main_f_node_idnums and iix.main_f_node_idnums[-1] == node.idNum: n = iix.main_f_node_idnums.pop() i = iix.main_f_content_index.pop() j = iix.main_f_tags_index.pop() t = iix.contents[i] c = (u"".join(iix.contents[i:])).strip() del iix.contents[i:] T = u" ".join(iix.tags[j:]) del iix.tags[j:] self.addDocumentToIndex(unicode(name), vrr, n, t.strip(), c, T) class __IIX(object): "internal indexing object" def __init__(self): # independent arrays self.contents = [] # array of contents to be indexed self.tags = [] # array of ix tags # main_f* parallel arrays self.main_f_node_idnums = [] # array of node.idNum of consuming ix fields (ie. header) self.main_f_content_index = [] # array of the starting index in self.contents for each main ix field (ie. header) self.main_f_tags_index = [] # array of the starting index in self.contents for each main ix field (ie. header) # sub_f* parallel arrays self.sub_f_node_idnums = [] # array of node.idNum for each sub ix field self.sub_f_content_index = [] # array of the starting index in self.contents for each sub ix field self.sub_f_tags_index = [] # array of the starting index in self.tags for each sub ix field # TODO: benchmark which is faster parallel arrays or small tubles sub_field = (idNum,content_i,tag_i) def indexKitab(self, name): """ create search index for a given Kitab name NOTE: you need to call indexingStart() before this and indexingEnd() after it """ #print "creating index for kitab with name:", name ki = self.th.getKitab(name) self.th.getMeta().setIndexedFlags(ki.uri, 1) vrr = metaVrr(ki.meta) iix = self.__IIX() ki.root.traverser(3, self.__ix_nodeStart, self.__ix_nodeEnd, name, vrr, iix) self.th.getMeta().setIndexedFlags(ki.uri, 2) thawab-4.1/Thawab/core.py000066400000000000000000001173641305262755200153570ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The core classes of thawab Copyright © 2008, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path, sqlite3, re import threading from glob import glob from itertools import imap,groupby from tempfile import mkstemp from StringIO import StringIO from xml.sax.saxutils import escape, unescape, quoteattr # for xml rendering from dataModel import * from tags import * from meta import MCache, metaDict2Hash, prettyId, makeId, metaVrr from userDb import UserDb from platform import guess_prefixes from whooshSearchEngine import SearchEngine from asyncIndex import AsyncIndex from othman.core import othmanCore from okasha.utils import ObjectsCache, fromFs, toFs th_ext = u'.ki' th_ext_glob = u'*.ki' othman = othmanCore() class ThawabMan (object): def __init__(self, prefixes=None, isMonolithic = True, indexerQueueSize = 0): """Create a new Thawab instance given a user writable directory and an optional system-wide read-only directory prefixes a list of directories all are read-only except the first the first writable directory can be os.path.expanduser('~/.thawab') os.path.join([os.path.dirname(sys.argv[0]),'..','data']) isMonolithic = True if we should use locks and reconnect to sqlite indexerQueueSize is the size of threaded index queue (0 infinite, -1 disabled) the first thing you should do is to call loadMCache() """ if not prefixes: prefixes = guess_prefixes() try: if not os.path.isdir(prefixes[0]): os.makedirs(prefixes[0]) except: raise OSError self.prefixes = filter(lambda i:os.path.isdir(i), [os.path.realpath(os.path.abspath(p)) for p in prefixes]) # make sure it's unique p = self.prefixes[0] s = set(self.prefixes[1:]) if p in s: s.remove(p) if len(s)= 0: self.asyncIndexer = AsyncIndex(self.searchEngine, indexerQueueSize) else: self.asyncIndexer = None self.isMonolithic = isMonolithic if not self.isMonolithic: import threading lock1 = threading.Lock() else: lock1 = None self.kutubCache = ObjectsCache(lock = lock1) def prase_conf(self): r = {} fn = os.path.join(self.prefixes[0], 'conf', 'main.txt') if not os.path.exists(fn): return {} try: f = open(fn) t = f.readlines() f.close() except: return {} for l in t: a = l.strip().split(" = ",1) if len(a) != 2: continue r[a[0].strip()] = a[1].strip() return r def assertManagedTree(self): """create the hierarchy inside the user-managed prefix # db contains Kitab files [.thawab] # index contains search index # conf application configuration # cache contains the metadata cache for all containers""" P = self.prefixes[0] if not os.access(P, os.W_OK): return False for i in ['db','index','conf','cache', 'tmp', 'themes']: p = os.path.join(P,i) if not os.path.isdir(p): os.makedirs(p) return True def mktemp(self): h, fn = mkstemp(th_ext, 'THAWAB_' ,os.path.join(self.prefixes[0], 'tmp')) return Kitab(fn, True) def getCachedKitab(self, uri): """ try to get a kitab by uri from cache, if it's not in the cache, it will be opened and cached """ ki = self.kutubCache.get(uri) if not ki: ki = self.getKitabByUri(uri) if ki: self.kutubCache.append(uri, ki) #elif not self.isMonolithic: ki.connect() # FIXME: no longer needed, kept to trace other usage of isMonolithic return ki def getCachedKitabByNameV(self, kitabNameV): a = kitabNameV.split(u'-') l = len(a) if l == 1: m = self.getMeta().getLatestKitab(kitabNameV) elif l == 2: m = self.getMeta().getLatestKitabV(*a) else: m = self.getMeta().getLatestKitabVr(*a) if m: return self.getCachedKitab(m['uri']) return None def getUriByKitabName(self,kitabName): """ return uri for the latest kitab with the given name """ m = self.getMeta().getLatestKitab(kitabName) if not m: return None return m['uri'] def getKitab(self,kitabName): m = self.getMeta().getLatestKitab(kitabName) if m: return Kitab(m['uri'], th = self, meta = m) return None def getKitabByUri(self,uri): m = self.getMeta().getByUri(uri) if m: return Kitab(uri, th = self, meta = m) return Kitab(uri, th=self) def getKitabList(self): """ return a list of managed kitab's name """ return self.getMeta().getKitabList() def getManagedUriList(self): """list of all managed uri (absolute filenames for a Kitab) this is low level as the user should work with kitabName, title, and rest of meta data""" if self.__meta: return self.__meta.getUriList() r = [] for i in self.prefixes: a = glob(toFs(os.path.join(fromFs(i),u'db',th_ext_glob))) p = map(lambda j: fromFs(j), a) r.extend(p) return r def getMeta(self): if not self.__meta: self.loadMeta() return self.__meta def loadMeta(self): self.__meta = None p = os.path.join(self.prefixes[0],'cache','meta.db') self.__meta = MCache(p, self.getManagedUriList()) return self.__meta def reconstructMetaIndexedFlags(self): # NOTE: getMeta is not used because we want to make sure we are using a fresh one m = self.loadMeta() l1 = m.getIndexedList() l2 = m.getUnindexedList() # NOTE: Dirty are kept as is #l3 = m.getDirtyIndexList() for i in l1: v = self.searchEngine.getIndexedVersion(i['kitab']) # mark as unindexed if not v or metaVrr(i) != v: m.setIndexedFlags(i['uri'], 0) for i in l2: v = self.searchEngine.getIndexedVersion(i['kitab']) if v and metaVrr(i) == v: # mark as indexed if same version m.setIndexedFlags(i['uri']) class KitabCursor: """ an object used to do a sequence of SQL operation """ def __init__(self, ki , *args, **kw): self.ki = ki self.__is_tailing = False self.__is_tmp = False self.__tmp_str = '' self.__parents=[] self.__c = None self.__last_go = -1 if args or kw: self.seek(*args, **kw) def __lock(self): # TODO: this is just a place holders, could be used to do "BEGIN TRANS" pass def __unlock(self): pass def seek(self, parentNodeIdNum = -1,nodesNum = -1): """ should be called before concatenating nodes, all descendants will be dropped where: parentNodeIdNum - the parent below which the concatenation will begin, -1 at the tail nodesNum - number of nodes to be concatenated, -1 for unknown open number seek() appendNode(parentNodeIdNum, content, tags) appendNode(parentNodeIdNum, content, tags) ... plush() """ self.__lock() self.__is_tailing = False self.__is_tmp = False self.__tmp_str = '' self.__parents = [] self.__c = self.ki.cn().cursor() self.__c.execute('BEGIN TRANSACTION') if parentNodeIdNum != -1: self.dropDescendants(parentNodeIdNum) if nodesNum == -1: self.__is_tmp = True self.__tmp_str = 'tmp' else: # FIXME: make sure raise IndexError, "not implented" else: self.__parents = [self.ki.root] r = self.__c.execute(SQL_GET_LAST_GLOBAL_ORDER).fetchone() if r: self.__last_go = r[0] else: self.__last_go = 0 self.__is_tailing = True def flush(self): """Called after the last appendNode""" if self.__is_tmp: # TODO: implement using "insert into ... select tmp_nodes ...;" raise IndexError, "not implented" self.__c.execute('END TRANSACTION') #self.__c.execute('COMMIT') #self.ki.cn.commit() # FIXME: is this needed ? self.__unlock() def appendNode(self, parentNode, content, tags): parentNodeIdNum = parentNode.idNum while(self.__parents[-1].idNum != parentNodeIdNum): self.__parents.pop() new_go = self.__last_go + self.ki.inc_size newid = self.__c.execute(SQL_APPEND_NODE[self.__is_tmp], (content, self.__parents[-1].idNum, new_go, self.__parents[-1].depth + 1)).lastrowid self.__last_go = new_go node = Node(kitab = self.ki, idNum = newid, parent = self.__parents[-1].idNum, depth = self.__parents[-1].depth + 1) node.applyTags(tags) self.__parents.append(node) return node def dropDescendants(self,parentNodeIdNum, withParent = False): """remove all child nodes going deep at any depth, and optionally with their parent""" o1, o2 = self.ki.getSliceBoundary(parentNodeIdNum) c = self.__c if not c: c = self.ki.cn().cursor() if o2 == -1: c.execute(SQL_DROP_TAIL_NODES[withParent],(o1,)) else: c.execute(SQL_DROP_DESC_NODES[withParent],(o1,o2)) class Kitab(object): """this class represents a book or an article ...etc.""" def __init__(self,uri, is_tmp = False, th = None, meta = None): """ open the Kitab pointed by uri (or try to create it) is_tmp should be set to True when we are creating a new kitab from scratch in temporary th is ThawabManaget to which this book belongs meta is meta cache entry of this kitab Note: don't rely on meta having uri, mtime, flags unless th is set (use uri property instead) """ self._cn_h = {} # per-thread sqlite connection # node generators self.grouped_rows_to_node = (self.grouped_rows_to_node0, self.grouped_rows_to_node1, self.grouped_rows_to_node2, self.grouped_rows_to_node3) self.row_to_node = (self.row_to_node0, self.row_to_node1) # TODO: do we need a mode = r|w ? self.uri = uri self.is_tmp = is_tmp self.th = th self.meta = meta if not meta: self.getMCache() if meta and meta.get('originalKitab',None): self.originalKi = self.th.getCachedKitabByNameV(meta['originalKitab'] + \ u"-" + \ meta['originalVersion']) else: self.originalKi = None # the logic to open the uri goes here # check if fn exists, if not then set the flag sql_create_schema if is_tmp or not os.path.exists(toFs(uri)): sql_create_schema = True else: sql_create_schema = False cn = self.cn() # FIXME: do we really need this cn.create_function("th_enumerate", 0, self.rowsEnumerator) # NOTE: we have a policy, no saving of cursors in object attributes for thread safty c = cn.cursor() self.toc = KitabToc(self) # private self.__tags = {} # a hash by of tags data by tag name self.__tags_loaded = False self.__counter = 0 # used to renumber rows self.inc_size = 1 << 10 # TODO: make a decision, should the root node be saved in SQL, # if so a lower bound checks to Kitab.getSliceBoundary() and an exception into Kitab.getNodeByIdNum() self.root = Node(kitab = self, idNum = 0, parent = -1, depth = 0, content = '', tags = {}) if sql_create_schema: c.executescript(SQL_DATA_MODEL) # create standard tags for t in STD_TAGS_ARGS: c.execute(SQL_ADD_TAG, t) def cn(self): """ return an sqlite connection for the current thread """ n = threading.current_thread().name if self._cn_h.has_key(n): r = self._cn_h[n] else: r = sqlite3.connect(self.uri, isolation_level = None) self._cn_h[n] = r return r def getMCache(self): if not self.th: return None # needs a manager if self.meta: return self.meta self.meta = self.th.getMeta().load_from_uri(self.uri) return self.meta def setMCache(self, meta): # TODO: add more checks a = meta.get('author', None) oa = meta.get('originalAuthor', None) if not oa and not a: meta['author'] = '_unset' if not a and oa != None: meta['author'] = oa if not oa and a != None: meta['originalAuthor'] = a y = meta.get('year', None) oy = meta.get('originalYear', None) if not y and oy != None: meta['year'] = oy if not oy and y != None: meta['originalYear'] = y if not meta.get('cache_hash',None): meta['cache_hash'] = metaDict2Hash(meta) self.meta = meta self.cn().execute(SQL_MCACHE_SET, meta) ################################### # retrieving data from the Kitab ################################### def getTags(self): if not self.__tags_loaded: self.reloadTags() return self.__tags def reloadTags(self): self.__tags = dict(map(lambda r: (r[0],r[1:]), self.cn().execute(SQL_GET_ALL_TAGS).fetchall())) self.__tags_loaded = True def getNodeByIdNum(self, idNum, load_content = False): if idNum <= 0: return self.root r = self.cn().execute(SQL_GET_NODE_BY_IDNUM[load_content], (idNum,)).fetchone() if not r: raise IndexError, "idNum not found" return self.row_to_node[load_content](r) def getNodesByTagValueIter(self, tagname, value, load_content = True, limit = 0): """an iter that retrieves all the modes tagged with tagname having value""" sql = SQL_GET_NODES_BY_TAG_VALUE[load_content] if type(limit) == int and limit > 0: sql + " LIMIT " + str(limit) it = self.cn().execute(sql, (tagname, value,)) return imap(self.row_to_node[load_content], it) def nodeFromId(self, i, load_content = False): """ get node from Id where is is one of the following: * an intger (just call getNodeByIdNum) * a string prefixed with "_i" followed by IdNum * the value of "header" param """ if type(i) == int: j = i return self.getNodeByIdNum(j, load_content) elif i.startswith('_i'): try: j = int(i[2:]) except TypeError: return None return self.getNodeByIdNum(j, load_content) else: nodes = self.getNodesByTagValueIter("header", i, load_content, 1) if nodes: return nodes[0] return None def seek(self, *args, **kw): """ short hand for creating a cursor object and seeking it, returns a new cursor object used for manipulation ops """ return KitabCursor(self, *args, **kw) def getSliceBoundary(self, nodeIdNum): """return a tuble of o1,o2 where: o1: is the globalOrder of the given Node o2: is the globalOrder of the next sibling of the given node, -1 if unbounded all the descendants of the given nodes have globalOrder belongs to the interval (o1,o2) """ # this is a private method used by dropDescendants if nodeIdNum == 0: return 0,-1 cn = self.cn() r = cn.execute(SQL_GET_GLOBAL_ORDER,(nodeIdNum,)).fetchone() if not r: raise IndexError o1 = r[0] depth = r[1] r = cn.execute(SQL_GET_DESC_UPPER_BOUND, (o1, depth)).fetchone() if not r: o2 = -1 else: o2 = r[0] return o1, o2 # node generators def row_to_node0(self,r): return Node(kitab=self, idNum = r[0], parent = r[1], depth = r[2], globalOrder = r[3]) def row_to_node1(self,r): return Node(kitab=self, idNum = r[0], parent = r[1], depth = r[2], globalOrder = r[3], content = r[4]) def grouped_rows_to_node0(self,l): r = list(l[1]) return Node(kitab=self, idNum = r[0][0], parent = r[0][1], depth = r[0][2], globalOrder = r[0][3]) def grouped_rows_to_node1(self,l): r = list(l[1]) return Node(kitab=self, idNum = r[0][0], parent = r[0][1], depth = r[0][2], globalOrder = r[0][3], content = r[0][4]) def grouped_rows_to_node2(self,l): r = list(l[1]) return Node(kitab=self.kitab, idNum = r[0][0], parent = r[0][1], depth = r[0][2], globalOrder = r[0][3], tags = dict(map(lambda i: (i[4],i[5]),r)), tag_flags = reduce(lambda a,b: a|b[6],r,0)) def grouped_rows_to_node3(self,l): r = list(l[1]) return Node(kitab=self, idNum = r[0][0], parent = r[0][1], depth = r[0][2], globalOrder = r[0][3], content = r[0][4], tags = dict(map(lambda i: (i[5],i[6]),r)), tag_flags = reduce(lambda a,b: a|b[7],r, 0)) def getChildNodesIter(self, idNum, preload = WITH_CONTENT_AND_TAGS): """ an iter that retrieves all direct children of a node by its IdNum, just one level deeper, content and tags will be pre-loaded by default. where preload can be: 0 WITH_NONE 1 WITH_CONTENT 2 WITH_TAGS 3 WITH_CONTENT_AND_TAGS """ it = self.cn().execute(SQL_GET_CHILD_NODES[preload],(idNum,)) # will work but having the next "if" is faster # return imap(self.grouped_rows_to_node[preload], groupby(it,lambda i:i[0])) if preload & 2: return imap(self.grouped_rows_to_node[preload], groupby(it,lambda i:i[0])) return imap(self.row_to_node[preload], it) def getTaggedChildNodesIter(self, idNum, tagName, load_content = True): """ an iter that retrieves all direct children of a node having tagName by its IdNum, just one level deeper, content will be preloaded by default. """ it = self.cn().execute(SQL_GET_TAGGED_CHILD_NODES[load_content], (idNum,tagName,)) return imap(self.row_to_node[load_content], it) # FIXME: do we really need this def rowsEnumerator(self): """private method used internally""" self.__counter += self.inc_size return self.__counter class KitabToc(object): def __init__(self, kitab): self.ki = kitab def breadcrumbs(self, node): l = [] n = node p = self.ki.getNodeByIdNum(n.parent, True) while(p.idNum): # TODO: do some kind of cache like this if p.idNum in cache: l = cached + l else: ... l.insert(0, (p.idNum, p.getContent())) p = self.ki.getNodeByIdNum(p.parent, True) return l def getNodePrevUpNextChildrenBreadcrumbs(self, i): """ an optimized way to get a tuple of node, prev, up, next, children, breadcrumbs where i is nodeIdNum or preferably the node it self """ if type(i) == int: n = self.ki.getNodeByIdNum(i, True) elif isinstance(i,basestring): n = self.ki.nodeFromId(i, True) else: n = i return (n, self.prev(n), self.ki.getNodeByIdNum(n.parent, True), self.next(n), self.children(n.idNum), self.breadcrumbs(n)) def children(self, i): """ return list of Node that are direct children of i where i is idNum of the node """ return list(self.ki.getTaggedChildNodesIter(i, 'header', True)) def up(self, i): if type(i) == int: n = self.ki.getNodeByIdNum(i, True) else: n = i return self.ki.getNodeByIdNum(n.parent, True) def prev(self, i): if type(i) == int: n = self.ki.getNodeByIdNum(i, True) else: n = i return n.getPrevTaggedNode('header') def next(self, i): if type(i) == int: n = self.ki.getNodeByIdNum(i, True) else: n = i return n.getNextTaggedNode('header') class Node (object): """ A node class returned by some Kitab methods, avoid creating your own it has the following properities: kitab the Kitab instance to which this node belonds, none if floating parent the parent node idNum, -1 if root idNum the node idNum, -1 if floating or not yet saved depth the depth of node, -1 for floating, 0 for root tags the applied tags, {tagname:param,...}, None if not loaded and the following methods: getContent() return node's content, loading it from back-end if needed reloadContent() force reloading content unloadContent() unload content to save memory """ _footnote_s_re = re.compile(r'(\^\[([^\[\]]+)\])', re.M) _footnote_t_re = re.compile(r'^( \* *\(([^\(\)]+)\))', re.M) _href_named_re = re.compile(r'\[\[([^ \[\]]+) ([^\[\]]+)\]\]', re.M) _href_re = re.compile(r'\[\[([^ \[\]]+)\]\]', re.M) def __init__(self, **args): self.kitab = args.get('kitab') self.parent = args.get('parent', -1) self.idNum = args.get('idNum', -1) self.depth = args.get('depth', -1) self.globalOrder = args.get('globalOrder', -1) # TODO: should globalOrder be a properity ? try: self.__content = args['content'] self.__content_loaded = True except KeyError: self.__content_loaded = False # TODO: should tags be called tagDict try: self.__tags=args['tags'] self.__tags_loaded = True except KeyError: self.__tags_loaded = False try: self.__tag_flags = args['tag_flags'] self.__tag_flags_loaded = True except KeyError: self.__tag_flags_loaded = False # tags related methods def getTags(self): """ return tag dictionary applied to the node, loading it from back-end if needed """ if not self.__tags_loaded: self.reloadTags() return self.__tags def getTagFlags(self): """ return the "or" summation of flags of all tags applied to this node """ if not self.__tag_flags_loaded: self.reloadTags() return self.__tag_flags def getTagsByFlagsMask(self, mask): """ return tag names having flags masked with mask, used like this node.getTagsByFlagsMask(TAG_FLAGS_IX_TAG) """ # return filter(lambda t: STD_TAGS_HASH[t][2]&mask, self.getTags()) return filter(lambda t: self.kitab.getTags()[t][0]&mask, self.getTags()) def reloadTags(self): """force reloading of Tags""" self.__tags = dict(self.kitab.cn().execute(SQL_GET_NODE_TAGS,(self.idNum,)).fetchall()) self.__tags_loaded = True T = map(lambda t: self.kitab.getTags()[t][0], self.__tags.keys()) self.__tag_flags = reduce(lambda a,b: a|b,T, 0) self.__tag_flags_loaded = True def unloadTags(self): """unload content to save memory""" self.__tags_loaded = False self.__tags = None self.__tag_flags = 0 self.__tag_flags_loaded = False # content related methods def getContent(self): """return node's content, loading it from back-end if needed""" if not self.__content_loaded: self.reloadContent() return self.__content def reloadContent(self): """force reloading content""" r = self.kitab.cn().execute(SQL_GET_NODE_CONTENT,(self.idNum,)).fetchone() if not r: self.__content = None self.__content_loaded = False raise IndexError, 'node not found, could be a floating node' self.__content = r[0] self.__content_loaded = True def unloadContent(self): """unload content to save memory""" self.__content_loaded = False self.__content = None # tags editing def tagWith(self,tag,param = None): """ apply a single tag to this node, if node is already taged with it, just update the param the tag should already be in the kitab. """ r = self.kitab.cn().execute(SQL_TAG,(self.idNum,param,tag)).rowcount if not r: raise IndexError, "tag not found" def applyTags(self,tags): """ apply a set of taga to this node, if node is already taged with them, just update the param each tag should already be in the kitab. """ for k in tags: self.tagWith(k, tags[k]) def clearTags(self): """clear all tags applyed to this node""" self.kitab.cn().execute(SQL_CLEAR_TAGS_ON_NODE, (self.idNum,)) def getPrevTaggedNode(self, tagName, load_content = True): if self.idNum <= 0: return None r = self.kitab.cn().execute(SQL_GET_PREV_TAGGED_NODE[load_content], (self.globalOrder, tagName)).fetchone() if not r: return None return self.kitab.row_to_node[load_content](r) def getNextTaggedNode(self, tagName, load_content = True): r = self.kitab.cn().execute(SQL_GET_NEXT_TAGGED_NODE[load_content], (self.globalOrder, tagName)).fetchone() if not r: return None return self.kitab.row_to_node[load_content](r) # methods that give nodes def childrenIter(self, preload = WITH_CONTENT_AND_TAGS): """ an iter that retrieves all direct children of this node, just one level deeper, content and tags will be pre-loaded by default. where preload can be: 0 WITH_NONE 1 WITH_CONTENT 2 WITH_TAGS 3 WITH_CONTENT_AND_TAGS """ return self.kitab.nodeChildrenIter(self.idNum, preload) def descendantsIter(self,preload = WITH_CONTENT_AND_TAGS, upperBound = -1): """ an iter retrieves all the children of this node, going deeper in a flat-fashion, pre-loading content and tags by default. where preload can be: 0 WITH_NONE 1 WITH_CONTENT 2 WITH_TAGS 3 WITH_CONTENT_AND_TAGS """ o1, o2 = self.kitab.getSliceBoundary(self.idNum) if upperBound != -1 and (o2 == -1 or o2 > upperBound): o2 = upperBound if o2 == -1: sql = SQL_GET_UNBOUNDED_NODES_SLICE[preload] args = (o1,) else: sql = SQL_GET_NODES_SLICE[preload] args = (o1, o2) it = self.kitab.cn().execute(sql, args) # will work but having the next "if" is faster # return imap(self.kitab.grouped_rows_to_node[preload], groupby(it,lambda i:i[0])) if preload & 2: return imap(self.kitab.grouped_rows_to_node[preload], groupby(it, lambda i:i[0])) return imap(self.kitab.row_to_node[preload], it) def childrenWithTagNameIter(self, tagname, load_content = True): """ an iter that retrieves all direct children taged with tagname, just one level deeper """ it = self.kitab.cn().execute(SQL_GET_TAGGED_CHILD_NODES[load_content], (self.idNum, tagname)) return imap(self.kitab.row_to_node[load_content], it) def descendantsWithTagNameIter(self, tagname,load_content = True): """ an iter that retrieves all the children tagged with tagname, going deeper in a flat-fashion """ o1, o2 = self.kitab.getSliceBoundary(self.idNum) if o2 == -1: sql = SQL_GET_UNBOUNDED_TAGGED_NODES_SLICE[load_content] args=(tagname, o1,) else: sql = SQL_GET_TAGGED_NODES_SLICE[load_content] args=(tagname, o1, o2) it = self.kitab.cn().execute(sql, args) return imap(self.kitab.row_to_node[load_content], it) # recursive non-optimized code # def traverser_(self, nodeStart, nodeEnd,preload = WITH_CONTENT_AND_TAGS,*args): # """recursively traverser nodes calling nodeStart and nodeEnd""" # nodeStart(self,*args) # for i in self.childrenIter(preload): # i.traverser_(nodeStart,nodeEnd,*args) # nodeEnd(self,*args) def traverser(self, preload, nodeStart, nodeEnd, *args): """ recursively traverser nodes calling nodeStart and nodeEnd Note: the implementation is a non-recursive optimized code with a single query """ dummy = lambda *args: None if not nodeStart: nodeStart = dummy if not nodeEnd: nodeEnd = dummy stack = [self] nodeStart(self, *args) for i in self.descendantsIter(preload): while(i.parent != stack[-1].idNum): nodeEnd(stack[-1], *args) stack.pop() stack.append(i) nodeStart(i, *args) while(stack): nodeEnd(stack[-1], *args) stack.pop() def traverserWithStack(self, preload, nodeStart, nodeEnd, *args): """ recursively traverser nodes calling nodeStart and nodeEnd passing the nodes stack to them Note: the implementation is a non-recursive optimized code with a single query """ dummy = lambda *args: None if not nodeStart: nodeStart = dummy if not nodeEnd: nodeEnd = dummy stack = [self] nodeStart(stack, *args) for i in self.descendantsIter(preload): while(i.parent != stack[-1].idNum): nodeEnd(stack, *args) stack.pop() stack.append(i) nodeStart(stack, *args) while(stack): nodeEnd(stack, *args) stack.pop() def sTraverser(self, preload, nodeStart, nodeEnd,upperBound = -1, *args): """ recursively traverser nodes calling nodeStart and nodeEnd and concatenating the return values """ stack = [self] s = nodeStart(self, *args) for i in self.descendantsIter(preload, upperBound): while(i.parent != stack[-1].idNum): s += nodeEnd(stack[-1],*args) stack.pop() s += nodeStart(i, *args) stack.append(i) while(stack): s += nodeEnd(stack[-1], *args) stack.pop() return s def toWiki(self): """export the node and its descendants into a wiki-like string""" return self.sTraverser(3, lambda n: n.getTags().has_key('header') and \ ''.join((u'\n', ((7-n.depth)*u' = '), n.getContent(), ((7-n.depth)*u' = '),u'\n')) or \ n.getContent(), lambda n: u'') def toHtml_cb(self, n): # trivial implementation #return n.getTags().has_key('header') and \ # u'\n%s\n' % (n.depth,escape(n.getContent()),n.depth) or \ # "

%s

" % escape(n.getContent()) r = u"" if n.getTags().has_key('header'): r = u'\n%s\n' % (n.depth, escape(n.getContent()), n.depth) else: r = u"

%s

" % self._wiki2html(escape(n.getContent())) if n.getTags().has_key('quran.tafseer.ref'): sura,aya,na = n.getTags()['quran.tafseer.ref'].split('-') #r += u'

نص من القرآن %s:%s:%s

\n\n' % (sura,aya,na) # tanween fix u'\u064E\u064E', u'\u064E\u200C\u064E' r += u'

%s

\n\n' % \ "".join(map(lambda i: (i[0] + u'\u202C').replace(u' \u06dd', u' \u202D\u06dd'), othman.getAyatIter(othman.ayaIdFromSuraAya(int(sura), int(aya)), int(na)))) if n.kitab and n.kitab.th: if n.kitab.originalKi and n.getTags().has_key('embed.original.section'): xref = n.getTags()['embed.original.section'] matnKi = n.kitab.originalKi embd_class="quote_orignal" embd = u"تعليقا على" elif n.getTags().has_key('embed.section.ref'): try: matn, xref = n.getTags()['embed.section.ref'].split(u'/', 1) except ValueError: pass else: matnKi = n.kitab.th.getCachedKitabByNameV(matn) embd_class = "quote_external" embd = u"اقتباس" else: embd = None if embd: matnNode = list(matnKi.getNodesByTagValueIter("header", xref, False, 1)) if matnNode: matnNode = matnNode[0] s = u'

%s:

' % (embd_class, embd) nx = matnKi.toc.next(matnNode) if nx: ub = nx.globalOrder else: ub = -1 # pass an option to disable embed to avoid endless recursion s += matnNode.toHtml(upperBound = ub) s += u'

    -- من كتاب %s

' % (matnKi.meta['kitab'],"_i"+str(matnNode.idNum),prettyId(matnKi.meta['kitab'])) s += u'
' r += s return r def _wiki2html(self, txt): # TODO: split from "^__________$" # FIXME: when an embedded quoted section got footnotes there would be duplicated ids txt = self._footnote_s_re.sub(r'''(\2)''', txt) txt = self._footnote_t_re.sub(r'''(\2)''', txt) txt = self._href_named_re.sub(r'''\2''', txt) txt = self._href_re.sub(r'''\1''', txt) return txt def toHtml(self, upperBound = -1): """export the node and its descendants into HTML string""" # TODO: escape special chars # TODO: replace ^$ with '
' or '

' # trivial implementation #return self.sTraverser( 3, lambda n: n.getTags().has_key('header') and u'\n%s\n' % (n.depth,escape(n.getContent()),n.depth) or "

%s

" % escape(n.getContent()), lambda n: u'', upperBound); return self.sTraverser( 3, self.toHtml_cb, lambda n: u'', upperBound); def toText(self, upperBound = -1): """ export node and its descendants into plain text string, can be used for generating excerpts of search results """ return self.sTraverser( 3, lambda n: n.getContent(), lambda n: u'', upperBound); def __toXmlStart(self, node, ostream): margin = u' '*node.depth tags=u' '.join(map(lambda d: d[1] == None and \ d[0] or \ d[0] + u' = ' + quoteattr(unicode(d[1])), node.getTags().items())) ostream.write(u' '.join((margin, u'',))) ostream.write(escape(node.getContent())) ostream.write(u'\n') def __toXmlEnd(self, node, ostream): margin = u' ' * node.depth ostream.write(margin + u' \n') def toXml(self,ostream): """ export the node and its descendants into a xml-like string using ostream as output """ # TODO: escape special chars self.traverser(3, self.__toXmlStart, self.__toXmlEnd,ostream) # def toXml(self,ostream): # # fixme # margin = u' '*self.depth # tags=u' '.join(map(lambda d: d[1] == None and d[0] or d[0]+u' = '+unicode(d[1]) ,self.getTags().items())) # ostream.write(u' '.join((margin,u'\n',))) # ostream.write(self.getContent()) # for i in descendantsIter(): # margin = u' '*i.depth # tags=u' '.join(map(lambda d: d[1] == None and d[0] or d[0]+u' = '+unicode(d[1]) ,i.getTags().items())) # ostream.write(u' '.join((margin,u'\n',))) # ostream.write(i.getContent()) # ostream.write(u'\n'+margin+u' \n') # ostream.write(u'\n'+margin+u' \n') #################################### if __name__ == '__main__': th = ThawabMan(os.path.expanduser('~/.thawab')) ki = th.mktemp() wiki = open(wiki_filename, "r") ki.seek(-1, -1) wiki2th(ki, wiki) ki.flush() thawab-4.1/Thawab/dataModel.py000066400000000000000000000332041305262755200163070ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The string constants to handle the data model Copyright © 2008, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ from tags import * MCACHE_BASE_FIELDS = [ 'cache_hash','repo','lang','kitab','version', 'releaseMajor', 'releaseMinor', 'type', 'author', 'year', 'originalAuthor', 'originalYear', 'originalKitab', 'originalVersion', 'classification', 'keywords' ] MCACHE_FIELDS = MCACHE_BASE_FIELDS + ['uri', 'mtime', 'flags'] SQL_MCACHE_SET = 'INSERT OR REPLACE INTO meta (rowid, %s) VALUES (1, %s)' % \ (', '.join(MCACHE_BASE_FIELDS), ', '.join(map(lambda i: ":"+i,MCACHE_BASE_FIELDS))) SQL_MCACHE_ADD = 'INSERT OR REPLACE INTO meta (%s) VALUES (%s)' % \ (', '.join(MCACHE_FIELDS), ', '.join(map(lambda i: ":"+i,MCACHE_FIELDS))) SQL_MCACHE_DROP = 'DELETE FROM meta WHERE uri=?' MCACHE_BASE = """\ CREATE TABLE "meta" ( "cache_hash" TEXT, "repo" TEXT, "lang" TEXT, "kitab" TEXT, "version" TEXT, "releaseMajor" INTEGER, "releaseMinor" INTEGER, "type" INTEGER, "author" TEXT, "year" INTEGER, "originalAuthor" TEXT, "originalYear" INTEGER, "originalKitab" TEXT, "originalVersion" TEXT, "classification" TEXT, "keywords" TEXT );""" SQL_MCACHE_DATA_MODEL = MCACHE_BASE[:MCACHE_BASE.find('\n)')] + \ """,\n\ "uri" TEXT UNIQUE, "mtime" FLOAT, "flags" INTEGER DEFAULT 0 ); CREATE INDEX MetaURIIndex on meta (uri); CREATE INDEX MetaRepoIndex on meta (repo); CREATE INDEX MetaLangIndex on meta (lang); CREATE INDEX MetaKitabIndex on meta (kitab); CREATE INDEX MetaKitabTypeIndex on meta (type); CREATE INDEX MetaKitabVersionIndex on meta (repo,kitab,version); CREATE INDEX MetaAuthorIndex on meta (author); CREATE INDEX MetaYearIndex on meta (year); CREATE INDEX MetaOriginalAuthorIndex on meta (originalAuthor); CREATE INDEX MetaOriginalYearIndex on meta (originalYear); CREATE INDEX MetaClassificationIndex on meta (classification); CREATE INDEX MetaFlagsIndex on meta (flags); CREATE TABLE "directories" ( "abspath" TEXT, "mtime" FLOAT ); """ SQL_MCACHE_GET = """SELECT rowid,* FROM meta""" SQL_MCACHE_GET_BY_KITAB = """SELECT rowid,* FROM meta ORDER BY kitab""" SQL_MCACHE_GET_UNINDEXED = """SELECT rowid,* FROM meta WHERE flags=0""" SQL_MCACHE_GET_DIRTY_INDEX = """SELECT rowid,* FROM meta WHERE flags=1""" SQL_MCACHE_GET_INDEXED = """SELECT rowid,* FROM meta WHERE flags=2""" SQL_MCACHE_SET_INDEXED = """UPDATE OR IGNORE meta SET flags=? WHERE uri=?""" SQL_MCACHE_SET_ALL_INDEXED = """UPDATE OR IGNORE meta SET flags=? WHERE flags>0""" SQL_DATA_MODEL = """\ %s CREATE TABLE "nodes" ( "idNum" INTEGER PRIMARY KEY NOT NULL, "content" TEXT, "parent" INTEGER, "globalOrder" INTEGER, "depth" INTEGER NOT NULL ); CREATE TABLE "tags" ( "idNum" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" VARCHAR NOT NULL, "flags" INTEGER NOT NULL, "comment" VARCHAR, "parent" INTEGER, "relation" INTEGER ); CREATE TABLE "nodesTags" ( "tagIdNum" INTEGER NOT NULL, "nodeIdNum" INTEGER NOT NULL, "param" VARCHAR, PRIMARY KEY ("tagIdNum", "nodeIdNum") ); CREATE INDEX NodesParentIndex on nodes (parent); CREATE INDEX NodesNodesGlobalOrderIndex on nodes (globalOrder); CREATE INDEX NodesDepthIndex on nodes (depth); CREATE INDEX NodesTagTagIdNumIndex on nodesTags(tagIdNum); CREATE INDEX NodesTagNodeIdNumIndex on nodesTags(nodeIdNum); CREATE INDEX NodesTagParamIndex on nodesTags(param); CREATE INDEX TagsName on tags (name); """ % MCACHE_BASE ################################################# # arguments to make the built-in tags STD_TAGS_ARGS = ( \ # (name, comment, flags, parent, relation) ("header", "an anchor that marks header in TOC.", TAG_FLAGS_FLOW_BLOCK | TAG_FLAGS_HEADER), ("request.fix.head", "a tag that marks an error in content.", 0), ("request.fix.footnote", "a tag that marks an error in content footnotes.", 0), ("textbody", "a tag that marks a typical text.",0), ("quran.tafseer.ref", 'a reference to some Ayat in tafseer (in the form of "Sura-Aya-number").', 0), ("embed.section.ref", 'a reference to some section in another kitab to embed (in the form of "kitabName-version/section").', 0), ("embed.original.section", 'a reference to some section in the original kitab to embed. (used in commentary books)', 0), # the following index-tags marks the header ("hadith.authenticity", "marks the authenticity of the hadith, param values are Sahih, Hasan, weak, fabricated", TAG_FLAGS_IX_TAG), # new index field for rawi ("hadith.ruwah.rawi", "marks a rawi", TAG_FLAGS_IX_FIELD), # the following index-tags marks the rawi field ("hadith.ruwah.authenticity", "marks the authenticity of the rawi, param values are thiqah, ...,kathoob", TAG_FLAGS_IX_TAG), ("hadith.ruwah.tabaqa", "marks the tabaqa of the rawi, param values are sahabi,tabii,...", TAG_FLAGS_IX_TAG) ) STD_TAGS_HASH = dict(map(lambda i: (i[0],i),STD_TAGS_ARGS)) # ENUMs WITH_NONE = 0 WITH_CONTENT = 1 WITH_TAGS = 2 WITH_CONTENT_AND_TAGS = 3 ################################################# # SQL statements for manipulating the dataModel SQL_GET_ALL_TAGS = """SELECT name,flags,comment,parent,relation FROM tags""" SQL_GET_NODE_CONTENT = """SELECT content from nodes WHERE idNum=? LIMIT 1""" SQL_GET_NODE_TAGS = """SELECT tags.name,nodesTags.param FROM nodesTags LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodesTags.nodeIdNum=?""" # FIXME: all sql that uses SQL_NODE_ARGS should be revised to check the shift after adding globalOrder SQL_NODE_ARGS = "nodes.idNum, nodes.parent, nodes.depth, nodes.globalOrder" SQL_NODE_COLS = (SQL_NODE_ARGS, SQL_NODE_ARGS+", nodes.content", SQL_NODE_ARGS+", tags.name, nodesTags.param, tags.flags", SQL_NODE_ARGS+", nodes.content"+", tags.name, nodesTags.param, tags.flags") SQL_GET_CHILD_NODES = ( \ """SELECT %s FROM nodes WHERE parent=? ORDER BY globalOrder""" % SQL_NODE_ARGS, """SELECT %s FROM nodes WHERE parent=? ORDER BY globalOrder""" % SQL_NODE_COLS[WITH_CONTENT], """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.parent=? ORDER BY nodes.globalOrder""" % SQL_NODE_COLS[WITH_TAGS], """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.parent=? ORDER BY nodes.globalOrder""" % SQL_NODE_COLS[WITH_CONTENT_AND_TAGS] ) SQL_TAG = """INSERT OR REPLACE INTO nodesTags (tagIdNum,nodeIdNum,param) SELECT tags.IdNum,?,? FROM tags WHERE tags.name = ? LIMIT 1""" SQL_CLEAR_TAGS_ON_NODE = """DELETE FROM nodesTags WHERE tags.name = ?""" SQL_GET_NODE_BY_IDNUM = ( \ """SELECT %s FROM nodes WHERE idNum=? ORDER BY globalOrder""" % SQL_NODE_ARGS, """SELECT %s FROM nodes WHERE idNum=? ORDER BY globalOrder""" % SQL_NODE_COLS[1], ) # node slices SQL_GET_NODES_SLICE = ( \ """SELECT %s FROM nodes WHERE globalOrder>? AND globalOrder? AND globalOrder? AND nodes.globalOrder? AND nodes.globalOrder? ORDER BY globalOrder""" % SQL_NODE_ARGS, """SELECT %s FROM nodes WHERE globalOrder>? ORDER BY globalOrder""" % SQL_NODE_COLS[WITH_CONTENT], """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.globalOrder>? ORDER BY nodes.globalOrder""" % SQL_NODE_COLS[WITH_TAGS], """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.globalOrder>? ORDER BY nodes.globalOrder""" % SQL_NODE_COLS[WITH_CONTENT_AND_TAGS] ) # tagged children node SQL_GET_TAGGED_CHILD_NODES = ( \ """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.parent=? AND tags.name=? ORDER BY nodes.globalOrder""" % SQL_NODE_ARGS, """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.parent=? AND tags.name=? ORDER BY nodes.globalOrder""" % SQL_NODE_COLS[1] ) # tagged node slices SQL_GET_TAGGED_NODES_SLICE = ( \ """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE tags.name=? AND nodes.globalOrder>? AND nodes.globalOrder? AND nodes.globalOrder? ORDER BY nodes.globalOrder""" % SQL_NODE_ARGS, """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE tags.name=? AND nodes.globalOrder>? ORDER BY nodes.globalOrder""" % SQL_NODE_COLS[1]) # get tagged node slices by param value SQL_GET_NODES_BY_TAG_VALUE = ( \ """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE tags.name=? AND nodesTags.param=? ORDER BY nodes.globalOrder""" % SQL_NODE_ARGS, """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE tags.name=? AND nodesTags.param=? ORDER BY nodes.globalOrder""" % SQL_NODE_COLS[1]) # get prev/next tagged node SQL_GET_PREV_TAGGED_NODE = ( \ """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.globalOrder? and tags.name=? ORDER BY nodes.globalOrder LIMIT 1""" % SQL_NODE_ARGS, """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.globalOrder>? and tags.name=? ORDER BY nodes.globalOrder LIMIT 1""" % SQL_NODE_COLS[1]) # get tagged child nodes SQL_GET_TAGGED_CHILD_NODES = ( \ """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.parent=? and tags.name=? ORDER BY nodes.globalOrder""" % SQL_NODE_ARGS, """SELECT %s FROM nodes LEFT OUTER JOIN nodesTags ON nodes.idNum = nodesTags.nodeIdNum LEFT OUTER JOIN tags on nodesTags.tagIdNum=tags.idNum WHERE nodes.parent=? and tags.name=? ORDER BY nodes.globalOrder""" % SQL_NODE_COLS[1]) SQL_GET_GLOBAL_ORDER = """SELECT globalOrder,depth FROM nodes WHERE idNum=? LIMIT 1""" SQL_GET_DESC_UPPER_BOUND = """SELECT globalOrder FROM nodes WHERE globalOrder>? AND depth<=? ORDER BY globalOrder LIMIT 1""" SQL_GET_SIBLING_GLOBAL_ORDER = """SELECT globalOrder FROM nodes WHERE parent=? and globalOrder>? ORDER BY globalOrder LIMIT 1""" SQL_GET_LAST_GLOBAL_ORDER = """SELECT globalOrder FROM nodes ORDER BY globalOrder DESC LIMIT 1""" SQL_DROP_DESC_NODES = ["""DELETE FROM nodes WHERE globalOrder>? AND globalOrder=? AND globalOrder?""", """DELETE FROM nodes WHERE globalOrder>=?"""] SQL_APPEND_NODE = ["""INSERT INTO nodes (content,parent,globalOrder,depth) VALUES (?,?,?,?)""", """INSERT INTO tmp_nodes (content,parent,globalOrder,depth) VALUES (?,?,?,?)"""] # SQL tags commands SQL_ADD_TAG = "INSERT OR REPLACE INTO tags (name, comment, flags, parent,relation) VALUES (?,?,?,-1,-1)" # modified: # SQL_GET_NODE_BY_IDNUM # SQL_GET_CHILD_NODES # SQL_GET_NODES_SLICE # SQL_GET_UNBOUNDED_NODES_SLICE # SQL_GET_TAGGED_CHILD_NODES # SQL_GET_TAGGED_NODES_SLICE # SQL_GET_UNBOUNDED_TAGGED_NODES_SLICE # removed: # SQL_GET_CHILD_NODES_AND_TAGS # SQL_GET_NODES_SLICE_AND_TAGS # SQL_GET_UNBOUNDED_NODES_SLICE_AND_TAGS # TODO: # make SQL_GET_NODE_BY_IDNUM capable of pre-loading tags (is this really needed??) thawab-4.1/Thawab/gtkUi.py000066400000000000000000001203661305262755200155060ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ gtkUi - gtk interface for thawab Copyright © 2009-2010, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path, time, re, sqlite3 import shutil, tempfile import threading, socket import gettext import gi gi.require_version("Gtk", "3.0") gi.require_version("WebKit", "3.0") from gi.repository import Gtk, Gdk, GObject, WebKit, Pango, GLib from subprocess import Popen, PIPE from urllib import unquote import Thawab.core from Thawab.webApp import webApp, get_theme_dirs from Thawab.shamelaUtils import ShamelaSqlite, shamelaImport from Thawab.platform import uri_to_filename from paste import httpserver setsid = getattr(os, 'setsid', None) if not setsid: setsid = getattr(os, 'setpgrp', None) _ps = [] def run_in_bg(cmd): global _ps setsid = getattr(os, 'setsid', None) if not setsid: setsid = getattr(os, 'setpgrp', None) _ps = filter(lambda x: x.poll() != None,_ps) # remove terminated processes from _ps list _ps.append(Popen(cmd,0,'/bin/sh',shell = True, preexec_fn = setsid)) def get_exec_full_path(fn): a = filter(lambda p: os.access(p, os.X_OK), map(lambda p: os.path.join(p, fn), os.environ['PATH'].split(os.pathsep))) if a: return a[0] return None def guess_browser(): e = get_exec_full_path("xdg-open") if not e: e = get_exec_full_path("firefox") if not e: e = "start" return e broswer = guess_browser() def sure(msg, parent = None): dlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, msg) dlg.connect("response", lambda *args: dlg.hide()) r = dlg.run() dlg.destroy() return r == Gtk.ResponseType.YES def info(msg, parent = None): dlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, msg) dlg.connect("response", lambda *args: dlg.hide()) r = dlg.run() dlg.destroy() def error(msg, parent = None): dlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, msg) dlg.connect("response", lambda *args: dlg.hide()) r = dlg.run() dlg.destroy() class ThWV(WebKit.WebView): def __init__(self): WebKit.WebView.__init__(self) self.set_full_content_zoom(True) self.connect_after("populate-popup", self.populate_popup) self.connect("navigation-requested", self._navigation_requested_cb) def _navigation_requested_cb(self, view, frame, networkRequest): uri = networkRequest.get_uri() if not uri.startswith('http://127.0.0.1') and not uri.startswith('http://localhost'): run_in_bg("%s '%s'" % (broswer ,uri)) return 1 return 0 def reload_if_index(self, *a, **kw): if self.get_property('uri').endswith('/index/'): self.reload() def _eval_js(self, e): """ can be used to eval a javascript expression eg. to obtain value of a javascript variable given its name """ self.execute_script('thawab_eval_js_oldtitle=document.title;document.title=%s;' % e) r = self.get_main_frame().get_title() self.execute_script('document.title=thawab_eval_js_oldtitle;') return r def populate_popup(self, view, menu): menu.append(Gtk.SeparatorMenuItem.new()) i = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_ZOOM_IN, None) i.connect('activate', lambda m,v,*a,**k: v.zoom_in(), view) menu.append(i) i = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_ZOOM_OUT, None) i.connect('activate', lambda m,v,**k: v.zoom_out(), view) menu.append(i) i = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_ZOOM_100, None) i.connect('activate', lambda m,v,*a,**k: v.get_zoom_level() == 1.0 or v.set_zoom_level(1.0), view) menu.append(i) menu.show_all() return False targets = Gtk.TargetList.new([]) targets.add_uri_targets((1 << 5) -1) class ThImportWindow(Gtk.Window): def __init__(self, main): Gtk.Window.__init__(self) self.progress_dict = { } self.progress_phase = 0 self.progress_books_in_file = 0 self.progress_element = 0 self.add_dlg = None self.set_size_request(-1, 400) ## prepare dnd self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.drag_dest_set_target_list(targets) self.connect('drag-data-received', self.drop_data_cb) self.set_title(_('Import Shamela .bok files')) self.set_type_hint(Gdk.WindowTypeHint.DIALOG) self.set_modal(True) self.set_transient_for(main) self.main = main self.connect('delete-event', self.close_cb) self.connect('destroy', self.close_cb) vb = Gtk.VBox(False,2) self.add(vb) hb0 = Gtk.HBox(False,2) vb.pack_start(hb0,False, False, 2) self.tool = hb = Gtk.HBox(False,2) hb0.pack_start(hb,False, False, 2) b = Gtk.Button(stock = Gtk.STOCK_ADD) b.connect('clicked', self.add_cb, self) hb.pack_start(b, False, False, 2) b = Gtk.Button(stock = Gtk.STOCK_REMOVE) b.connect('clicked', self.rm) hb.pack_start(b, False, False, 2) b = Gtk.Button(stock = Gtk.STOCK_CLEAR) b.connect('clicked', lambda *a: self.ls.clear()) hb.pack_start(b, False, False, 2) b = Gtk.Button(stock = Gtk.STOCK_CONVERT) b.connect('clicked', self.start) hb.pack_start(b, False, False, 2) self.progress = Gtk.ProgressBar() self.progress.set_fraction(0.0) hb0.pack_start(self.progress, True, True, 2) self.cancel_b = b = Gtk.Button(stock = Gtk.STOCK_CANCEL) b.connect('clicked', self.stop) b.set_sensitive(False) hb0.pack_start(b, False, False, 2) self.close_b = b = Gtk.Button(stock = Gtk.STOCK_CLOSE) b.connect('clicked', self.close_cb) hb0.pack_start(b, False, False, 2) self.ls = Gtk.ListStore(str,str,float,int,str) # fn, basename, percent, pulse, label self.lsv = Gtk.TreeView(self.ls) #self.lsv.set_size_request(250, -1) cells = [] cols = [] cells.append(Gtk.CellRendererText()) cols.append(Gtk.TreeViewColumn('Files', cells[-1], text = 1)) cols[-1].set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) cols[-1].set_resizable(True) cols[-1].set_expand(True) cells.append(Gtk.CellRendererProgress()) cols.append(Gtk.TreeViewColumn('%', cells[-1], value = 2,pulse = 3,text = 4)) cols[-1].set_expand(False) self.lsv.set_headers_visible(True) self.lsv.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) for i in cols: self.lsv.insert_column(i, -1) scroll = Gtk.ScrolledWindow() scroll.set_policy(Gtk.PolicyType.NEVER,Gtk.PolicyType.AUTOMATIC) scroll.add(self.lsv) vb.pack_start(scroll,True, True, 2) self.x = x = Gtk.Expander.new(_("Advanced options")) vb.pack_start(x, False, False, 2) xvb = Gtk.VBox(False,2); x.add(xvb) f = Gtk.Frame.new(_('Performance tuning:')) xvb.add(f) fvb = Gtk.VBox(False,2) f.add(fvb) hb = Gtk.HBox(False,2) fvb.add(hb) self.in_mem = Gtk.CheckButton(_('in memory')) self.in_mem.set_tooltip_text(_("faster but consumes more memory and harder to debug.")) hb.pack_start(self.in_mem, False, False, 2) f = Gtk.Frame.new('Version Control:') xvb.add(f) fvb = Gtk.VBox(False,2); f.add(fvb) hb = Gtk.HBox(False,2); fvb.add(hb) hb.pack_start(Gtk.Label(_('Release Major:')), False, False, 2) adj = Gtk.Adjustment(0, 0, 10000, 1, 10, 0) self.releaseMajor = s = Gtk.SpinButton() s.set_adjustment(adj) hb.pack_start(self.releaseMajor, False, False, 2) hb.pack_start(Gtk.Label(_('Release Minor:')), False, False, 2) self.releaseMinor = s = Gtk.SpinButton() s.set_adjustment(adj) hb.pack_start(self.releaseMinor, False, False, 2) f = Gtk.Frame.new('Footnotes:'); xvb.add(f) fvb = Gtk.VBox(False,2); f.add(fvb) hb = Gtk.HBox(False,2); fvb.add(hb) hb.pack_start(Gtk.Label(_('Prefix:')), False, False, 2) self.ft_prefix = Gtk.Entry() self.ft_prefix.set_text('(') self.ft_prefix.set_width_chars(3) hb.pack_start(self.ft_prefix, False, False, 2) hb.pack_start(Gtk.Label(_('Suffix:')), False, False, 2) self.ft_suffix = Gtk.Entry() self.ft_suffix.set_text(')') self.ft_suffix.set_width_chars(3) hb.pack_start(self.ft_suffix, False, False, 2) self.ft_at_line_start = Gtk.CheckButton(_('only at line start')) hb.pack_start(self.ft_at_line_start, False, False, 2) hb = Gtk.HBox(False,2); fvb.add(hb) hb.pack_start(Gtk.Label(_('in between spaces:')), False, False, 2) self.ft_sp = [Gtk.RadioButton(group = None, label = _('no spaces'))] self.ft_sp.append(Gtk.RadioButton(group=self.ft_sp[0], label = _('optional white-space'))) self.ft_sp.append(Gtk.RadioButton(group=self.ft_sp[0], label = _('optional white-spaces'))) for i in self.ft_sp: hb.pack_start(i, False, False, 2) f = Gtk.Frame.new('Footnote anchors in body:'); xvb.add(f) fvb = Gtk.VBox(False,2); f.add(fvb) hb = Gtk.HBox(False,2); fvb.add(hb) hb.pack_start(Gtk.Label(_('Prefix:')), False, False, 2) self.bft_prefix = Gtk.Entry() self.bft_prefix.set_text('(') self.bft_prefix.set_width_chars(3) hb.pack_start(self.bft_prefix, False, False, 2) hb.pack_start(Gtk.Label(_('Suffix:')), False, False, 2) self.bft_suffix = Gtk.Entry() self.bft_suffix.set_text(')') self.bft_suffix.set_width_chars(3) hb.pack_start(self.bft_suffix, False, False, 2) hb = Gtk.HBox(False,2); fvb.add(hb) hb.pack_start(Gtk.Label(_('in between spaces:')), False, False, 2) self.bft_sp = [Gtk.RadioButton(group = None, label = _('no spaces'))] self.bft_sp.append(Gtk.RadioButton(group=self.bft_sp[0], label = _('optional white-space'))) self.bft_sp.append(Gtk.RadioButton(group=self.bft_sp[0], label = _('optional white-spaces'))) for i in self.bft_sp: hb.pack_start(i, False, False, 2) # TODO: add options to specify version and revision # TODO: add options to specify wither to break by hno # TODO: add options for handling existing files (overwrite?) ft_at_line_start = False ft_prefix = u'(' ft_suffix = u')' ft_sp = u'' # can be ur'\s?' or ur'\s*' body_footnote_re = re.escape(ft_prefix)+ft_sp+ur'(\d+)'+ft_sp+re.escape(ft_suffix) footnote_re = (ft_at_line_start and u'^\s*' or u'') + body_footnote_re ft_prefix_len = len(ft_prefix) ft_suffix_len = len(ft_suffix) #shamelaImport(cursor, sh, bkid, footnote_re = ur'\((\d+)\)', body_footnote_re = ur'\((\d+)\)', ft_prefix_len = 1, ft_suffix_len = 1): #self.show_all() def close_cb(self, *w): return self.hide() or True def element_pulse_cb(self, i): self.ls[(i,)][2] = 0 self.ls[(i,)][3] = int(abs(self.ls[(i,)][3])+1) Gtk.main_iteration() def element_progress_cb(self, i, percent, text = None): l = self.ls[(i,)] if percent >= 0.0: l[2] = percent if text != None and not 'working' in text: l[4] = text else: l[4] = '%s%%' % str(int(percent)) Gtk.main_iteration() def progress_cb(self, msg, p, *d, **kw): # print " ** progress phase %d: [%g%% completed] %s" % (self.progress_phase, p, msg) i = self.progress_element N = len(self.ls) j = self.progress_book_in_file n = self.progress_books_in_file if n == 0 or N == 0: return if self.progress_phase == 1: percent = p*0.25 else: percent = (75.0/n)*j + p*0.75/n + 25.0 if not kw.has_key('show_msg'): msg = _("working ...") self.element_progress_cb(i, percent, msg) self.progress.set_fraction( float(i)/N + percent/100.0/N ) Gtk.main_iteration() def start_cb(self): self.tool.set_sensitive(False) self.x.set_sensitive(False) self.cancel_b.set_sensitive(True) self.progress_dict['cancel'] = False def start(self, b): self.start_cb() self.progress.set_text(_("working ...")) ft_at_line_start = self.ft_at_line_start.get_active() ft_prefix = self.ft_prefix.get_text() ft_prefix_len = len(ft_prefix) ft_suffix=self.ft_suffix.get_text() ft_suffix_len = len(ft_suffix) ft_sp = [u'', ur'\s?' , ur'\s*'][ [i.get_active() for i in self.ft_sp].index(True) ] footnote_re = (ft_at_line_start and u'^\s*' or u'') + \ re.escape(ft_prefix) + \ ft_sp+ur'(\d+)' + \ ft_sp + \ re.escape(ft_suffix) bft_prefix=self.bft_prefix.get_text() bft_suffix=self.bft_suffix.get_text() bft_sp = [u'', ur'\s?' , ur'\s*'][ [i.get_active() for i in self.bft_sp].index(True) ] body_footnote_re = re.escape(bft_prefix) + \ bft_sp + \ ur'(\d+)' + \ bft_sp + \ re.escape(bft_suffix) if not self.in_mem.get_active(): fh, db_fn = tempfile.mkstemp(suffix = '.sqlite', prefix = 'th_shamela_tmp') else: db_fn = None for i,l in enumerate(self.ls): self.progress_element = i self.progress_book_in_file = 0 self.progress_books_in_file = 1 fn = l[0] if db_fn: f = open(db_fn, "w") f.truncate(0) f.close() cn = sqlite3.connect(db_fn, isolation_level = None) else: cn = None self.progress_phase = 1 try: sh = ShamelaSqlite(fn, cn, int(self.releaseMajor.get_value()), int(self.releaseMinor.get_value()), self.progress_cb, progress_dict = self.progress_dict) except TypeError: print "not a shamela file" continue except OSError: print "mdbtools is not installed" break if not sh.toSqlite(): # canceled self.progress.set_text(_("Canceled")) self.element_progress_cb(self.progress_element, -1.0, _("Canceled")) return self.progress_phase = 2 ids = sh.getBookIds() self.progress_books_in_file = len(ids) for j, bkid in enumerate(ids): self.progress_book_in_file = j ki = self.main.th.mktemp() c = ki.seek(-1,-1) m = shamelaImport(c, sh, bkid, footnote_re, body_footnote_re, ft_prefix_len, ft_suffix_len) if m == None: # canceled self.progress.set_text(_("Canceled")) self.element_progress_cb(self.progress_element, -1.0, _("Canceled")) return c.flush() t_fn = os.path.join(self.main.th.prefixes[0], 'db', u"".join((m['kitab'] + \ u"-" + \ m['version'] + \ Thawab.core.th_ext,))) #print "moving %s to %s" % (ki.uri, t_fn) try: shutil.move(ki.uri, t_fn) except OSError: print "unable to move converted file." # windows can't move an opened file # FIXME: close ki in a clean way so the above code works in windows self.progress_cb(_("Done"), 100.0, show_msg = True) if db_fn and os.path.exists(db_fn): try: os.unlink(db_fn) except OSError: pass #self.element_progress_cb(0, 25.0, "testing") self.tool.set_sensitive(True) self.x.set_sensitive(True) self.cancel_b.set_sensitive(False) self.main.th.loadMeta() self.main._do_in_all_views('reload_if_index') self.progress.set_text(_("Done")) info(_("Convert Book, Done"), self.main) self.ls.clear() self.progress.set_text("") self.progress.set_fraction(0.0) self.hide() def stop(self, b): self.tool.set_sensitive(True) self.x.set_sensitive(True) self.cancel_b.set_sensitive(False) self.progress_dict['cancel'] = True def add_cb(self, b, parent=None): if self.run_add_dlg(parent) == Gtk.ResponseType.ACCEPT: for i in self.add_dlg.get_filenames(): self.add_fn(i) def run_add_dlg(self, parent=None): if self.add_dlg: return self.add_dlg.run() self.add_dlg = Gtk.FileChooserDialog(_("Select files to import"), parent = parent, buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)) ff = Gtk.FileFilter() ff.set_name(_('Shamela BOK files')) ff.add_pattern('*.[Bb][Oo][Kk]') self.add_dlg.add_filter(ff) ff = Gtk.FileFilter() ff.set_name('All files') ff.add_pattern('*') self.add_dlg.add_filter(ff) self.add_dlg.set_select_multiple(True) self.add_dlg.connect('delete-event', lambda w,*a: w.hide() or True) self.add_dlg.connect('response', lambda w,*a: w.hide() or True) return self.add_dlg.run() def rm(self, b): l, ls_p = self.lsv.get_selection().get_selected_rows() r = map(lambda p: Gtk.TreeRowReference.new(self.ls, p), ls_p) for i in r: self.ls.remove(self.ls.get_iter(i.get_path())) def add_fn(self, fn): self.ls.append([fn, os.path.basename(fn), float(0), -1, "Not started"]) def add_uri(self, i): if i.startswith('file://'): f = uri_to_filename(unquote(i[7:])) self.add_fn(f) else: print "Protocol not supported in [%s]" % i def drop_data_cb(self, widget, dc, x, y, selection_data, info, t): for i in selection_data.get_uris(): self.add_uri(i) #dc.drop_finish(True, t) class TabLabel(Gtk.HBox): """A class for Tab labels""" __gsignals__ = { "close": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_OBJECT,)) } def __init__ (self, title, child): """initialize the tab label""" Gtk.HBox.__init__(self, False, 4) self.title = title self.child = child self.label = Gtk.Label(title) self.label.props.max_width_chars = 30 self.label.set_ellipsize(Pango.EllipsizeMode.MIDDLE) self.label.set_alignment(0.0, 0.5) # FIXME: use another icon icon = Gtk.Image.new_from_icon_name("thawab", Gtk.IconSize.MENU) close_image = Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU) close_button = Gtk.Button() close_button.set_relief(Gtk.ReliefStyle.NONE) close_button.connect("clicked", self._close_tab, child) close_button.add(close_image) self.pack_start(icon, False, False, 0) self.pack_start(self.label, True, True, 0) self.pack_start(close_button, False, False, 0) #self.set_data("label", self.label) #self.set_data("close-button", close_button) self.connect("style-set", tab_label_style_set_cb) def set_label_text (self, text): """sets the text of this label""" if text: self.label.set_label(text) def _close_tab (self, widget, child): self.emit("close", child) def tab_label_style_set_cb (tab_label, style): #context = tab_label.get_pango_context() #font_desc = Pango.font_description_from_string(tab_label.label.get_label()) #metrics = context.get_metrics(font_desc, context.get_language()) #metrics = context.get_metrics(tab_label.style.font_desc, context.get_language()) #char_width = metrics.get_approximate_digit_width() #(icons, width, height) = Gtk.icon_size_lookup_for_settings(tab_label.get_settings(), # Gtk.IconSize.MENU) #tab_label.set_size_request(20 * char_width + 2 * width, -1) tab_label.set_size_request(230, -1) #button = tab_label.get_data("close-button") #button.set_size_request(width + 4, height + 4) class ContentPane (Gtk.Notebook): __gsignals__ = { "focus-view-title-changed": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_OBJECT, GObject.TYPE_STRING,)), "focus-view-load-committed": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_OBJECT, GObject.TYPE_OBJECT,)), "new-window-requested": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_OBJECT,)) } def __init__ (self, default_url = None, default_title = None, hp = Gtk.PolicyType.NEVER, vp = Gtk.PolicyType.ALWAYS): """initialize the content pane""" Gtk.Notebook.__init__(self) self.set_scrollable(True) self.default_url = default_url self.default_title = default_title self.hp = hp self.vp = vp self.props.scrollable = True #self.props.homogeneous = True self.connect("switch-page", self._switch_page) self.show_all() self._hovered_uri = None def load (self, uri): """load the given uri in the current web view""" child = self.get_nth_page(self.get_current_page()) wv = child.get_child() wv.open(uri) def new_tab_with_webview (self, webview): """creates a new tab with the given webview as its child""" self._construct_tab_view(webview) def new_tab (self, url = None): """creates a new page in a new tab""" # create the tab content wv = ThWV() self._construct_tab_view(wv, url) return wv def _construct_tab_view (self, wv, url = None, title = None): wv.connect("hovering-over-link", self._hovering_over_link_cb) wv.connect("populate-popup", self._populate_page_popup_cb) wv.connect("load-committed", self._view_load_committed_cb) wv.connect("load-finished", self._view_load_finished_cb) wv.connect("create-web-view", self._new_web_view_request_cb) # load the content self._hovered_uri = None if not url: url=self.default_url if url: wv.open(url) scrolled_window = Gtk.ScrolledWindow() scrolled_window.props.hscrollbar_policy = self.hp scrolled_window.props.vscrollbar_policy = self.vp scrolled_window.add(wv) scrolled_window.show_all() # create the tab if not title: title=self.default_title if not title: title = url label = TabLabel(title, scrolled_window) label.connect("close", self._close_tab) label.show_all() new_tab_number = self.append_page(scrolled_window, label) self.set_tab_reorderable(scrolled_window, True) #self.set_tab_label_packing(scrolled_window, False, False, Gtk.PACK_START) self.set_tab_label(scrolled_window, label) # hide the tab if there's only one self.set_show_tabs(self.get_n_pages() > 1) self.show_all() self.set_current_page(new_tab_number) def _populate_page_popup_cb(self, view, menu): # misc if self._hovered_uri: open_in_new_tab = Gtk.MenuItem(_("Open Link in New Tab")) open_in_new_tab.connect("activate", self._open_in_new_tab, view) menu.insert(open_in_new_tab, 0) menu.show_all() def _open_in_new_tab (self, menuitem, view): self.new_tab(self._hovered_uri) def _close_tab (self, label, child): page_num = self.page_num(child) if page_num != -1: view = child.get_child() view.destroy() self.remove_page(page_num) self.set_show_tabs(self.get_n_pages() > 1) def _switch_page (self, notebook, page, page_num): child = self.get_nth_page(page_num) view = child.get_child() frame = view.get_main_frame() self.emit("focus-view-load-committed", view, frame) def _hovering_over_link_cb (self, view, title, uri): self._hovered_uri = uri def _view_load_committed_cb (self, view, frame): self.emit("focus-view-load-committed", view, frame) def _view_load_finished_cb(self, view, frame): child = self.get_nth_page(self.get_current_page()) label = self.get_tab_label(child) title = frame.get_title() if not title: title = frame.get_uri() label.set_label_text(title) def _new_web_view_request_cb (self, web_view, web_frame): view = self.new_tab() view.connect("web-view-ready", self._new_web_view_ready_cb) return view def _new_web_view_ready_cb (self, web_view): self.emit("new-window-requested", web_view) class ThIndexerWindow(Gtk.Window): def __init__(self, main): Gtk.Window.__init__(self) self.main = main self.connect('delete-event', lambda w,*a: w.hide() or True) self.set_title(_('Manage search index')) self.set_type_hint(Gdk.WindowTypeHint.DIALOG) self.set_modal(True) self.set_transient_for(main) self.main = main self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) vb = Gtk.VBox(False,2); self.add(vb) hb = Gtk.HBox(False,2); vb.pack_start(hb, False, False, 0) self.progress = Gtk.ProgressBar() self.progress.set_show_text(True) self.progress.set_text("") self.start_b = b = Gtk.Button(_("Queue new books")) b.connect('clicked', self.indexNew) hb.pack_start(b, False, False, 0) hb.pack_start(self.progress, False, False, 0) self.cancel_b = b = Gtk.Button(stock = Gtk.STOCK_CLOSE) #b.connect('clicked', self.cancel_cb) b.connect('clicked', lambda w,*a: self.hide() or True) hb.pack_start(b, False, False, 0) #b.set_sensitive(False) #self.update() def cancel_cb(self, *w): return False if self.main.th.asyncIndexer.started: self.main.th.asyncIndexer.cancelQueued() self.start_b.set_sensitive(True) self.cancel_b.set_sensitive(False) self.progress.set_text(_("Indexing jobs canceled")) return False def indexNew(self, *a): self.start_b.set_sensitive(False) #self.cancel_b.set_sensitive(True) self.main.th.asyncIndexer.queueIndexNew() if not self.main.th.asyncIndexer.started: self.main.th.asyncIndexer.start() self.update() #GLib.timeout_add(250, self.update) def update(self, *a): #if not self.get_property('visible'): # return True jj = j = self.main.th.asyncIndexer.jobs() while (j > 0 and self.main.get_property('visible')): self.progress.set_text (_("Indexing ... (%d left)") % j) self.progress.pulse() j = self.main.th.asyncIndexer.jobs() Gtk.main_iteration() #Gtk.main_iteration_do(True) self.progress.set_text (_("No indexing jobs left")) self.start_b.set_sensitive(True) if j <= 0 and jj > 0: info(_("Indexing %d jobs, Done") % jj, self.main) #self.cancel_b.set_sensitive(False) return True class ThFixesWindow(Gtk.Window): def __init__(self, main): Gtk.Window.__init__(self) self.set_title(_('Misc. Fixes')) self.set_type_hint(Gdk.WindowTypeHint.DIALOG) self.set_modal(True) self.set_transient_for(main) self.main = main self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.connect('delete-event', lambda w,*a: w.hide() or True) self.set_deletable(True) vb = Gtk.VBox(False,2); self.add(vb) hb = Gtk.HBox(False,2); vb.pack_start(hb, False, False, 0) l = Gtk.Label() l.set_markup(_("""Those procedures are to be used in case of emergency only, for example to recover power failure.""")) hb.pack_start(l , False, False, 0) hb = Gtk.HBox(False,2); vb.pack_start(hb, False, False, 0) b = Gtk.Button(_('remove search index')) b.set_tooltip_text(_('you will need to re-index all books')) hb.pack_start(b , False, False, 0) b.connect('clicked', self.rm_index_cb) hb = Gtk.HBox(False,2); vb.pack_start(hb, False, False, 0) b = Gtk.Button(_('remove meta data cache to generate a fresh one')) b.set_tooltip_text(_('instead of incremental meta data gathering')) hb.pack_start(b , False, False, 0) b.connect('clicked', self.rm_mcache_cb) b = Gtk.Button(stock = Gtk.STOCK_CLOSE) hb.pack_end(b , False, False, 0) b.connect('clicked', lambda w,*a: self.hide() or True) #self.show_all() def rm_index_cb(self, b): if not sure(_("You will need to recreate search index in-order to search again.\nAre you sure you want to remove search index?"), self.main): return p = os.path.join(self.main.th.prefixes[0], 'index') try: shutil.rmtree(p) except OSError: error(_("unable to remove folder [%s]" % p), self.main) else: info(_("Done"), self.main) def rm_mcache_cb(self, b): if not sure(_("Are you sure you want to remove search meta data cache?"), self.main): return p = os.path.join(self.main.th.prefixes[0], 'cache', 'meta.db') try: os.unlink(p) except OSError: error(_("unable to remove file [%s]" % p), self.main) else: self.main.th.reconstructMetaIndexedFlags() info(_("Done"), self.main) class ThMainWindow(Gtk.Window): def __init__(self, th, port, server): self.th = th self.port = port self.server = server # we need this to quit the server when closing main window Gtk.Window.set_default_icon_name('thawab') Gtk.Window.__init__(self) self.set_title(_('Thawab')) self.set_default_size(600, 480) self.maximize() self.fixes_w = ThFixesWindow(self) self.import_w = ThImportWindow(self) self.ix_w = ThIndexerWindow(self) vb = Gtk.VBox(False,0); self.add(vb) tools = Gtk.Toolbar() vb.pack_start(tools, False, False, 2) self._content = ContentPane("http://127.0.0.1:%d/" % port, _("Thawab")) vb.pack_start(self._content,True, True, 2) self.axl = Gtk.AccelGroup() self.add_accel_group(self.axl) ACCEL_CTRL_KEY, ACCEL_CTRL_MOD = Gtk.accelerator_parse("") ACCEL_SHFT_KEY, ACCEL_SHFT_MOD = Gtk.accelerator_parse("") b = Gtk.ToolButton.new_from_stock(Gtk.STOCK_NEW) b.connect('clicked', lambda bb: self._content.new_tab()) b.add_accelerator("clicked", self.axl, ord('n'), ACCEL_CTRL_MOD, Gtk.AccelFlags.VISIBLE) b.set_tooltip_text("{}\t‪{}‬".format(_("Open a new tab"), "(Ctrl+N)" )) tools.insert(b, -1) # TODO: add navigation buttons (back, forward ..etc.) and zoom buttons tools.insert(Gtk.SeparatorToolItem(), -1) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_CONVERT, Gtk.IconSize.BUTTON) b = Gtk.ToolButton.new(icon_widget = img, label = _("Import")) b.set_tooltip_text(_("Import .bok files")) b.connect('clicked', self.import_cb) tools.insert(b, -1) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_FIND_AND_REPLACE, Gtk.IconSize.BUTTON) b = Gtk.ToolButton(icon_widget = img, label = _("Index")) b.set_is_important(True) b.set_tooltip_text(_("Create search index")) b.connect('clicked', lambda *a: self.ix_w.show_all()) tools.insert(b, -1) tools.insert(Gtk.SeparatorToolItem(), -1) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_ZOOM_IN, Gtk.IconSize.BUTTON) b = Gtk.ToolButton(icon_widget = img, label = _("Zoom in")) b.add_accelerator("clicked", self.axl, Gdk.KEY_equal, ACCEL_CTRL_MOD, Gtk.AccelFlags.VISIBLE) b.add_accelerator("clicked", self.axl, Gdk.KEY_plus, ACCEL_CTRL_MOD, Gtk.AccelFlags.VISIBLE) b.add_accelerator("clicked", self.axl, Gdk.KEY_KP_Add, ACCEL_CTRL_MOD, Gtk.AccelFlags.VISIBLE) b.set_is_important(True) b.set_tooltip_text("{}\t‪{}‬".format(_("Makes things appear bigger"), "(Ctrl++)")) b.connect('clicked', lambda a: self._do_in_current_view("zoom_in")) tools.insert(b, -1) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_ZOOM_OUT, Gtk.IconSize.BUTTON) b = Gtk.ToolButton(icon_widget = img, label = _("Zoom out")) b.add_accelerator("clicked", self.axl, Gdk.KEY_minus, ACCEL_CTRL_MOD, Gtk.AccelFlags.VISIBLE) b.add_accelerator("clicked", self.axl, Gdk.KEY_KP_Subtract, ACCEL_CTRL_MOD, Gtk.AccelFlags.VISIBLE) b.set_tooltip_text("{}\t‪{}‬".format(_("Makes things appear smaller"), "(Ctrl+-)")) b.connect('clicked', lambda a: self._do_in_current_view("zoom_out")) tools.insert(b, -1) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_ZOOM_100, Gtk.IconSize.BUTTON) b = Gtk.ToolButton(icon_widget = img, label = _("1:1 Zoom")) b.add_accelerator("clicked", self.axl, ord('0'), ACCEL_CTRL_MOD, Gtk.AccelFlags.VISIBLE) b.add_accelerator("clicked", self.axl, Gdk.KEY_KP_0, ACCEL_CTRL_MOD, Gtk.AccelFlags.VISIBLE) b.set_tooltip_text("{}\t{}".format(_("Restore original zoom factor"), "(Ctrl+0)")) b.connect('clicked', lambda a: self._do_in_current_view("set_zoom_level",1.0)) tools.insert(b, -1) tools.insert(Gtk.SeparatorToolItem(), -1) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_PREFERENCES, Gtk.IconSize.BUTTON) b = Gtk.ToolButton(icon_widget = img, label = _("Fixes")) b.set_is_important(True) b.set_tooltip_text(_("Misc Fixes")) b.connect('clicked', self.fixes_cb) tools.insert(b, -1) tools.insert(Gtk.SeparatorToolItem(), -1) img = Gtk.Image() img.set_from_stock(Gtk.STOCK_HELP, Gtk.IconSize.BUTTON) b = Gtk.ToolButton(icon_widget = img, label = _("Help")) b.set_tooltip_text(_("Show user manual")) b.connect('clicked', lambda a: self._content.new_tab ("http://127.0.0.1:%d/_theme/manual/manual.html" % port)) tools.insert(b, -1) self._content.new_tab() self.connect("delete_event", self.quit) self.connect("destroy", self.quit) ## prepare dnd self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.drag_dest_set_target_list(targets) self.connect('drag-data-received', self.drop_data_cb) self.show_all() def _do_in_current_view (self, action, *a, **kw): n = self._content.get_current_page() if n < 0: return view = self._content.get_nth_page(n).get_child() getattr(view, action)(*a,**kw) def _do_in_all_views (self, action, *a, **kw): for n in range(self._content.get_n_pages()): view = self._content.get_nth_page(n).get_child() getattr(view, action)(*a,**kw) def fixes_cb(self, b): if not self.fixes_w: self.fixes_w = ThFixesWindow(self) self.fixes_w.show_all() def drop_data_cb(self, widget, dc, x, y, selection_data, info, t): if not self.import_w: self.import_w = ThImportWindow(self) for i in selection_data.get_uris(): self.import_w.add_uri(i) self.import_w.show_all() #dc.drop_finish (True, t); def import_cb(self, b): if not self.import_w: self.import_w = ThImportWindow(self) self.import_w.show_all() def quit(self,*args): #if self.import_w.cancel_b.get_sensitive(): # self.import_w.show() # return True #if not self.ix_w.start_b.get_sensitive(): # self.ix_w.show_all() # return True self.server.running = False Gtk.main_quit() return False THAWAB_HIGH_PORT = 18080 def launchServer(): exedir = os.path.dirname(sys.argv[0]) th = Thawab.core.ThawabMan(isMonolithic = False) lookup = [ os.path.join(exedir,'thawab-themes'), os.path.join(exedir,'..','share','thawab','thawab-themes'), ] lookup.extend(map(lambda i: os.path.join(i, 'themes'), th.prefixes)) app = webApp(th, 'app', lookup, th.conf.get('theme', 'default'), '/_theme/',) launched = False port = THAWAB_HIGH_PORT while(not launched): try: server = httpserver.serve(app, host = '127.0.0.1', port = port, start_loop = False) except socket.error: port += 1 else: launched = True return th, port, server def onlyterminal(): #To run thawab by terminal only by thawab-server exedir = os.path.dirname(sys.argv[0]) ld = os.path.join(exedir,'..','share','locale') if not os.path.isdir(ld): ld = os.path.join(exedir, 'locale') gettext.install('thawab', ld, unicode = 0) th, port, server = launchServer() try: thread=threading.Thread(target=server.serve_forever, args=()) thread.daemon=True thread.start() while True: time.sleep(100) except (KeyboardInterrupt, SystemExit): print '\nHope to made a nice time, Ojuba team .\n' os._exit(0) def main(): exedir = os.path.dirname(sys.argv[0]) ld = os.path.join(exedir,'..','share','locale') if not os.path.isdir(ld): ld = os.path.join(exedir, 'locale') gettext.install('thawab', ld, unicode = 0) th, port, server = launchServer() GObject.threads_init() Gdk.threads_init() threading.Thread(target=server.serve_forever, args=()).start() while(not server.running): time.sleep(0.25) Gdk.threads_enter() w = ThMainWindow(th, port,server) Gtk.main() Gdk.threads_leave() if __name__ == "__main__": main() thawab-4.1/Thawab/meta.py000066400000000000000000000212331305262755200153420ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The meta handling classes of thawab Copyright © 2008, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import os import os.path import sqlite3 import threading import time import hashlib from itertools import imap,groupby from dataModel import * from okasha.utils import fromFs, toFs, strverscmp import re def prettyId(i, empty_for_special = True): """convert the id into a more human form""" if empty_for_special and i.startswith('_'): return '' return i.replace('_',' ') def makeId(i): """convert the id into a canonical form""" return i.strip().replace(' ','_').replace('/','_') def metaVr(m): return m[u"version"] + u"-" + unicode(m[u"releaseMajor"]) def metaVrr(m): return u"-".join((m[u"version"], unicode(m[u"releaseMajor"]), unicode(m[u"releaseMinor"]))) def metaDict2Hash(meta, suffix = None): k = filter(lambda i: i != 'cache_hash', meta.keys()) k.sort() l = [] for i in k: l.append(u"%s:%s" % (i,meta[i])) l.append(u"timestamp:%d" % int(time.time())) if suffix: l.append(suffix) return hashlib.sha256((u"-".join(l)).encode('utf-8')).digest().encode('base64').strip()[:-1] class MCache(object): """a class holding metadata cache""" def __init__(self, mcache_db, uri_list, smart = -1): self.db_fn = mcache_db if not os.path.exists(mcache_db): create_new = True else: create_new = False self._cn = {} cn = self._getConnection() if create_new: cn.executescript(SQL_MCACHE_DATA_MODEL) cn.commit() self.__reload() if self.__create_cache(uri_list, smart) > 0: self.__reload() def _getConnection(self): n = threading.current_thread().name if self._cn.has_key(n): r = self._cn[n] else: r = sqlite3.connect(self.db_fn) r.row_factory = sqlite3.Row self._cn[n] = r return r def __reload(self): self.__meta = map(lambda i: dict(i), self._getConnection().execute(SQL_MCACHE_GET_BY_KITAB)) self.__meta_by_uri = (dict(map(lambda a: (a[1]['uri'], a[0]), enumerate(self.__meta)))) self.__meta_uri_list = self.__meta_by_uri.keys() self.__meta_by_kitab = {} for k,G in groupby(enumerate(self.__meta), lambda a: a[1]['kitab']): g = list(G) self.__meta_by_kitab[k] = map(lambda i: i[0], g) def load_from_uri(self, uri): """extract meta object from kitab's uri and return it""" cn = sqlite3.connect(uri) cn.row_factory=sqlite3.Row c = cn.cursor() try: r = c.execute(SQL_MCACHE_GET).fetchone() except sqlite3.OperationalError: return None if not r: return None return dict(r) def __cache(self, c, uri, meta = None): if not meta: meta = self.load_from_uri(uri) if not meta: return 0 #if drop_old_needed: meta['uri'] = uri meta['mtime'] = os.path.getmtime(toFs(uri)) meta['flags'] = 0 c.execute(SQL_MCACHE_ADD, meta) return 1 def __create_cache(self, uri_list, smart = -1): """ create cache and return the number of newly created meta caches smart is how fast you want to do that: * 0 force regeneration of entire meta cache * 1 regenerate cache when hash differs (it would need to open every kitab) * 2 regenerate when mtime differs * -1 do not update cache for exiting meta (even if the file is changed) """ cn = self._getConnection() c = cn.cursor() r = 0 uri_set = set(uri_list) #c.execute('BEGIN TRANSACTION') # remove meta for kitab that no longer exists deleted = filter(lambda i: i not in uri_set, self.__meta_uri_list) for uri in deleted: c.execute(SQL_MCACHE_DROP, (uri,)) r += 1 # update meta for the rest (in a smart way) for uri in uri_list: if not os.access(toFs(uri), os.R_OK): continue if smart == 0: # force recreation of cache, drop all, then create all r+=self.__cache(c, uri, uri in self.__meta_uri_list) continue meta = None drop_old_needed = False cache_needed = False if uri not in self.__meta_uri_list: cache_needed = True else: drop_old_needed = True cache_needed = True if smart == -1: continue # don't replace existing cache elif smart == 2: # rely of mtime if abs(os.path.getmtime(toFs(uri)) - self.getByUri(uri)['mtime']) < 1e-5: continue elif smart == 1: # rely on a hash saved inside the database old_meta = self.getByUri(uri) meta = self.load_from_uri(uri) if not meta or old_meta['hash'] == meta['hash']: continue if cache_needed: r += self.__cache(c, uri, meta) #c.execute('END TRANSACTION') cn.commit() return r def getKitabList(self): return self.__meta_by_kitab.keys() def getUriList(self): return self.__meta_by_uri.keys() def getByUri(self, uri): """return meta object for uri""" i = self.__meta_by_uri.get(uri,None) if i == None: return None return self.__meta[i] def getByKitab(self, kitab): """return a list of meta objects for a kitab""" a = self.__meta_by_kitab.get(kitab,None) if not a: return None return map(lambda i: self.__meta[i], a) def _latest(self, a): lm = a[0] l = metaVrr(lm) for m in a[1:]: v = metaVrr(m) if strverscmp(v, l) > 0: lm = m l = v return lm def getLatestKitab(self, kitab): """return a meta object for latest kitab (based on version)""" a = self.__meta_by_kitab.get(kitab, None) if not a: return None return self._latest([self.__meta[i] for i in a]) def getLatestKitabV(self, kitab, v): """ given kitab name and version return a meta object for latest kitab (based on version) """ a = self.__meta_by_kitab.get(kitab, None) if not a: return None ma = filter(lambda m: m[u'version'] == v,[self.__meta[i] for i in a]) if not ma: return None return self._latest(ma) def getLatestKitabVr(self, kitab, v, r): """ given kitab name and version and major release return a meta object for latest kitab (based on version) """ if type(r) != int: r = int(r) a = self.__meta_by_kitab.get(kitab, None) ma = filter(lambda m: m[u'version'] == v and m[u'releaseMajor'] == r, [self.__meta[i] for i in a]) if not ma: return None return self._latest(ma) def setIndexedFlags(self, uri, flags=2): cn = self._getConnection() cn.execute(SQL_MCACHE_SET_INDEXED, (flags, uri,)) cn.commit() def setAllIndexedFlags(self, flags=0): cn = self._getConnection() cn.execute(SQL_MCACHE_SET_ALL_INDEXED, (flags,)) cn.commit() def getUnindexedList(self): """ return a list of meta dicts for Kutub that are likely to be unindexed """ return map(lambda i: dict(i), self._getConnection().execute(SQL_MCACHE_GET_UNINDEXED)) def getDirtyIndexList(self): """ return a list of meta dicts for Kutub that are likely to have broken index """ return map(lambda i: dict(i), self._getConnection().execute(SQL_MCACHE_GET_DIRTY_INDEX)) def getIndexedList(self): """ return a list of meta dicts for Kutub that are already in index. """ return map(lambda i: dict(i), self._getConnection().execute(SQL_MCACHE_GET_INDEXED)) thawab-4.1/Thawab/platform.py000066400000000000000000000046131305262755200162430ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Platform specific routines of thawab Copyright © 2008-2010, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path from glob import glob if sys.platform == 'win32': def uri_to_filename(u): if len(u) <= 1: return u return u[1:].replace('/','\\') def get_drives(): return filter(lambda j: os.path.exists(j), [chr(i)+':\\' for i in range(67,91)]) try: from winpaths import get_appdata as application_data except ImportError: try: from winshell import application_data except ImportError: try: import win32com.shell as shell def application_data(): return shell.SHGetFolderPath(0, 26, 0, 0) except ImportError: application_data = None if application_data: app_data = application_data() th_conf = os.path.join(app_data, u"thawab", "conf", "main.conf") else: app_data = u"C:\\" th_conf = u"C:\\thawab.conf" else: app_data = u"/usr/share/" application_data = None def uri_to_filename(u): return u def get_drives(): return [] th_conf = os.path.expanduser('~/.thawab/conf/main.conf') def guess_prefixes(): l = [] ed = os.path.join(os.path.dirname(sys.argv[0]), u'thawab-data') ed_1st = False if os.path.isdir(ed) and os.access(ed, os.W_OK): l.append(ed) ed_1st = True if sys.platform == 'win32': l.append(os.path.join(app_data,'thawab')) if not ed_1st: l.append(ed) l.extend([os.path.join(d, 'thawab-data') for d in get_drives()]) else: l.append(os.path.expanduser('~/.thawab')) if not ed_1st: l.append(ed) l.append(u'/usr/local/share/thawab') l.append(u'/usr/share/thawab') return l thawab-4.1/Thawab/shamelaUtils.py000066400000000000000000001237231305262755200170560ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The shamela related tools for thawab Copyright © 2008-2009, Muayyad Saleh Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path import re import sqlite3 import bisect from okasha.utils import cmp_bisect_right from subprocess import Popen,PIPE from itertools import groupby,imap from meta import MCache, prettyId, makeId schema = { 'main':"bkid INTEGER, bk TEXT, shortname TEXT, cat INTEGER, betaka TEXT, inf TEXT, bkord INTEGER DEFAULT -1, authno INTEGER DEFAULT 0, auth TEXT, authinfo TEXT, higrid INTEGER DEFAULT 0, ad INTEGER DEFAULT 0, islamshort INTEGER DEFAULT 0, blnk TEXT", 'men': "id INTEGER, arrname TEXT, isoname TEXT, dispname TEXT", 'shorts': "bk INTEGER, ramz TEXT, nass TEXT", 'mendetail': "spid INTEGER PRIMARY KEY, manid INTEGER, bk INTEGER, id INTEGER, talween TEXT", 'shrooh': "matn INTEGER, matnid INTEGER, sharh INTEGER, sharhid INTEGER, PRIMARY KEY (sharh, sharhid)", 'cat':"id INTEGER PRIMARY KEY, name Text, catord INTEGER, lvl INTEGER", 'book':"id, nass TEXT, part INTEGER DEFAULT 0, page INTEGER DEFAULT 0, hno INTEGER DEFAULT 0, sora INTEGER DEFAULT 0, aya INTEGER DEFAULT 0, na INTEGER DEFAULT 0, blnk TEXT", 'toc': "id INTEGER, tit TEXT, lvl INTEGER DEFAULT 1, sub INTEGER DEFAULT 0" } schema_index = { 'main':"CREATE INDEX MainBkIdIndex on main (bkid);", 'men': "CREATE INDEX MenIdIndex on men (id); CREATE INDEX MenIsoNameIndex on men (isoname);", 'shorts': "CREATE INDEX ShortsIndex on shorts (bk,ramz);", 'mendetail': "CREATE INDEX MenDetailSpIdIndex on mendetail (spid);", 'shrooh': "CREATE INDEX ShroohIndex on shrooh (sharhid);", 'book':"CREATE INDEX Book%(table)sIdIndex on %(table)s (id);", 'toc': "CREATE INDEX Toc%(table)sIdIndex on %(table)s (id);" } hashlen = 32 # must be divisible by 4 # some mark to know how and where to cut mark = "-- CUT HERE STUB (%s) BUTS EREH TUC --\n" % \ os.urandom(hashlen*3/4).encode('base64')[:hashlen] table_cols = dict(map(lambda tb: (tb, map(lambda i: i.split()[0], schema[tb].split(','))), schema.keys())) table_col_defs = dict(map(lambda tb: (tb, dict(map(lambda i: (i.strip().split()[0], i.strip()), schema[tb].split(',')))), schema.keys())) # transformations dos2unix_tb = {13: 10} normalize_tb = { 65: 97, 66: 98, 67: 99, 68: 100, 69: 101, 70: 102, 71: 103, 72: 104, 73: 105, 74: 106, 75: 107, 76: 108, 77: 109, 78: 110, 79: 111, 80: 112, 81: 113, 82: 114, 83: 115, 84: 116, 85: 117, 86: 118, 87: 119, 88: 120, 89: 121, 90: 122, 1600: None, 1569: 1575, 1570: 1575, 1571: 1575, 1572: 1575, 1573: 1575, 1574: 1575, 1577: 1607, 1611: None, 1612: None, 1613: None, 1614: None, 1615: None, 1616: None, 1617: None, 1618: None, 1609: 1575} spaces='\t\n\r\f\v' spaces_d = dict(map(lambda s: (ord(s),32),list(spaces))) schema_fix_del = re.compile('\(\d+\)') # match digits in parenthesis (after types) to be removed schema_fix_text = re.compile('Memo/Hyperlink',re.I) schema_fix_int = re.compile('(Boolean|Byte|Byte|Numeric|Replication ID|(\w+ )?Integer)',re.I) sqlite_cols_re = re.compile("\((.*)\)",re.M | re.S) no_sql_comments=re.compile('^--.*$',re.M) shamela_footers_re = re.compile(u'^(¬?_{4,})$',re.M) digits_re = re.compile(r'\d+') no_w_re = re.compile(ur'[^A-Za-zابتثجحخدذرزسشصضطظعغفقكلمنهوي\s]') # one to one transformations that does not change chars order sh_digits_to_spaces_tb = { 48:32, 49:32, 50:32, 51:32, 52:32, 53:32, 54:32, 55:32, 56:32, 57:32 } sh_normalize_tb = { 65: 97, 66: 98, 67: 99, 68: 100, 69: 101, 70: 102, 71: 103, 72: 104, 73: 105, 74: 106, 75: 107, 76: 108, 77: 109, 78: 110, 79: 111, 80: 112, 81: 113, 82: 114, 83: 115, 84: 116, 85: 117, 86: 118, 87: 119, 88: 120, 89: 121, 90: 122, 1569: 1575, 1570: 1575, 1571: 1575, 1572: 1575, 1573: 1575, 1574: 1575, 1577: 1607, 1609: 1575, 8: 32, 1600:32, 1632: 48, 1633: 49, 1634: 50, 1635: 51, 1636: 52, 1637: 53, 1638: 54, 1639: 55, 1640: 56, 1641: 57, 1642:37, 1643:46 } # TODO: remove unused variables and methods # shorts std_shorts={ u'A': u'صلى الله عليه وسلم', u'B': u'رضي الله عن', u'C': u'رحمه الله', u'D': u'عز وجل', u'E': u'عليه الصلاة و السلام', } footnotes_cnd = [] # candidate, in the form of (footnote_mark, footnote_text) tuples footnotes =[] def footer_shift_cb(mi): global footnotes_cnd, footnotes if footnotes_cnd and footnotes_cnd[0][0] == mi.group(1): # int(mi.group(1)) footnotes.append(footnotes_cnd.pop(0)) return " ^[" + str(len(footnotes)) + "]" return mi.group(0) class ShamelaSqlite(object): mode=None def __init__(self, src, cn = None, releaseMajor = 0, releaseMinor = 0, progress = None, progress_args = [], progress_kw = {}, progress_dict = None): """import the bok file into sqlite""" self.releaseMajor = releaseMajor self.releaseMinor = releaseMinor self.progress = progress self.progress_args = progress_args self.progress_kw = progress_kw self.progress_dict = progress_dict self.tables = None self.tablesFn = {} self.src_is_dir = False if os.path.isdir(src): self.sh_prefix = src self.src_is_dir = True elif os.path.isfile(src): self.bok_fn = src else: raise OSError self.metaById = {} self._blnk = {} self.xref = {} self.encoding_fix_needed = None # True/False or None ie. not yet checked self.__bkids = None self.__commentaries = None self.version, self.tb, self.bkids = self.identify() if self.progress_dict == None: self.progress_dict = {} # note: the difference between tb and self.tables that tables are left as reported by mdbtoolds while tb are lower-cased self.cn = cn or sqlite3.connect(':memory:', isolation_level = None) self.cn.row_factory = sqlite3.Row self.c = self.cn.cursor() self.imported_tables = [] self.__meta_by_bkid = {} def set_xref(self, bkid, pg_id, xref): if self.xref.has_key(bkid): self.xref[bkid].append( (pg_id, xref,) ) else: self.xref[bkid] = [ (pg_id, xref,) ] def get_xref(self, bkid, pg_id): if self.xref.has_key(bkid): i = cmp_bisect_right( lambda a,b: cmp(a[0], b), self.xref[bkid], pg_id) if i > 0: return self.xref[bkid][i-1][1] return None def identify(self): tables = self.getTables() # Note: would raise OSError or TypeError if len(tables) == 0: raise TypeError tables.sort() tb = dict(map(lambda s: (s.lower(),s), tables)) if 'book' in tables and 'title' in tables: return (2,tb,[]) bkid = map(lambda i:int(i[1:]), filter(lambda i: i[0] == 'b' and i[1:].isdigit(), tables)) bkid.sort() return (3, tb, bkid) def _getTablesInFile(self, fn): try: p = Popen(['mdb-tables', '-1', fn], 0, stdout = PIPE, env = {'MDB_JET3_CHARSET':'cp1256', 'MDB_ICONV':'UTF-8'}) except OSError: raise try: tables = p.communicate()[0].replace('\r','').strip().split('\n') except OSError: raise r = p.returncode del p if r != 0: raise TypeError tables = filter(lambda t: not t.isdigit(), tables) return tables def _getTablesInBok(self): if self.tables: return self.tables self.tables = self._getTablesInFile(self.bok_fn) self.tablesFn = dict(((t,self.bok_fn) for t in self.tables)) return self.tables def _getTablesInDir(self): if self.tables: return self.tables self.tables = [] for f in ("main.mdb", "special.mdb"): fn = os.path.join(self.sh_prefix, "Files", f) tb = self._getTablesInFile(fn) self.tables.extend(tb) self.tablesFn.update(dict(((t,fn) for t in tb))) return self.tables def _getTableFile(self, tb): if self.src_is_dir: return self.tablesFn[tb] return self.bok_fn def getTables(self): if self.tables: return self.tables if self.src_is_dir: return self._getTablesInDir() return self._getTablesInBok() def __shamela3_fix_insert(self, sql_cmd, prefix = "OR IGNORE INTO tmp_"): """Internal function used by importTable""" if prefix and sql_cmd[0].startswith('INSERT INTO '): sql_cmd[0] = 'INSERT INTO ' + prefix + sql_cmd[0][12:] sql = ''.join(sql_cmd) self.c.execute(sql) def __schemaGetCols(self, r): """used internally by importTableSchema""" m = sqlite_cols_re.search( no_sql_comments.sub('', r) ) if not m: return [] return map(lambda i: i.split()[0], m.group(1).split(',')) def importTableSchema(self, Tb, tb, is_tmp = False,prefix = 'tmp_'): """create schema for table""" if is_tmp: temp = 'temp' else: temp = '' fn = self._getTableFile(Tb) opts=['mdb-schema', '-S','-T', Tb, fn] e="" if self.mode==None or self.mode=='0.6': self.mode='0.6' print "MODE 0.6" pipe = Popen(opts, 0, stdout = PIPE, stderr = PIPE, env = {'MDB_JET3_CHARSET':'cp1256','MDB_ICONV':'UTF-8'}) r,e = pipe.communicate() print e r=r.replace('\r', '') #if pipe.returncode != 0: #raise TypeError if self.mode=='0.7' or ((e.startswith("mdb-schema: invalid option") or e.startswith("option parsing failed: Unknown option")) and opts[1]=='-S'): print "MODE 0.7" del opts[1] self.mode='0.7' pipe = Popen(opts, 0, stdout = PIPE, env = {'MDB_JET3_CHARSET':'cp1256','MDB_ICONV':'UTF-8'}) r = pipe.communicate()[0].replace('\r', '').replace('[', '').replace(']', '') if pipe.returncode != 0: raise TypeError sql = schema_fix_text.sub('TEXT', schema_fix_int.sub('INETEGER', schema_fix_del.sub('',r))).lower() sql = sql.replace('create table ', ' '.join(('create ', temp, ' table ', prefix,))) sql = sql.replace('drop table ', 'drop table if exists ' + prefix) cols = self.__schemaGetCols(sql) if table_cols.has_key(tb): missing = filter(lambda i: not i in cols,table_cols[tb]) missing_def = u', '.join(map(lambda i: table_col_defs[tb][i], missing)) else: missing = [] missing_def = u'' if missing_def: sql = sql.replace('\n)',',' + missing_def + '\n)') sql += schema_index.get(tb,'') % {'table': Tb.lower()} sql_l = no_sql_comments.sub('', sql).split(';') for l in sql_l: l = l.strip() if l: try: self.c.execute(l) except: print l raise def importTable(self, Tb, tb, tb_prefix = None, is_tmp = False, is_ignore = False, is_replace = False): """ import a table where: * Tb is the case-sesitive table name found reported in mdbtools. * tb is the name in our standard schema, usually tb = Tb.lower() except for book and toc where its Tb is b${bok_id}, t${bok_id} * tb_prefix a prefix added to tb [default is tmp_ if is_tmp otherwise it's ''] """ tb_prefix = is_tmp and 'tmp_' or '' if Tb in self.imported_tables: return self.importTableSchema(Tb, tb, is_tmp, tb_prefix) fn = self._getTableFile(Tb) if self.mode=='0.6': opts=['mdb-export', '-R',';\n'+mark,'-I', fn, Tb] else: opts=['mdb-export', '-R','\n'+mark,'-I', 'postgres', fn, Tb] print "** opts: ",opts print "** mode: ", self.mode pipe = Popen(opts, 0, stdout = PIPE, env = {'MDB_JET3_CHARSET':'cp1256', 'MDB_ICONV':'UTF-8'}) sql_cmd = [] prefix = "" if is_ignore: prefix = "OR IGNORE INTO " elif is_replace: prefix = "OR REPLACE INTO " prefix += tb_prefix for l in pipe.stdout: l = l.replace('\r','\n') # output encoding in mdbtools in windows is cp1256, this is a bug in it if self.encoding_fix_needed == None: try: l.decode('UTF-8') except: self.encoding_fix_needed = True l = l.decode('cp1256') else: self.encoding_fix_needed = False elif self.encoding_fix_needed: l = l.decode('cp1256') if l == mark: self.__shamela3_fix_insert(sql_cmd,prefix) sql_cmd = [] else: sql_cmd.append(l) if len(sql_cmd): self.__shamela3_fix_insert(sql_cmd,prefix); sql_cmd = [] pipe.wait() # TODO: why is this needed if pipe.returncode != 0: raise TypeError del pipe self.imported_tables.append(Tb) def toSqlite(self, in_transaction = True, bkids=None): """ return True if success, or False if canceled """ if in_transaction: self.c.execute('BEGIN TRANSACTION') tables = self.getTables() is_special = lambda t: (t.lower().startswith('t') or \ t.lower().startswith('b')) and \ t[1:].isdigit() is_not_special = lambda t: not is_special(t) s_tables = filter(is_special, tables) g_tables = filter(is_not_special, tables) if bkids: # filter bkids in s_tables s_tables =filter(lambda t: int(t[1:]) in bkids, s_tables) progress_delta = 1.0 / (len(s_tables) + len(g_tables)) * 100.0 progress = 0.0 for t in g_tables: if self.progress_dict.get('cancel', False): return False if self.progress: self.progress("importing table [%s]" % t, progress, *self.progress_args, **self.progress_kw) progress += progress_delta self.importTable(t, t.lower()) for t in s_tables: if self.progress_dict.get('cancel', False): return False if self.progress: self.progress("importing table [%s]" % t, progress, *self.progress_args, **self.progress_kw) progress += progress_delta if t.lower().startswith('t'): self.importTable(t, 'toc') else: self.importTable(t, 'book') progress = 100.0 if self.progress: self.progress("finished, committing ...", progress, *self.progress_args, **self.progress_kw) if in_transaction: self.c.execute('END TRANSACTION') self.__getCommentariesHash() return True def __getCommentariesHash(self): if self.__commentaries != None: return self.__commentaries self.__commentaries={} for a in self.c.execute('SELECT DISTINCT matn, sharh FROM shrooh'): try: r = (int(a[0]),int(a[1])) # fix that some books got string bkids not integer except ValueError: continue # skip non integer book ids if self.__commentaries.has_key(r[0]): self.__commentaries[r[0]].append(r[1]) else: self.__commentaries[r[0]] = [r[1]] for i in self.getBookIds(): if not self.__commentaries.has_key(i): self.__commentaries[i] = [] return self.__commentaries def authorByID(self, authno, main_tb = {}): # TODO: use authno to search shamela specific database a, y = '_unset',0 if main_tb: a = makeId(main_tb.get('auth','') or '') y = main_tb.get('higrid',0) or 0 if not y: y = main_tb.get('ad',0) or 0 if isinstance(y,basestring) and y.isdigit(): y = int(y) else: m = digits_re.search(unicode(y)) if m: y = int(m.group(0)) else: y = 0 return a, y def classificationByBookId(self, bkid): return '_unset' def getBookIds(self): if self.__bkids != None: return self.__bkids r = self.c.execute('SELECT bkid FROM main') self.__bkids = map(lambda a: a[0],r.fetchall() or []) if self.__commentaries != None: # sort to make sure we import the book before its commentary self.__bkids.sort(lambda a,b: (int(a in self.__commentaries.get(b,[])) << 1) - 1) return self.__bkids def _is_tafseer(self, bkid): r = self.c.execute('''SELECT sora, aya FROM b%d WHERE sora>0 and sora <115 and aya>0 LIMIT 1''' % bkid).fetchone() return bool(r) def _get_matn(self, sharh_bkid): r = self.c.execute('''SELECT matn, matnid, sharh, sharhid FROM shrooh WHERE sharh = ? LIMIT 1''', (sharh_bkid, ) ).fetchone() if not r: return -1 return int(r['matn']) def getBLink(self, bkid): if not self._blnk.has_key(bkid): r = self.c.execute('SELECT blnk FROM main WHERE bkid = ?', (bkid,)).fetchone() self._blnk[bkid] = r['blnk'] return self._blnk[bkid] def getBookMeta(self, bkid): if self.__meta_by_bkid.has_key(bkid): return self.__meta_by_bkid[bkid] else: r = self.c.execute('SELECT bk, shortname, cat, betaka, inf, bkord, authno, auth, higrid, ad, islamshort FROM main WHERE bkid = ?', (bkid,)).fetchone() if not r: m = None else: r = dict(r) # FIXME: make "releaseMajor" "releaseMinor" integers m = { "repo":"_user", "lang":"ar", "type": int(self._is_tafseer(bkid)), "version":"0."+str(bkid), "releaseMajor":0, "releaseMinor":0, 'originalKitab':None, 'originalVersion':None, 'originalAuthor':None, 'originalYear':None } m['kitab'] = makeId(r['bk']) m['author'], m['year'] = self.authorByID(r['authno'], r) m['classification'] = self.classificationByBookId(bkid) m['keywords'] = u'' matn_bkid = self._get_matn(bkid) #print "%d is sharh for %d" % (bkid, matn_bkid) if matn_bkid>0: matn_m = self.getBookMeta(matn_bkid) if matn_m: m['originalKitab'] = matn_m['kitab'] m['originalVersion'] = matn_m['version'] m['originalAuthor'] = matn_m['author'] m['originalYear'] = matn_m['year'] self.__meta_by_bkid[bkid] = m return m class _foundShHeadingMatchItem(): def __init__(self, start, end = -1, txt = '', depth = -1, fuzzy = -1): self.start = start self.end = end self.txt = txt self.depth = depth self.fuzzy = fuzzy self.suffix = '' def __repr__(self): return (u"".format(self.start, self.end, self.txt)).encode('utf-8') def overlaps_with(self,b): return b.end > self.start and self.end > b.start def __cmp__(self, b): return cmp(self.start, b.start) def _fixHeadBounds(pg_txt, found): for i, f in enumerate(found): if f.fuzzy >= 4: # then the heading is part of some text f.end = f.start f.suffix = u'\u2026' if f.fuzzy >= 7: #then move f.start to the last \n f.end = max(pg_txt[:f.end].rfind('\n'), 0) if i > 0: f.end = max(f.end,found[i-1].end) f.start = min(f.start, f.end) def reformat(txt, shorts_t, shorts_dict): txt = txt.replace('\n', '\n\n') if shorts_t & 1: for k in std_shorts: txt = txt.replace(k, std_shorts[k]) for k in shorts_dict: txt = txt.replace(k, "\n====== %s ======\n\n" % shorts_dict[k]) return txt def set_get_xref(xref, h_tags, sh, bkid, pg_id, matn, matnid): h_tags['header'] = xref sh.set_xref(bkid, pg_id, xref) if matn and matnid and sh.metaById.has_key(matn): m = sh.metaById[matn] xref = sh.get_xref(matn, matnid) if xref: h_tags['embed.original.section'] = xref ss_re = re.compile(" +") re_ss_re = re.compile("( \*){2,}") def ss(txt): """squeeze spaces""" return ss_re.sub(" ", txt) def re_ss(txt): """squeeze spaces in re""" return re_ss_re.sub(" *", ss(txt)) def shamelaImport(cursor, sh, bkid, footnote_re = ur'\((\d+)\)', body_footnote_re = ur'\((\d+)\)', ft_prefix_len = 1, ft_suffix_len = 1): """ import a ShamelaSqlite book as thawab kitab object, where * cursor - a cursor for an empty thawab kitab object * sh - ShamelaSqlite object * bkid - the id of the shamela book to be imported this function returns the cached meta dictionary """ global footnotes_cnd, footnotes shamela_footer_re = re.compile(footnote_re, re.M | re.U) shamela_shift_footers_re = re.compile(body_footnote_re, re.M | re.U) ki = cursor.ki # NOTE: page id refers to the number used as id in shamela not thawab c = sh.c # step 0: prepare shorts shorts_t = c.execute("SELECT islamshort FROM main WHERE bkid = ?", (bkid,)).fetchone() if shorts_t: shorts_t = shorts_t[0] or 0 else: shorts_t = 0 if shorts_t > 1: shorts_dict = dict(c.execute("SELECT ramz,nass FROM shorts WHERE bk = ?", (bkid,)).fetchall()) else: shorts_dict = {} # step 1: import meta meta = sh.getBookMeta(bkid) ki.setMCache(meta) # step 2: prepare topics hashed by page_id r = c.execute("SELECT id,tit,lvl FROM t%d ORDER BY id,sub" % bkid).fetchall() # NOTE: we only need page_id,title and depth, sub is only used to sort them toc_ls = filter(lambda i: i[2] and i[1], [list(i) for i in r]) if not toc_ls: raise TypeError # no text in the book if toc_ls[0][0] != 1: toc_ls.insert(0, [1, sh.getBookMeta(bkid)['kitab'].replace('_',' '), toc_ls[0][2]]) toc_hash = map(lambda i: (i[1][0],i[0]),enumerate(toc_ls)) # toc_hash.sort(lambda a,b: cmp(a[0],b[0])) # FIXME: this is not needed! toc_hash = dict(map(lambda j: (j[0],map(lambda k:k[1], j[1])), groupby(toc_hash, lambda i: i[0]))) # NOTE: toc_hash[pg_id] holds list of indexes in toc_ls found = [] parents = [ki.root] depths = [-1] # -1 is used to indicate depth or level as shamela could use 0 last = u'' started = False rm_fz4_re = re.compile(ur'(?:[^\w\n]|[_ـ])',re.M | re.U) # [\W_ـ] without \n rm_fz7_re = re.compile(ur'(?:[^\w\n]|[\d_ـ])',re.M | re.U) # [\W\d_ـ] without \n def _shamelaFindHeadings(page_txt, page_id, d, h, headings_re, heading_ix, j, fuzzy): # fuzzy is saved because it could be used later to figure whither to add newline # or to move start point for m in headings_re.finditer(page_txt): # # NOTE: since this is not exact, make it ends at start. FIXME: it was m.end() candidate = _foundShHeadingMatchItem(m.start(), m.start(), h, d, fuzzy) ii = bisect.bisect_left(found, candidate) # only check for overlaps in found[ii:] # skip matches that overlaps with previous headings if any(imap(lambda mi: mi.overlaps_with(candidate),found[ii:])): continue bisect.insort(found, candidate) # add the candidate to the found list toc_hash[page_id][j] = None return True return False def _shamelaFindExactHeadings(page_txt, page_id, f, d, heading, heading_ix,j, fuzzy): shift = 0 s = f % page_txt h = f % heading #print "*** page:", s #print "*** h:", page_id, heading_ix, fuzzy, "[%s]" % h.encode('utf-8') l = len(heading) while(True): i = s.find(h) if i >= 0: # print "found" candidate = _foundShHeadingMatchItem(i+shift, i+shift+l, h, d, fuzzy) # only check for overlaps in found[ii:] ii = bisect.bisect_left(found, candidate) # skip matches that overlaps with previous headings if not any(imap(lambda mi: mi.overlaps_with(candidate),found[ii:])): # add the candidate to the found list bisect.insort(found, candidate) toc_hash[page_id][j] = None return True # skip to i+l s = s[i+l:] shift += i + l # not found: return False return False def _shamelaHeadings(page_txt, page_id): l = toc_hash.get(page_id, []) if not l: return txt = None txt_no_d = None # for each heading for j, ix in enumerate(l): h, d = toc_ls[ix][1:3] # search for entire line matches # (exact, then only letters and digits then only letters: 1,2,3) # search for leading matches # (exact, then only letters and digits then only letters: 4,5,6) # search for matches anywhere # (exact, then only letters and digits then only letters: 7,8,9) if _shamelaFindExactHeadings(page_txt, page_id, "\n%s\n", d, h, ix, j, 1): continue if not txt: txt = no_w_re.sub(' ', page_txt.translate(sh_normalize_tb)) h_p = no_w_re.sub(' ', h.translate(sh_normalize_tb)).strip() if h_p: # if normalized h_p is not empty # NOTE: no need for map h_p on re.escape() because it does not contain special chars h_re_entire_line = re.compile(re_ss(ur"^\s*%s\s*$" % ur" *".join(list(h_p))), re.M) if _shamelaFindHeadings(txt, page_id, d, h, h_re_entire_line, ix, j, 2): continue if not txt_no_d: txt_no_d = txt.translate(sh_digits_to_spaces_tb) h_p_no_d = h_p.translate(sh_digits_to_spaces_tb).strip() if h_p_no_d: h_re_entire_line_no_d = re.compile(re_ss(ur"^\s*%s\s*$" % \ ur" *".join(list(h_p_no_d))), re.M) if _shamelaFindHeadings(txt_no_d, page_id, d, h, h_re_entire_line_no_d, ix, j, 3): continue # at the beginning of the line if _shamelaFindExactHeadings(page_txt, page_id, "\n%s", d, h, ix, j, 4): continue if h_p: h_re_line_start = re.compile(re_ss(ur"^\s*%s\s*" % ur" *".join(list(h_p))), re.M) if _shamelaFindHeadings(txt, page_id, d, h, h_re_line_start, ix, j, 5): continue if h_p_no_d: h_re_line_start_no_d = re.compile(re_ss(ur"^\s*%s\s*" % \ ur" *".join(list(h_p_no_d))), re.M) if _shamelaFindHeadings(txt_no_d, page_id, d, h, h_re_line_start_no_d, ix, j, 6): continue # any where in the line if _shamelaFindExactHeadings(page_txt, page_id, "%s", d, h, ix,j, 7): continue if h_p: h_re_any_ware = re.compile(re_ss(ur"\s*%s\s*" % \ ur" *".join(list(h_p))), re.M) if _shamelaFindHeadings(txt, page_id, d, h, h_re_any_ware, ix, j, 8): continue if h_p_no_d: h_re_any_ware_no_d = re.compile(re_ss(ur"\s*%s\s*" % \ ur" *".join(list(h_p_no_d))), re.M) if _shamelaFindHeadings(txt_no_d, page_id, d, h, h_re_any_ware, ix, j, 9): continue # if we reached here then head is not found # place it just after last one if found: last_end = found[-1].end #try: last_end += page_txt[last_end:].index('\n')+1 #except ValueError: last_end = len(page_txt); print "*" #print "last_end = ",last_end else: last_end = 0 candidate = _foundShHeadingMatchItem(last_end, last_end, h, d, 0) bisect.insort(found, candidate) # add the candidate to the found list del toc_hash[page_id] return footnotes_cnd = [] footnotes = [] h_tags = {} t_tags0 = {'textbody':None} t_tags = t_tags0.copy() last_hno = None hno_pop_needed = False def pop_footers(ft): s = "\n\n".join(map(lambda (i,a): " * (%d) %s" % (i + 1, a[1]), enumerate(ft))) del ft[:] return s # step 3: walk through pages, accumulating contents # NOTE: in some books id need not be unique # blnk_base = sh.getBLink(bkid) blnk = "" blnk_old = "" r = c.execute('SELECT rowid FROM b%d ORDER BY rowid DESC LIMIT 1' % bkid).fetchone() r_max = float(r['rowid'])/100.0 for r in c.execute('SELECT b%d.rowid,id,nass,part,page,hno,sora,aya,na,matn,matnid,blnk FROM b%d LEFT OUTER JOIN shrooh ON shrooh.sharh = %d AND id=shrooh.sharhid ORDER BY id' % (bkid,bkid,bkid,)): if sh.progress_dict.get('cancel', False): return None # FIXME: since we are using ORDER BY id, then using rowid for progress is not always correct sh.progress("importing book [%d]" % bkid, r['rowid']/r_max, *sh.progress_args, **sh.progress_kw) if r['nass']: pg_txt = r['nass'].translate(dos2unix_tb).strip() else: pg_txt = u"" pg_id = r['id'] hno = r['hno'] blnk_old = blnk blnk = r['blnk'] try: matn = r['matn'] and int(r['matn']) matnid = r['matnid'] and int(r['matnid']) except ValueError: matn,matnid = None,None except TypeError: matn,matnid = None,None sura, aya, na = 0, 0, 0 if r['sora'] and r['aya'] and r['sora'] > 0 and r['aya'] > 0: sura, aya, na = r['sora'], r['aya'], r['na'] if not na or na <= 0: na = 1 h_tags['quran.tafseer.ref'] = "%03d-%03d-%03d" % (sura, aya, na) # split pg_txt into pg_body and pg_footers_txt m = shamela_footers_re.search(pg_txt) if m: i = m.start() pg_body = pg_txt[:i].strip() pg_footers_txt = pg_txt[m.end()+1:].strip() # A = [(mark, offset_of_num, offset_of_text)] A = [(fm.group(1), fm.start(), fm.start() + \ len(fm.group(1)) + \ ft_prefix_len + \ ft_suffix_len) \ for fm in shamela_footer_re.finditer(pg_footers_txt)] # fixme it need not be +2 if A: pg_footers_continue = pg_footers_txt[:A[0][1]].strip() B = [] for i, (j, k, l) in enumerate(A[:-1]): # TODO: do we need to check if j is in right order B.append([j, pg_footers_txt[l:A[i + 1][1]].strip()]) j, k, l = A[-1] B.append([j,pg_footers_txt[l:].strip()]) last_digit = 0 for i, j in B: if i.isdigit(): if int(i) == last_digit + 1: footnotes_cnd.append([i, j]) last_digit = int(i) elif footnotes_cnd: footnotes_cnd[-1][1] += " (%s) %s" % (i, j) else: pg_footers_continue += "(%s) %s" % (i, j) else: footnotes_cnd.append([i, j]) if pg_footers_continue: # FIXME: should this be footnotes or footnotes_cnd if footnotes: footnotes[-1][1] += " " + pg_footers_continue else: # NOTE: an excess footnote without previous footnotes to add it to print " * warning: an excess text in footnotes in pg_id = ", pg_id pg_body += "\n\n==========\n\n" + \ pg_footers_continue + \ "\n\n==========\n\n" # NOTE: t_tags is used since h_tags was already committed t_tags["request.fix.footnote"] = "shamela import warning: excess text in footnotes" else: pg_body = pg_txt # debug stubs #if pg_id == 38: # print "pg_body = [%s]\n" % pg_body # for j,k in footnotes_cnd: # print "j = [%s] k = [%s]" % (j,k) # # raise KeyError if toc_hash.has_key(pg_id): hno_pop_needed = False elif hno != None and hno != last_hno: # FIXME: make it into a new head last_hno = hno # commit anything not commited if footnotes: last += "\n\n__________\n" + pop_footers(footnotes) cursor.appendNode(parents[-1], reformat(last, shorts_t, shorts_dict), t_tags) t_tags = t_tags0.copy() last = "" # create a new node set_get_xref(unicode(hno), h_tags, sh, bkid, pg_id, matn, matnid) h_tags[u'request.fix.head'] = u'shamela import warning: automatically generated head' # FIXME: handle the case of a new hno on the beginning of a chapter if hno_pop_needed: parents.pop() depths.pop() # FIXME: how many time to pop ? else: hno_pop_needed = True parent = cursor.appendNode(parents[-1], unicode(hno), h_tags) h_tags = {} parents.append(parent) depths.append(depths[-1] + 0.5) # FIXME: does this hack work? # TODO: set the value of header tag to be a unique reference # TODO: keep part,page,hno,sora,aya,na somewhere in the imported document # TODO: add special handling for hadeeth number and tafseer info found = [] # step 4: for each page content try to find all headings _shamelaHeadings(pg_body, pg_id) # now we got all headings in found # step 5: add the found headings and its content # splitting page text pg_body into [:f0.start] [f0.end:f1.start] [f1.end:f2.start]...[fn.end:] # step 5.1: add [:f0.start] to the last heading contents and push it if not found: # if no new heading in this page, add it to be committed later last += shamela_shift_footers_re.sub(footer_shift_cb, pg_body) if footnotes_cnd: print " * fixing stall footnotes at pg_id = ", pg_id last += " ".join(map(lambda (j,k): "(%s) %s" % (j,k),footnotes_cnd)) del footnotes_cnd[:] continue # here some new headings were found _fixHeadBounds(pg_body, found) # commit the body of previous heading first if started: if blnk_old and blnk_base: last += u"\n\n[[%s]]\n\n" % (blnk_base+blnk_old) blnk_old = None last += shamela_shift_footers_re.sub(footer_shift_cb, pg_body[:found[0].start]) if footnotes_cnd: print " ** stall footnotes at pg_id = ", pg_id #for j,k in footnotes_cnd: # print "j = [%s] k = [%s]" % (j,k) #raise if footnotes: last += "\n\n__________\n" + pop_footers(footnotes) cursor.appendNode(parents[-1], reformat(last, shorts_t, shorts_dict), t_tags) t_tags = t_tags0.copy() last = "" # step 5.2: same for all rest segments [f0.end:f1.start],[f1.end:f2.start]...[f(n-1).end:fn.start] for i,f in enumerate(found[:-1]): while(depths[-1] >= f.depth): depths.pop() parents.pop() started = True # FIXME: pg_id won't be unique, add a counter like "_p5", "_p5.2", ..etc set_get_xref(u"_p" + unicode(pg_id), h_tags, sh, bkid, pg_id, matn, matnid) if f.fuzzy == 0: h_tags[u'request.fix.head'] = u'shamela import error: missing head' parent = cursor.appendNode(parents[-1], f.txt+f.suffix, h_tags) h_tags = {} parents.append(parent) depths.append(f.depth) last = shamela_shift_footers_re.sub(footer_shift_cb, pg_body[f.end:found[i+1].start]) if footnotes: last += "\n\n__________\n" + pop_footers(footnotes) parent = cursor.appendNode(parent, reformat(last, shorts_t, shorts_dict), t_tags) t_tags = t_tags0.copy() # step 5.3: save [fn.end:] as last heading f = found[-1] while(depths[-1] >= f.depth): depths.pop() parents.pop() # FIXME: pg_id won't be unique, add a counter like "_p5", "_p5.2", ..etc set_get_xref(u"_p"+unicode(pg_id), h_tags, sh, bkid, pg_id, matn, matnid) txt_start = f.end if f.fuzzy == 0: h_tags[u'request.fix.head'] = u'shamela import error: missing header' parent = cursor.appendNode(parents[-1], f.txt+f.suffix,h_tags) h_tags={} started = True parents.append(parent) depths.append(f.depth) #last = pg_body[f.end:]+'\n' last = shamela_shift_footers_re.sub(footer_shift_cb, pg_body[f.end:]+'\n') if footnotes_cnd: last += "\n==========[\n" + \ pop_footers(footnotes_cnd) + \ "\n]==========\n" if not started: raise TypeError if blnk and blnk_base: last += u"\n\n[[%s]]\n\n" % (blnk_base + blnk) blnk = None if last: if footnotes: last += "\n\n__________\n" + pop_footers(footnotes) cursor.appendNode(parents[-1], reformat(last, shorts_t, shorts_dict), t_tags) t_tags=t_tags0.copy() # l should be empty because we have managed missing headers #l = filter(lambda i: i,toc_hash.values()) #for j in l: print j #print "*** headings left: ",len(l) sh.metaById[bkid] = meta sh.progress("importing book [%d]" % bkid, 100.0, *sh.progress_args, **sh.progress_kw) return meta if __name__ == '__main__': # input bok_fn, dst th = ThawabMan(os.path.expanduser('~/.thawab')) sh = ShamelaSqlite(bok_fn) sh.toSqlite() for bok_id in sh.getBokIds(): ki = th.mktemp() c = ki.seek(-1,-1) meta = shamelaImport(c, cn, bok_id) c.flush() o = ki.uri n = meta['kitab'] del ki shutil.move(o, os.path.join(dst, n)) thawab-4.1/Thawab/stemming.py000066400000000000000000000074041305262755200162430ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Copyright © 2008, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path, re #harakat = "ًٌٍَُِّْـ".decode('utf-8') #normalize_tb = dict(map(lambda i: (ord(i),None),list(harakat))) #normalize_tb[ord('ة'.decode('utf-8'))] = ord('ه'.decode('utf-8')) #for i in list("ىئإؤأآء".decode('utf-8')): # normalize_tb[ord(i)] = ord('ا'.decode('utf-8')) normalize_tb = { 65: 97, 66: 98, 67: 99, 68: 100, 69: 101, 70: 102, 71: 103, 72: 104, 73: 105, 74: 106, 75: 107, 76: 108, 77: 109, 78: 110, 79: 111, 80: 112, 81: 113, 82: 114, 83: 115, 84: 116, 85: 117, 86: 118, 87: 119, 88: 120, 89: 121, 90: 122, 1600: None, 1569: 1575, 1570: 1575, 1571: 1575, 1572: 1575, 1573: 1575, 1574: 1575, 1577: 1607, # teh marboota -> haa 1611: None, 1612: None, 1613: None, 1614: None, 1615: None, 1616: None, 1617: None, 1618: None, 1609: 1575} rm_prefix = re.compile(u"^(?:ا?[وف]?((?:[بك]?ال|لل?)|[اينت])?)") # TODO: reconsider the suffex re rm_suffix = re.compile(u"(?:ا[نت]|[يهة]|ها|ي[هنة]|ون)$") #rm_prefix = u"^(?:ا?[وف]?((?:[بك]?ال|لل?)|[اينت])?)" #rm_suffix = u"(?:ا[نت]|[يهة]|ها|ي[هنة]|ون)$" #stem_re = rm_prefix+"(\w{3,}?)"+rm_suffix # أواستقدمتموني # استفهام عطف جر وتعريف (مثال: "أفككتابي تؤلف ؟" "وللآخرة فلنعد العدة" "فالاستغفار") أو مضارعة # الجر والتعريف لا تجتمع مع المضارعة prefix_re = u''.join( ( u"^\u0627?" , # optional hamza u"[\u0648\u0641]?", # optional Atf (with Waw or Faa) u"(?:" , # nouns specific prefixes (Jar and definite article) u"[\u0628\u0643]?\u0627\u0644?|" , # optional Jar (with ba or kaf) with optional AL u"\u0644\u0644|" , # optional LL (Jar with Lam and article ) u"\u0644" , # optional LL (Jar with Lam and article) u")?" , # end nouns specific prefixes u"(\\w{2,})$" ) ) # the stem is grouped # [اتني]|نا|ان|تا|ون|ين|تما verb_some_subject_re = u"[\u0627\u062a\u0646\u064a]|\u0646\u0627|\u0627\u0646|\u062a\u0627|\u0648\u0646|\u064a\u0646|\u062a\u0645\u0627" # [هن]|ني|نا|ها|هما|هم|هن|كما|كم|كن verb_object_re = u"(?[\u0647\u0646]|\u0646\u064a|\u0646\u0627|\u0647\u0627|\u0647\u0645\u0627|\u0647\u0645|\u0647\u0646|\u0643\u0645\u0627|\u0643\u0645|\u0643\u0646)" verb_suffix_re = u''.join( [ u"(?:(?:\u0648\u0627|\u062a\u0645)|" , # وا|تم u"(?:", u"(?:", verb_some_subject_re, u'|\u0648|\u062a\u0645\u0648', # و|تمو u")", verb_object_re,u'{1,2}' u")|(?:", verb_some_subject_re, u"))?$"]) def removeArabicSuffix(word): if len(word) > 4: w = rm_suffix.sub("", word, 1) if len(w) > 2: return w return word def removeArabicPrefix(word): if len(word) > 3: w = rm_prefix.sub("", word, 1) if len(w)>2: return w return word def stemArabic(word): return removeArabicPrefix(removeArabicSuffix(unicode(word).translate(normalize_tb))) thawab-4.1/Thawab/tags.py000066400000000000000000000025371305262755200153600ustar00rootroot00000000000000# the following flags are Or-ed in a node-wide (not tag-wide) TAG_FLAGS_EXTERNAL_SOURCE = 1 # some external source pointed by param TAG_FLAGS_BYBOT = 2 # the content and descendant nodes are generated by a bot, CHANGES WILL BE LOST TAG_FLAGS_HEADER = 4 # index content in a separated document then consume content TAG_FLAGS_IX_TAG = 8 # add this tag name into index tags list, if it has a param append it to the tag name TAG_FLAGS_IX_FIELD = 16 # index content (again) in a separated document without consuming content TAG_FLAGS_IX_SKIP = 32 # don't index content TAG_FLAGS_PAD_CONTENT = 64 # append a space/LF after content TAG_FLAGS_FLOW_BLOCK = 128 # in a separated block eg.
TAG_FLAGS_FLOW_FLOAT = 256 # marked text does not flow normally, but float in a box TAG_FLAGS_FLOW_FOOTER = 512 # marked text does not flow normally, but accumelated in the tail TAG_FLAGS_FLOW_HIDDEN = 1024 # marked text does not appear in usual cases # NOTE: validaty of data: a node of type TAG_FLAGS_HEADER can't be a child of TAG_FLAGS_IX_FIELD # NOTE: validaty of data: both TAG_FLAGS_IX_SKIP and TAG_FLAGS_IX_FIELD should not be applied to the same node # NOTE: validaty of data: both TAG_FLAGS_HEADER and TAG_FLAGS_IX_FIELD will case redudancy if applied to same node (as things will be indexed twice without any befinit) thawab-4.1/Thawab/userDb.py000066400000000000000000000145451305262755200156500ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ The meta handling classes of thawab Copyright © 2008-2010, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path, re, sqlite3, time, threading ################################################# USER_DB_SCHEMA = """\ CREATE TABLE "starred" ( "kitab" TEXT PRIMARY KEY, "time" FLOAT ); CREATE INDEX StarredTimeIndex on starred(time); CREATE TABLE "bookmarks" ( "kitab" TEXT, "version" TEXT, "globalOrder" INTEGER, "nodeIdNum" INTEGER, "nodeId" TEXT, "title" TEXT, "time" FLOAT, PRIMARY KEY ("kitab", "version", nodeId) ); CREATE INDEX BookmarksKitabIndex on bookmarks(kitab); CREATE INDEX BookmarksNodeIdNumIndex on bookmarks(nodeIdNum); CREATE INDEX BookmarksGlobalOrderIndex on bookmarks(globalOrder); CREATE INDEX BookmarksTimeIndex on bookmarks(time); CREATE TABLE "comments" ( "kitab" TEXT, "version" TEXT, "globalOrder" INTEGER, "nodeIdNum" INTEGER, "nodeId" TEXT, "title" TEXT, "comment" TEXT, "time" FLOAT, PRIMARY KEY ("kitab", "version", nodeId) ); CREATE INDEX CommentsKitabIndex on comments(kitab); CREATE INDEX CommentsNodeIdNumIndex on comments(nodeIdNum); CREATE INDEX CommentsGlobalOrderIndex on comments(globalOrder); CREATE INDEX CommentsTimeIndex on comments(time); """ SQL_GET_ALL_STARRED = """SELECT kitab FROM starred ORDER BY time""" SQL_GET_STARRED_TIME = """SELECT time FROM starred WHERE kitab=?""" SQL_SET_STARRED = 'INSERT OR REPLACE INTO starred (kitab, time) VALUES (?, ?)' SQL_UNSET_STARRED = 'DELETE OR IGNORE FROM starred WHERE kitab=?' # NOTE: globalOrder is used to get the right book order # NOTE: nodeIdNum is used for consistancy checking and optimization SQL_GET_ALL_BOOKMARKS = """SELECT * FROM bookmarks ORDER BY kitab""" SQL_GET_BOOKMARKED_KUTUB = """SELECT DISTINCT kitab FROM bookmarks ORDER BY kitab""" SQL_GET_KITAB_BOOKMARKS = """SELECT * FROM bookmarks WHERE kitab=? ORDER BY time""" SQL_ADD_BOOKMARK = 'INSERT OR REPLACE INTO bookmarks (kitab, version, globalOrder, nodeIdNum, nodeId, title, time) VALUES (?,?,?,?,?,?,?)' SQL_GET_ALL_COMMENTS = """SELECT * FROM comments ORDER BY kitab""" SQL_GET_COMMENTED_KUTUB = """SELECT DISTINCT kitab FROM comments ORDER BY kitab""" SQL_GET_KITAB_COMMENTS = """SELECT * FROM comments WHERE kitab=? ORDER BY time""" SQL_ADD_COMMENT = 'INSERT OR REPLACE INTO comments (kitab, version, globalOrder, nodeIdNum, nodeId, title, comment, time) VALUES (?,?,?,?,?,?,?,?)' ################################# class UserDb(object): """a class holding metadata cache""" def __init__(self, th, user_db): self.th = th self.db_fn = user_db if not os.path.exists(self.db_fn): create_new = True else: create_new = False self._cn = {} cn = self._getConnection() if create_new: cn.executescript(USER_DB_SCHEMA) cn.commit() def _getConnection(self): n = threading.current_thread().name if self._cn.has_key(n): r = self._cn[n] else: r = sqlite3.connect(self.db_fn) r.row_factory = sqlite3.Row self._cn[n] = r return r def getStarredTime(self, kitab): """ return None if not starred, can be used to check if starred """ r = self._getConnection().execute(SQL_GET_STARRED_TIME, (kitab,)).fetchone() if not r: return None return r['time'] def getStarredList(self): r = self._getConnection().execute(SQL_GET_ALL_STARRED).fetchall() return map(lambda i: i['kitab'], r) def starKitab(self, kitab): self._getConnection().execute(SQL_SET_STARRED , (kitab, float(time.time()))) def unstarKitab(self, kitab): self._getConnection().execute(SQL_UNSET_STARRED, (kitab,)) def starKitab(self, kitab): self._getConnection().execute(SQL_SET_STARRED , (kitab, float(time.time()))) def getAllBookmarks(self): r = self._getConnection().execute(SQL_GET_ALL_BOOKMARKS).fetchall() return map(lambda i: dict(i), r) def getBookmarkedKutub(self): r = self._getConnection().execute(SQL_GET_BOOKMARKED_KUTUB).fetchall() return map(lambda i: i['kitab'], r) def getKitabBookmarks(self, kitab): r = self._getConnection().execute(SQL_GET_KITAB_BOOKMARKS, (kitab, )).fetchall() return map(lambda i: dict(i), r) def addBookmark(self, kitab, version, globalOrder, nodeIdNum, nodeId, title): self._getConnection().execute(SQL_ADD_BOOKMARKS, (kitab, version, globalOrder, nodeIdNum, nodeId, title, float(time.time()) )) def getAllComments(self): r = self._getConnection().execute(SQL_GET_ALL_COMMENTS).fetchall() return map(lambda i: dict(i), r) def getCommentedKutub(self): r = self._getConnection().execute(SQL_GET_COMMENTED_KUTUB).fetchall() return map(lambda i: i['kitab'], r) def getKitabComments(self, kitab): r = self._getConnection().execute(SQL_GET_KITAB_COMMENTS, (kitab, )).fetchall() return map(lambda i: dict(i), r) def addComment(self, kitab, version, globalOrder, nodeIdNum, nodeId, title, comment): self._getConnection().execute(SQL_ADD_COMMENT, (kitab, version, globalOrder, nodeIdNum, nodeId, title, comment, float(time.time()) )) thawab-4.1/Thawab/webApp.py000066400000000000000000000315701305262755200156370ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Copyright © 2009, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path import hashlib import time import bisect from cgi import escape # for html escaping from meta import prettyId, makeId, metaVrr from stemming import normalize_tb from okasha.utils import ObjectsCache from okasha.baseWebApp import * from okasha.bottleTemplate import bottleTemplate # fixme move this to okasha.utils def tryInt(s, d = 0): try: return int(s) except ValueError: pass except TypeError: pass return d class webApp(baseWebApp): _emptyViewResp = { 'apptype': 'web', 'content': '', 'childrenLinks': '', 'prevUrl': '', 'prevTitle': '', 'upUrl': '', 'upTitle': '', 'nextUrl': '', 'nextTitle': '', 'breadcrumbs': '' } def __init__(self, th, typ = 'web', *args, **kw): """ th is an instance of ThawabMan allowByUri = True for desktop, False for server """ self.th = th self.isMonolithic = th.isMonolithic self.stringSeed = "S3(uR!r7y" self._typ = typ self._allowByUri = (typ == 'app') self._emptyViewResp[u"apptype"]=self._typ # FIXME: move ObjectsCache of kitab to routines to core.ThawabMan if not self.isMonolithic: import threading lock1 = threading.Lock(); else: lock1 = None self.searchCache = ObjectsCache(lock = lock1) baseWebApp.__init__(self, *args, **kw) def _safeHash(self,o): """ a URL safe hash, it results a 22 byte long string hash based on md5sum """ if isinstance(o,unicode): o = o.encode('utf8') return hashlib.md5(self.stringSeed+o).digest().encode('base64').replace('+','-').replace('/','_')[:22] def _root(self, rq, *args): if args: if args[0] == 'favicon.ico': raise redirectException(rq.script+'/_files/img/favicon.ico') elif args[0] == 'robots.txt': return self._robots(rq, *args) elif args[0] == 'sitemap.xml': return self._sitemap(rq, *args) raise forbiddenException() raise redirectException(rq.script+'/index/') @expose(contentType = 'text/plain; charset = utf-8') def _robots(self, rq, *args): return """Sitemap: http://%s/sitemap.xml User-agent: * Allow: / """ % (rq.environ['HTTP_HOST']+rq.script) @expose(contentType = 'text/xml; charset = utf-8') def _sitemap(self, rq, *args): t = time.gmtime() # FIXME: use meta to get mime of meta.db d = time.strftime("%Y-%m-%dT%H:%M:%S+00:00", t) tmp = "\t\n\t\thttp://"+rq.environ['HTTP_HOST']+rq.script+"/static/%s/_i0.html\n\t\t"+d+"\n\t\tdaily\n\t\t0.5\n\t" l=self.th.getMeta().getKitabList() urls=[] for k in l: urls.append(tmp % (k)) return """ http://thawab.ojuba.org/index/ %s daily 0.8 %s """ % (d,"\n".join(urls)) @expose(bottleTemplate,["main"]) def index(self, rq, *args): rq.q.title = "الرئيسية" l = self.th.getMeta().getKitabList() htmlLinks = [] l = sorted(l) for k in l: # FIXME: it currenly offers only one version for each kitab (the first one) htmlLinks.append('\t
  • %s
  • ' % (k, prettyId(self.th.getMeta().getByKitab(k)[0]['kitab']))) htmlLinks = (u"\n".join(htmlLinks)) return { u"lang":u"ar", u"dir":u"rtl", u"kutublinks": htmlLinks, "args":'/'.join(args)} @expose(percentTemplate,["stem.html"]) def stem(self, rq, *args): from stemming import stemArabic w = rq.q.getfirst('word','').decode('utf-8') s = '' if w: s = " ".join([stemArabic(i) for i in w.split()]) return {u"script":rq.script, u"word":w, u"stem":s} def _getKitabObject(self, rq, *args): # FIXME: cache KitabObjects and update last access if not args: raise forbiddenException() # TODO: make it a redirect to index k = args[0] if k == '_by_uri': if self._allowByUri: uri = rq.q.getfirst('uri',None) if not uri: raise fileNotFoundException() m = self.th.getMeta().getByUri(uri) else: raise forbiddenException() else: m = self.th.getMeta().getLatestKitab(k) if not m: raise forbiddenException() uri = m['uri'] ki = self.th.getCachedKitab(uri) return ki, m def _view(self, ki, m, i, d = '#', s = ""): r = self._emptyViewResp.copy() node, p, u, n, c, b = ki.toc.getNodePrevUpNextChildrenBreadcrumbs(i) if n: ub = n.globalOrder else: ub = -1 if not node or i == "_i0": r['content'] = "

    %s

    " % escape(prettyId(m['kitab'])) else: r['content'] = node.toHtml(ub).replace('\n\n','\n

    \n') if c: cLinks = ''.join(map(lambda cc: '

  • %s
  • \n' % \ (d + "_i" + str(cc.idNum) + s, escape(cc.getContent())), c)) cLinks = "
      \n" + cLinks + "
    " else: cLinks = '' r['childrenLinks'] = cLinks if n: r['nextUrl'] = d + '_i' + str(n.idNum) + s r['nextTitle'] = escape(n.getContent()) if p: r['prevUrl'] = d + '_i' + str(p.idNum) + s r['prevTitle'] = escape(p.getContent()) if u: r['upUrl'] = d + '_i' + str(u.idNum) + s r['upTitle'] = escape(u.getContent()) if b: r['breadcrumbs'] = " > ".join(map(lambda (i,t): ("%s") % \ (i, escape(t)), b)) vrr = metaVrr(ki.meta) #self.th.searchEngine.related(m['kitab'], vrr, node.idNum) return r def _get_kitab_details(self, rq, *args): ki, m = self._getKitabObject(rq, *args) if not ki or not m: return None, None, {} lang = m.get('lang', 'ar') if lang in ('ar', 'fa', 'he'): d = 'rtl' else: d = 'ltr' kitabId = escape(makeId(m['kitab'])) t = escape(prettyId(m['kitab'])) r = self._emptyViewResp.copy() r.update({ u"script": rq.script, u"kitabTitle": t, u"kitabId": kitabId, u"headingId": u"_i0", u"app": u"Thawab", u"version": u"3.0.1", u"lang": lang, u"dir": d, u"title": t, u"content": t, "args": '/'.join(args)}) return ki, m, r @expose(bottleTemplate,["view"]) def static(self, rq, *args): l = len(args) if l < 1: raise forbiddenException() # TODO: make it show a list of books elif l == 1: raise redirectException(rq.script + '/static/' + args[0] + "/_i0.html") elif l != 2: raise forbiddenException() ki, m, r = self._get_kitab_details(rq, *args) if not ki: raise fileNotFoundException() h = args[1] if h.endswith(".html"): h = h[:-5] r.update(self._view(ki, m, h, './', ".html")) if self.th.searchEngine.getIndexedVersion(m['kitab']): rq.q.is_indexed = 1 r['is_indexed'] = 1 else: rq.q.is_indexed = 0 r['is_indexed'] = 0 r['is_static'] = 1 r['d'] = './' r['s'] = '.html' return r @expose(bottleTemplate,["view"]) def view(self, rq, *args): if len(args) != 1: raise forbiddenException() ki, m, r = self._get_kitab_details(rq, *args) if not ki: raise fileNotFoundException() if self.th.searchEngine.getIndexedVersion(m['kitab']): rq.q.is_indexed = 1 r['is_indexed'] = 1 else: rq.q.is_indexed = 0 r['is_indexed'] = 0 r['is_static'] = 0 r['d'] = '#' r['s'] = '' return r @expose() def ajax(self, rq, *args): if not args: raise forbiddenException() if args[0] == 'searchExcerpt' and len(args) == 3: h = args[1] try: i = int(args[2]) except TypeError: raise forbiddenException() R = self.searchCache.get(h) if R == None: return 'انتهت صلاحية هذا البحث' try : r = self.th.searchEngine.resultExcerpt(R, i) except OSError, e: print '** webapp.ajax: %s' , e return '' #r = escape(self.th.searchEngine.resultExcerpt(R,i)).replace('\0','').replace('\010','').replace(u"\u2026",u"\u2026
    ").encode('utf8') return r elif args[0] == 'kutub' and len(args) == 1: q = rq.q.getfirst('q','').decode('utf-8').strip().translate(normalize_tb) r = [] l = self.th.getMeta().getKitabList() l = sorted(l) for k in l: n = prettyId(k) if not q or q in n.translate(normalize_tb): r.append('\t
  • %s
  • ' % (k, n)) return '
      %s
    \n
    ' % "\n".join(r) raise forbiddenException() @expose(jsonDumps) def json(self, rq, *args): # use rq.rhost to impose host-based limits on searching if not args: raise forbiddenException() ki = None r = {} if args[0] == 'view': a = args[1:] ki, m = self._getKitabObject(rq, *a) if len(a) == 2: r = self._view(ki, m, a[1]) elif args[0] == 'search': q = rq.q.getfirst('q','') h = self._safeHash(q) # FIXME: check to see if one already search for that before q = q.decode('utf8') R = self.th.searchEngine.queryIndex(q) # print R if not R: return {'t': 0, 'c': 0, 'h': ''} self.searchCache.append(h,R) r = {'t': R.runtime, 'c': len(R), 'h': h} elif args[0] == 'searchResults': h = rq.q.getfirst('h','') try: i = int(rq.q.getfirst('i', '0')) except TypeError: i = 0 try: c = int(rq.q.getfirst('c', '0')) except TypeError: c = 0 R = self.searchCache.get(h) if R == None: return {'c': 0} C = len(R) if i >= C: return {'c': 0} c = min(c, C-i) r = {'c': c, 'a': []} n = 100.0 / R[0].score j = 0 for j in range(i, i + c): name = R[j]['kitab'] v = R[j]['vrr'].split('-')[0] m = self.th.getMeta().getLatestKitabV(name,v) k = m['kitab'] #.replace('_', ' ') if not m: continue # book is removed r['a'].append({ 'i':j,'n':'_i'+R[j]['nodeIdNum'], 'k':k, 'a':prettyId(m['author']), 'y':tryInt(m['year']), 't':R[j]['title'], 'r':'%4.1f' % (n*R[j].score)}) j += 1 r[c] = j; else: r = {} return r thawab-4.1/Thawab/whooshSearchEngine.py000066400000000000000000000263171305262755200202070ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Copyright © 2008, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path, re import shutil from tags import * from meta import prettyId,makeId from whoosh import query from whoosh.index import EmptyIndexError, create_in, open_dir, IndexVersionError from whoosh.highlight import highlight, SentenceFragmenter, BasicFragmentScorer, FIRST, HtmlFormatter from whoosh.filedb.filestore import FileStorage from whoosh.fields import Schema, ID, IDLIST, TEXT from whoosh.formats import Frequency from whoosh.qparser import QueryParserError from whoosh.lang.porter import stem from whoosh.analysis import StandardAnalyzer, StemFilter try: from whoosh.index import _CURRENT_TOC_VERSION as whoosh_ix_ver except ImportError: from whoosh.filedb.fileindex import _INDEX_VERSION as whoosh_ix_ver from stemming import stemArabic def stemfn(word): return stemArabic(stem(word)) # word_re = ur"[\w\u064e\u064b\u064f\u064c\u0650\u064d\u0652\u0651\u0640]" analyzer = StandardAnalyzer(expression = ur"[\w\u064e\u064b\u064f\u064c\u0650\u064d\u0652\u0651\u0640]+(?:\.?[\w\u064e\u064b\u064f\u064c\u0650\u064d\u0652\u0651\u0640]+)*") | StemFilter(stemfn) from whoosh.qparser import FieldAliasPlugin from whooshSymbolicQParser import MultifieldSQParser class ExcerptFormatter(object): def __init__(self, between = "..."): self.between = between def _format_fragment(self, text, fragment): output = [] index = fragment.startchar for t in fragment.matches: if t.startchar > index: output.append(text[index:t.startchar]) ttxt = text[t.startchar:t.endchar] if t.matched: ttxt = "\0" + ttxt.upper() + "\010" output.append(ttxt) index = t.endchar output.append(text[index:fragment.endchar]) return "".join(output) def __call__(self, text, fragments): return self.between.join((self._format_fragment(text, fragment) for fragment in fragments)) from baseSearchEngine import BaseSearchEngine class SearchEngine(BaseSearchEngine): def __init__(self, th): BaseSearchEngine.__init__(self, th, False) self.__ix_writer = None ix_dir = os.path.join(th.prefixes[0],'index', "ix_" + str(whoosh_ix_ver)) if not os.path.isdir(ix_dir): os.makedirs(ix_dir) # try to load a pre-existing index try: self.indexer = open_dir(ix_dir) except (EmptyIndexError, IndexVersionError): # create a new one try: shutil.rmtree(ix_dir, True) os.makedirs(ix_dir) except OSError: pass schema = Schema( kitab = ID(stored = True), vrr = ID(stored = True, unique = False), # version release nodeIdNum = ID(stored = True, unique = False), title = TEXT(stored = True, field_boost = 1.5, analyzer = analyzer), content = TEXT(stored = False,analyzer = analyzer), #content = TEXT(stored = False,analyzer = analyzer, #vector = Frequency(analyzer = analyzer)), # with term vector tags=IDLIST(stored = False) ) self.indexer = create_in(ix_dir, schema) #self.__ix_qparser = ThMultifieldParser(self.th, ("title","content",), schema=self.indexer.schema) self.__ix_qparser = MultifieldSQParser(("title","content",), self.indexer.schema) self.__ix_qparser.add_plugin(FieldAliasPlugin({ u"kitab":(u"كتاب",), u"title":(u"عنوان",), u"tags":(u"وسوم",)}) ) #self.__ix_pre = whoosh.query.Prefix self.__ix_searcher = self.indexer.searcher() def __del__(self): if self.__ix_writer: self.__ix_writer.commit() def getIndexedVersion(self, name): """ return a Version-Release string if in index, otherwise return None """ try: d = self.__ix_searcher.document(kitab = unicode(makeId(name))) except TypeError: return None except KeyError: return None if d: return d['vrr'] return None def queryIndex(self, queryString): """return an interatable of fields dict""" # FIXME: the return should not be implementation specific try: r = self.__ix_searcher.search(self.__ix_qparser.parse(queryString), limit = 500) except QueryParserError: return None return r def resultExcerpt(self, results, i, ki = None): # FIXME: this should not be implementation specific if not ki: r = results[i] name = r['kitab'] v = r['vrr'].split('-')[0] m = self.th.getMeta().getLatestKitabV(name,v) ki = self.th.getCachedKitab(m['uri']) num = int(results[i]['nodeIdNum']) node = ki.getNodeByIdNum(num) n = ki.toc.next(node) if n: ub = n.globalOrder else: ub = -1 txt = node.toText(ub) s = set() #results.query.all_terms(s) # return (field,term) pairs # return (field,term) pairs # self.self.__ix_searcher.reader() results.q.existing_terms(self.indexer.reader(), s, phrases = True) terms = dict( map(lambda i: (i[1],i[0]), filter(lambda j: j[0] == 'content' or j[0] == 'title', s))).keys() #print "txt = [%s]" % len(txt) snippet = txt[:min(len(txt),512)] # dummy summary snippet = highlight(txt, terms, analyzer, SentenceFragmenter(sentencechars = ".!?؟\n"), HtmlFormatter(between = u"\u2026\n"), top = 3, scorer = BasicFragmentScorer, minscore = 1, order = FIRST) #snippet = highlight(txt, terms, analyzer, # SentenceFragmenter(sentencechars = ".!?"), ExcerptFormatter(between = u"\u2026\n"), top = 3, # scorer = BasicFragmentScorer, minscore = 1, # order = FIRST) return snippet def indexingStart(self): """ should be called before any sequence of indexing Ops, reindexAll() calls this method automatically """ if not self.__ix_writer: try: self.__ix_writer = self.indexer.writer() except OSError, e: print '*** whooshSearchEnfine.indexingStart: %s', e def indexingEnd(self): """ should be called after a sequence of indexing Ops, reindexAll() calls this method automatically """ self.__ix_writer.commit(optimize = True) # self.indexer.optimize() # no need for this with optimize in previous line self.reload() def reload(self): """ called after commiting changes to index (eg. adding or dropping from index) """ self.__ix_searcher = self.__ix_searcher.refresh() # no need to obtain new one with self.indexer.searcher() self.__ix_writer = None def dropKitabIndex(self, name): """ drop search index for a given Kitab by its uri if you call indexingStart() before this then you must call indexingEnd() after it """ # FIXME: it seems that this used not work correctly without commit() just after drop, this mean that reindex needs a commit in-between ki = self.th.getKitab(name) if ki: self.th.getMeta().setIndexedFlags(ki.uri, 1) print "dropping index for kitab name:", name, w, c = self.__ix_writer, False if not w: w, c = self.indexer.writer(), True # creates a writer internally if one is not defined # NOTE: because the searcher could be limited do a loop that keeps deleting till the query is empty while(w.delete_by_term('kitab', name)): print "*", print if c: w.commit() if ki: self.th.getMeta().setIndexedFlags(ki.uri, 0) def dropAll(self): # FIXME: it would be more effeciant to delete the directory # NOTE: see http://groups.google.com/group/whoosh/browse_thread/thread/35b1700b4e4a3d5d self.th.getMeta().setAllIndexedFlags(1) self.indexingStart() reader = self.indexer.reader() # also self.__ix_searcher.reader() for docnum in reader.all_stored_fields(): self.__ix_writer.delete_document(docnum) self.indexingEnd() self.th.getMeta().setAllIndexedFlags(0) def reindexKitab(self,name): """ you need to call indexingStart() before this and indexingEnd() after it """ # NOTE: this method is overridden here because we need to commit # between dropping and creating a new index. # NOTE: can't use updateDocument because each Kitab contains many documents self.dropKitabIndex(name) self.__ix_writer.commit() self.indexKitab(name) def addDocumentToIndex(self, name, vrr, nodeIdNum, title, content, tags): """ this method must be overridden in implementation specific way """ if content: self.__ix_writer.add_document(kitab = name, vrr = vrr, nodeIdNum = unicode(nodeIdNum), title = title, content = content, tags = tags) def keyterms(self, kitab, vrr, nodeIdNum): s = self.indexer.searcher() dn = s.document_number(kitab = kitab, vrr = vrr, nodeIdNum = unicode(nodeIdNum)) if dn == None: return None, [] print " ## ", dn r = s.key_terms([dn], "content", numterms = 5) return dn, r def related(self, kitab, vrr, nodeIdNum): dn, kt = self.keyterms(kitab, vrr, nodeIdNum) if not dn: return None for t, r in kt: print "term = ", t, " @ rank = ",r q = query.Or([query.Term("content", t) for (t, r) in kt]) results = self.indexer.searcher().search(q, limit = 10) for i, fields in enumerate(results): if results.docnum(i) != dn: print fields['kitab'],"\t\t",str(fields['nodeIdNum']),"\t\t",fields['title'] thawab-4.1/Thawab/whooshSymbolicQParser.py000066400000000000000000000007051305262755200207240ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Copyright © 2010, Muayyad Alsadi """ import sys, os, os.path, re from whoosh import query from whoosh.qparser import * def MultifieldSQParser(fieldnames, schema = None, fieldboosts=None, **kwargs): p = MultifieldParser(fieldnames, schema, fieldboosts, **kwargs) cp = OperatorsPlugin(And = r"&", Or = r"\|", AndNot = r"&!", AndMaybe = r"&~", Not = r'!') p.replace_plugin(cp) return p thawab-4.1/Thawab/wiki.py000066400000000000000000000063661305262755200153710ustar00rootroot00000000000000# -*- coding: UTF-8 -*- """ Copyright © 2008, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import time, re #################################### header_re = re.compile(r'^\s*( = +)\s*(.+?)\s*\1\s*$') def importFromWiki(c, wiki): """import a wiki-like into a thawab""" ki = c.ki txt = "" parents = [ki.root] wikidepths = [0] title = None wiki_started = 0 meta = { 'cache_hash': time.time(), 'repo': u'_local', 'lang': None, 'kitab': None, 'version': u'1', 'releaseMajor': u'0', 'releaseMinor': u'0', 'author': None, 'year': 0, 'originalAuthor': None, 'originalYear': 0, 'originalKitab': None, 'originalVersion': None, 'classification': u'_misc'} for l in wiki: #l = l.decode('utf-8') if wiki_started == 0: if l.startswith('@'): kv = l.split(' = ',1) key = kv[0][1:].strip() if len(kv) == 2: value = kv[1].strip() meta[key] = value continue else: wiki_started = 1 m = header_re.match(l) if not m: # textbody line: add the line to accumelated textbody variable txt += l else: # new header: # add the accumelated textbody of a previous header (if exists) to the Kitab if txt and title: c.appendNode(parents[-1], txt, {'textbody': None}) # elif txt and not title: pass # it's leading noise, as title can't be empty because of + in the RE # reset the accumelated textbody txt = "" # now get the title of matched by RE title = m.group(2) newwikidepth = 7 - len(m.group(1)) # several methods, first one is to use: while(wikidepths[-1] >= newwikidepth): wikidepths.pop() parents.pop() wikidepths = wikidepths + [newwikidepth] parent = c.appendNode(parents[-1], title, {'header': None}) parents = parents + [parent] if (txt): c.appendNode(parents[-1], txt, {'textbody': None}) ki.setMCache(meta) def wiki2th(w, dst): import os import os.path import Thawab.core import shutil n = os.path.basename(w) if n.endswith('.txt'): n = n[:-4] + ".ki" th = Thawab.core.ThawabMan(os.path.expanduser('~/.thawab')) ki = th.mktemp() wiki = open(w, "rt").read().decode('utf-8').splitlines() c = ki.seek(-1, -1) importFromWiki(c, wiki) c.flush() o = ki.uri del ki shutil.move(o, os.path.join(dst, n)) thawab-4.1/bok2ki.py000077500000000000000000000076171305262755200144040ustar00rootroot00000000000000#! /usr/bin/python # -*- coding: UTF-8 -*- """ Script to import .bok files Copyright © 2008-2010, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path, glob, shutil, re import sqlite3 from getopt import getopt, GetoptError # TODO: take shamela prefix # import Files/special.mdb Files/main.mdb first # then take bokids eg. -f /opt/emu/apps/shamela-r1/ 100 15001 ..etc. # if first arg of ShamelaSqlite is a directory, # getTables should generate tb:fn # def usage(): print '''\ Usage: %s [-i] [-m DIR] FILES ... Where: \t-i\t\t- in-memory \t-m DIR\t\t- move successfully imported BOK files into DIR \t--ft-prefix=FOOTER_PREFIX default is "(¬" \t--ft-suffix=FOOTER_SUFFIX default is ")" \t--ft-leading=[0|1] should footnote be match at line start only, default is 0 \t--ft-sp=[0|1|2] no, single or many whitespaces, default is 0 \t--bft-prefix=FOOTER_PREFIX footnote anchor in body prefix, default is "(¬" \t--bft-suffix=FOOTER_SUFFIX footnote anchor in body suffix, default is ")" \t--bft-sp=[0|1|2] no, single or many whitespaces, default is 0 the generated files will be moved into db in thawab prefix (usually ~/.thawab/db/) ''' % os.path.basename(sys.argv[0]) try: opts, args = getopt(sys.argv[1:], "im:", ["help", 'ft-prefix=', 'ft-suffix=', 'bft-prefix=', 'bft-suffix=', 'ft-leading=', 'ft-sp=', 'bft-sp=']) except GetoptError, err: print str(err) # will print something like "option -a not recognized" usage() sys.exit(1) if not args: print "please provide at least one .bok files" usage() sys.exit(1) opts=dict(opts) def progress(msg, p, *a, **kw): print " ** [%g%% completed] %s" % (p,msg) from Thawab.core import ThawabMan from Thawab.shamelaUtils import ShamelaSqlite,shamelaImport th=ThawabMan() thprefix=th.prefixes[0] if not opts.has_key('-i'): db_fn=os.path.expanduser('~/bok2sql.db') else: db_fn=None # ¬ U+00AC NOT SIGN ft_prefix=opts.get('--ft-prefix','(¬').decode('utf-8'); ft_prefix_len=len(ft_prefix) ft_suffix=opts.get('--ft-suffix',')').decode('utf-8'); ft_suffix_len=len(ft_suffix) ft_sp=[u'', ur'\s?' , ur'\s*'][int(opts.get('--ft-sp','0'))] ft_at_line_start=int(opts.get('--ft-leading','0')) footnote_re=(ft_at_line_start and u'^\s*' or u'') + re.escape(ft_prefix)+ft_sp+ur'(\d+)'+ft_sp+re.escape(ft_suffix) bft_prefix=opts.get('--bft-prefix','(¬').decode('utf-8'); bft_suffix=opts.get('--bft-suffix',')').decode('utf-8'); bft_sp=[u'', ur'\s?' , ur'\s*'][int(opts.get('--bft-sp','0'))] body_footnote_re=re.escape(bft_prefix)+bft_sp+ur'(\d+)'+bft_sp+re.escape(bft_suffix) for fn in args: if db_fn: if os.path.exists(db_fn): os.unlink(db_fn) cn=sqlite3.connect(db_fn, isolation_level=None) else: cn=None sh=ShamelaSqlite(fn, cn, 0 , 0, progress) sh.toSqlite() for bkid in sh.getBookIds(): ki=th.mktemp() c=ki.seek(-1,-1) m=shamelaImport(c, sh, bkid, footnote_re, body_footnote_re, ft_prefix_len, ft_suffix_len) c.flush() print "moving %s to %s" % (ki.uri, os.path.join(thprefix,'db', m['kitab']+u"-"+m['version']+u".ki")) shutil.move(ki.uri, os.path.join(thprefix,'db', m['kitab']+u"-"+m['version']+u".ki")) if opts.has_key('-m'): dd=opts['-m'] if not os.path.isdir(dd): try: os.makedirs(dd) except OSError: pass if os.path.isdir(dd): dst=os.path.join(dd,os.path.basename(fn)) print "moving %s to %s" % (fn,dst) shutil.move(fn, dst) else: print "could not move .bok files, target directory does not exists" thawab-4.1/po/000077500000000000000000000000001305262755200132515ustar00rootroot00000000000000thawab-4.1/po/Makefile000066400000000000000000000022761305262755200147200ustar00rootroot00000000000000APPNAME := thawab POFILES := $(wildcard *.po) MOFILES := $(patsubst %.po,%.mo,$(POFILES)) CRE_POTFILESin := for i in $(shell cat POTFILES.am ); do echo ../$${i} | sed 's/\s/\n/g; s/\.\.\///g' ; done > POTFILES.in CAT := cat ECHO := echo MKDIR := mkdir MSGFMT := msgfmt INTLTOOL_UPDATE := intltool-update RM := $(shell which rm | egrep '/' | sed 's/\s*//g') MV := $(shell which mv | egrep '/' | sed 's/\s*//g') all: $(APPNAME).pot $(MOFILES) $(APPNAME).pot: @$(CRE_POTFILESin) @$(ECHO) "*** Building $(APPNAME).pot: $(SOURCES)" @$(CAT) POTFILES.in @$(INTLTOOL_UPDATE) -g $(APPNAME) -p %.mo: %.po @$(ECHO) "- Merging translations into $*.mo" @$(MSGFMT) $*.po -o $*.mo @$(MKDIR) -p ../locale/$*/LC_MESSAGES/ || : @$(ECHO) "- Moving: $*.mo -> ../locale/$*/LC_MESSAGES/$(APPNAME).mo" @$(MV) $*.mo ../locale/$*/LC_MESSAGES/$(APPNAME).mo @$(RM) -f *.tmp %.po: $(APPNAME).pot @$(ECHO) "- Updating: $*.po" @$(INTLTOOL_UPDATE) -g $(APPNAME) -d $* clean: @$(ECHO) "*** Cleaning pos..." @$(ECHO) "- Removing: $(APPNAME).pot" @$(RM) -f $(APPNAME).pot @$(ECHO) "- Removing: *.tmp" @$(RM) -f *.tmp @$(ECHO) "- Removing: *.mo" @$(RM) -f *.mo @$(ECHO) "- Removing: POTFILES.in" @$(RM) -f POTFILES.in thawab-4.1/po/POTFILES.am000066400000000000000000000000371305262755200150150ustar00rootroot00000000000000thawab.desktop.in Thawab/*.py thawab-4.1/po/ar.po000066400000000000000000000154341305262755200142220ustar00rootroot00000000000000# Translation of thawab templates to Arabic # Copyright (C) 2008-2010, ojuba.org # This file is distributed under the same license as the thawab package. # Muayyad Saleh Alsadi , 2010 # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-02-16 04:06+0200\n" "PO-Revision-Date: 2010-06-12 19:35+0300\n" "Last-Translator: Muayyad Saleh Alsadi \n" "Language-Team: thawab@ojuba.org\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../thawab.desktop.in.h:1 ../Thawab/gtkUi.py:837 ../Thawab/gtkUi.py:849 msgid "Thawab" msgstr "ثواب" #: ../thawab.desktop.in.h:2 msgid "Electronic Arabic/Islamic Encyclopedia" msgstr "موسوعة ثواب العربية الإسلامية" #: ../Thawab/gtkUi.py:154 msgid "Import Shamela .bok files" msgstr "استيراد ملفات bok من الشاملة" #: ../Thawab/gtkUi.py:216 msgid "Advanced options" msgstr "خيارات متقدمة" #: ../Thawab/gtkUi.py:221 msgid "Performance tuning:" msgstr "معايرة الأداء" #: ../Thawab/gtkUi.py:228 msgid "in memory" msgstr "في الذاكرة" #: ../Thawab/gtkUi.py:229 msgid "faster but consumes more memory and harder to debug." msgstr "أسرع لكنها تستهلك الكثير من الذاكرة وصعبة التمحيص" #: ../Thawab/gtkUi.py:238 msgid "Release Major:" msgstr "الإصدار الأكبر:" #: ../Thawab/gtkUi.py:243 msgid "Release Minor:" msgstr "الإصدار الأصغر:" #: ../Thawab/gtkUi.py:252 ../Thawab/gtkUi.py:278 msgid "Prefix:" msgstr "السابقة:" #: ../Thawab/gtkUi.py:258 ../Thawab/gtkUi.py:284 msgid "Suffix:" msgstr "اللاحقة:" #: ../Thawab/gtkUi.py:264 msgid "only at line start" msgstr "فقط على بداية الكلمة" #: ../Thawab/gtkUi.py:268 ../Thawab/gtkUi.py:291 msgid "in between spaces:" msgstr "المسافات البينية:" #: ../Thawab/gtkUi.py:269 ../Thawab/gtkUi.py:292 msgid "no spaces" msgstr "دون مسافات" #: ../Thawab/gtkUi.py:270 ../Thawab/gtkUi.py:293 msgid "optional white-space" msgstr "مسافة واحدة اختيارية" #: ../Thawab/gtkUi.py:271 ../Thawab/gtkUi.py:294 msgid "optional white-spaces" msgstr "مسافات اختيارية" #: ../Thawab/gtkUi.py:344 ../Thawab/gtkUi.py:357 msgid "working ..." msgstr "يجري العمل ..." #. canceled #: ../Thawab/gtkUi.py:413 ../Thawab/gtkUi.py:414 ../Thawab/gtkUi.py:432 #: ../Thawab/gtkUi.py:433 msgid "Canceled" msgstr "ملغي" #. windows can't move an opened file #. FIXME: close ki in a clean way so the above code works in windows #: ../Thawab/gtkUi.py:448 ../Thawab/gtkUi.py:461 ../Thawab/gtkUi.py:817 #: ../Thawab/gtkUi.py:828 msgid "Done" msgstr "تم" #: ../Thawab/gtkUi.py:462 msgid "Convert Book, Done" msgstr "" #: ../Thawab/gtkUi.py:482 msgid "Select files to import" msgstr "اختر الملفات كي تستورد" #: ../Thawab/gtkUi.py:489 msgid "Shamela BOK files" msgstr "ملفات BOK الخاصة بالشاملة" #: ../Thawab/gtkUi.py:667 msgid "Open Link in New Tab" msgstr "فتح الرابط في لسان جديد" #: ../Thawab/gtkUi.py:716 msgid "Manage search index" msgstr "إدارة فهارس البحث" #: ../Thawab/gtkUi.py:727 msgid "Queue new books" msgstr "دفع الكتب الجديدة" #: ../Thawab/gtkUi.py:744 msgid "Indexing jobs canceled" msgstr "تم إلغاء مهمات الفهرسة" #: ../Thawab/gtkUi.py:761 #, python-format msgid "Indexing ... (%d left)" msgstr "يجري الفهرسة ... (بقي %d)" #. Gtk.main_iteration_do(True) #: ../Thawab/gtkUi.py:766 msgid "No indexing jobs left" msgstr "لم يتبق أي مهمات فهرسة" #: ../Thawab/gtkUi.py:769 #, python-format msgid "Indexing %d jobs, Done" msgstr "إنتهى %d مهمات فهرسة" #: ../Thawab/gtkUi.py:776 msgid "Misc. Fixes" msgstr "إصلاحات متنوعة" #: ../Thawab/gtkUi.py:788 msgid "" "Those procedures are to be used in case of " "emergency only,\n" "for example to recover power failure." msgstr "" "تستخدم هذه الإجراءات في الحالات الطارئة فقط,\n" "مثل عطل ناتج عن انقطاع الطاقة الكهربائية." #: ../Thawab/gtkUi.py:793 msgid "remove search index" msgstr "حذف فهرس البحث" #: ../Thawab/gtkUi.py:794 msgid "you will need to re-index all books" msgstr "ستحتاج للقيام بإعادة فعرسة كل الكتب" #: ../Thawab/gtkUi.py:799 msgid "remove meta data cache to generate a fresh one" msgstr "إزالة نسخة الميتا الخبيئة وتوليد واحدة جديدة" #: ../Thawab/gtkUi.py:800 msgid "instead of incremental meta data gathering" msgstr "عوضا عن جمعها تزايديا" #: ../Thawab/gtkUi.py:810 msgid "" "You will need to recreate search index in-order to search again.\n" "Are you sure you want to remove search index?" msgstr "" "يتوجب عليك إعادة توليد فهارس البحث حتى يعمل البحث مجددا.\n" "هل أنت متأكد من رغبتك في حذف فهارس البحث؟" #: ../Thawab/gtkUi.py:815 #, python-format msgid "unable to remove folder [%s]" msgstr "غير قادر على حذف المجلد [%s]" #: ../Thawab/gtkUi.py:820 msgid "Are you sure you want to remove search meta data cache?" msgstr "هل أنت متاكد من رغبتك في إزالة خبيئة الميتا؟" #: ../Thawab/gtkUi.py:825 #, python-format msgid "unable to remove file [%s]" msgstr "غير قادر على إزالة الملف [%s]" #: ../Thawab/gtkUi.py:860 msgid "Open a new tab" msgstr "فتح لسان جديد" #: ../Thawab/gtkUi.py:868 msgid "Import" msgstr "استيراد" #: ../Thawab/gtkUi.py:869 msgid "Import .bok files" msgstr "استيراد ملف bok" #: ../Thawab/gtkUi.py:875 msgid "Index" msgstr "فهرسة" #: ../Thawab/gtkUi.py:877 msgid "Create search index" msgstr "إنشاء فهرس البحث" #: ../Thawab/gtkUi.py:885 msgid "Zoom in" msgstr "تكبير" #: ../Thawab/gtkUi.py:890 msgid "Makes things appear bigger" msgstr "تجعل الأشياء تبدو أكبر" #: ../Thawab/gtkUi.py:896 msgid "Zoom out" msgstr "تصغيير" #: ../Thawab/gtkUi.py:899 msgid "Makes things appear smaller" msgstr "تجعل الأشياء تبدو أصغر" #: ../Thawab/gtkUi.py:905 msgid "1:1 Zoom" msgstr "تكبير 1:1" #: ../Thawab/gtkUi.py:908 msgid "Restore original zoom factor" msgstr "تعيد التكبير الأصلي" #: ../Thawab/gtkUi.py:916 msgid "Fixes" msgstr "إصلاحات" #: ../Thawab/gtkUi.py:918 msgid "Misc Fixes" msgstr "إصلاحات متنوعة" #: ../Thawab/gtkUi.py:926 msgid "Help" msgstr "مساعدة" #: ../Thawab/gtkUi.py:927 msgid "Show user manual" msgstr "إظهار دليل المستخدم" thawab-4.1/po/de.po000066400000000000000000000145561305262755200142140ustar00rootroot00000000000000# Translation of thawab templates to Arabic # Copyright (C) 2008-2010, ojuba.org # This file is distributed under the same license as the thawab package. # cegerxwin , 2010 # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-02-16 04:06+0200\n" "PO-Revision-Date: 2010-08-27 23:53+0100\n" "Last-Translator: cegerxwin \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../thawab.desktop.in.h:1 ../Thawab/gtkUi.py:837 ../Thawab/gtkUi.py:849 msgid "Thawab" msgstr "Thawab" #: ../thawab.desktop.in.h:2 msgid "Electronic Arabic/Islamic Encyclopedia" msgstr "Elektronische Arabisch/Islamische Ezyklopädie" #: ../Thawab/gtkUi.py:154 msgid "Import Shamela .bok files" msgstr "Importiere Shamela .bok Dateien" #: ../Thawab/gtkUi.py:216 msgid "Advanced options" msgstr "Erweiterte Optionen:" #: ../Thawab/gtkUi.py:221 msgid "Performance tuning:" msgstr "Geschwindigkeits Feineinstellungen" #: ../Thawab/gtkUi.py:228 msgid "in memory" msgstr "im Speicher" #: ../Thawab/gtkUi.py:229 msgid "faster but consumes more memory and harder to debug." msgstr "" "schneller aber verbraucht mehr Speicherplatz und die Fehlersuche ist " "aufwändiger" #: ../Thawab/gtkUi.py:238 msgid "Release Major:" msgstr "Hauptveröffentlichung:" #: ../Thawab/gtkUi.py:243 msgid "Release Minor:" msgstr "Kleinere Veröffentlichungen:" #: ../Thawab/gtkUi.py:252 ../Thawab/gtkUi.py:278 msgid "Prefix:" msgstr "Präfix:" #: ../Thawab/gtkUi.py:258 ../Thawab/gtkUi.py:284 msgid "Suffix:" msgstr "Suffix:" #: ../Thawab/gtkUi.py:264 msgid "only at line start" msgstr "nur am Zeilenanfang" #: ../Thawab/gtkUi.py:268 ../Thawab/gtkUi.py:291 msgid "in between spaces:" msgstr "in den Zwischenräumen:" #: ../Thawab/gtkUi.py:269 ../Thawab/gtkUi.py:292 msgid "no spaces" msgstr "keine Zwischenräume" #: ../Thawab/gtkUi.py:270 ../Thawab/gtkUi.py:293 msgid "optional white-space" msgstr "optional Leerzeile" #: ../Thawab/gtkUi.py:271 ../Thawab/gtkUi.py:294 msgid "optional white-spaces" msgstr "optional Leerzeilen" #: ../Thawab/gtkUi.py:344 ../Thawab/gtkUi.py:357 msgid "working ..." msgstr "läuft..." #. canceled #: ../Thawab/gtkUi.py:413 ../Thawab/gtkUi.py:414 ../Thawab/gtkUi.py:432 #: ../Thawab/gtkUi.py:433 msgid "Canceled" msgstr "Abgebrochen" #. windows can't move an opened file #. FIXME: close ki in a clean way so the above code works in windows #: ../Thawab/gtkUi.py:448 ../Thawab/gtkUi.py:461 ../Thawab/gtkUi.py:817 #: ../Thawab/gtkUi.py:828 msgid "Done" msgstr "Erledigt" #: ../Thawab/gtkUi.py:462 msgid "Convert Book, Done" msgstr "" #: ../Thawab/gtkUi.py:482 msgid "Select files to import" msgstr "Wähle zu importierende Dateien aus" #: ../Thawab/gtkUi.py:489 msgid "Shamela BOK files" msgstr "Shamela BOK Dateien" #: ../Thawab/gtkUi.py:667 msgid "Open Link in New Tab" msgstr "Öffne Link im neuen Tab" #: ../Thawab/gtkUi.py:716 msgid "Manage search index" msgstr "Verwalte Suchindex" #: ../Thawab/gtkUi.py:727 msgid "Queue new books" msgstr "Neue Bücher in der Warteschlange aufstellen" #: ../Thawab/gtkUi.py:744 #, fuzzy msgid "Indexing jobs canceled" msgstr "Keine Indizierungsaufgaben mehr vorhanden" #: ../Thawab/gtkUi.py:761 #, python-format msgid "Indexing ... (%d left)" msgstr "Indizierung...(%d left)" #. Gtk.main_iteration_do(True) #: ../Thawab/gtkUi.py:766 #, fuzzy msgid "No indexing jobs left" msgstr "Keine Indizierungsaufgaben mehr vorhanden" #: ../Thawab/gtkUi.py:769 #, fuzzy, python-format msgid "Indexing %d jobs, Done" msgstr "Keine Indizierungsaufgaben mehr vorhanden" #: ../Thawab/gtkUi.py:776 msgid "Misc. Fixes" msgstr "Misc. Fixes" #: ../Thawab/gtkUi.py:788 #, fuzzy msgid "" "Those procedures are to be used in case of " "emergency only,\n" "for example to recover power failure." msgstr "" "Diese Prozedur sollte nur in Notfällen benutzt " "werden,\n" " z.B. um nach einem Stromausfall die Daten wiederherzustellen." #: ../Thawab/gtkUi.py:793 msgid "remove search index" msgstr "entferne Suchindex" #: ../Thawab/gtkUi.py:794 msgid "you will need to re-index all books" msgstr "Neu-Indizierung aller Bücher notwendig" #: ../Thawab/gtkUi.py:799 msgid "remove meta data cache to generate a fresh one" msgstr "entferne meta Daten Speicher um eine neue zu generieren" #: ../Thawab/gtkUi.py:800 msgid "instead of incremental meta data gathering" msgstr "anstatt der inkrementellen meta Daten ansammlung" #: ../Thawab/gtkUi.py:810 msgid "" "You will need to recreate search index in-order to search again.\n" "Are you sure you want to remove search index?" msgstr "" "Du musst den Suchindex noch einmal um danach eine Suche durchführen zu " "können.\n" "Bist du sicher, das du den Suchindex entfernen möchtest?" #: ../Thawab/gtkUi.py:815 #, python-format msgid "unable to remove folder [%s]" msgstr "Fehler beim entfernen des Ordners [%s]" #: ../Thawab/gtkUi.py:820 msgid "Are you sure you want to remove search meta data cache?" msgstr "Sind sie sicher, das sie die Suchmetadaten Speicher entfernen möchten?" #: ../Thawab/gtkUi.py:825 #, python-format msgid "unable to remove file [%s]" msgstr "Fehler beim entfernen der Datei [%s]" #: ../Thawab/gtkUi.py:860 msgid "Open a new tab" msgstr "Öffne im neuen Tab" #: ../Thawab/gtkUi.py:868 msgid "Import" msgstr "Importieren" #: ../Thawab/gtkUi.py:869 msgid "Import .bok files" msgstr "Importiere .bok Dateien" #: ../Thawab/gtkUi.py:875 msgid "Index" msgstr "Index" #: ../Thawab/gtkUi.py:877 msgid "Create search index" msgstr "Erzeuge Suchindex" #: ../Thawab/gtkUi.py:885 msgid "Zoom in" msgstr "Einzoomen" #: ../Thawab/gtkUi.py:890 msgid "Makes things appear bigger" msgstr "Macht das Dinge größer wirken" #: ../Thawab/gtkUi.py:896 msgid "Zoom out" msgstr "Auszoomen" #: ../Thawab/gtkUi.py:899 msgid "Makes things appear smaller" msgstr "Macht das Dinge kleiner wirken" #: ../Thawab/gtkUi.py:905 msgid "1:1 Zoom" msgstr "1:1 Zoom" #: ../Thawab/gtkUi.py:908 #, fuzzy msgid "Restore original zoom factor" msgstr "Original Zoomgröße wiederherstellen" #: ../Thawab/gtkUi.py:916 msgid "Fixes" msgstr "Fixes" #: ../Thawab/gtkUi.py:918 msgid "Misc Fixes" msgstr "Misc Fixes" #: ../Thawab/gtkUi.py:926 msgid "Help" msgstr "Hilfe" #: ../Thawab/gtkUi.py:927 msgid "Show user manual" msgstr "Zeige Benutzerhandbuch" thawab-4.1/readme000066400000000000000000000000441305262755200140110ustar00rootroot00000000000000إقرأني أولا Read me first thawab-4.1/setup.py000066400000000000000000000023511305262755200143460ustar00rootroot00000000000000#! /usr/bin/python import sys, os, os.path from distutils.core import setup from glob import glob # to install type: # python setup.py install --root=/ def no_empty(l): return filter(lambda (i,j): j, l) def recusive_data_dir(to, src, l=None): D=glob(os.path.join(src,'*')) files=filter( lambda i: os.path.isfile(i), D ) dirs=filter( lambda i: os.path.isdir(i), D ) if l==None: l=[] l.append( (to , files ) ) for d in dirs: recusive_data_dir( os.path.join(to,os.path.basename(d)), d , l) return l locales=map(lambda i: ('share/'+i,[''+i+'/thawab.mo',]),glob('locale/*/LC_MESSAGES')) data_files=no_empty(recusive_data_dir('share/thawab/', 'thawab-data')) data_files.extend(locales) setup (name='thawab', version='3.0.10', description='Thawab Arabic/Islamic encyclopedia system', author='Muayyad Saleh Alsadi', author_email='alsadi@ojuba.org', url='http://thawab.ojuba.org/', license='Waqf', packages=['Thawab'], scripts=['thawab-gtk','thawab-server'], classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: End Users/Desktop', 'Operating System :: POSIX', 'Programming Language :: Python', ], data_files=data_files ) thawab-4.1/th-set-meta.py000077500000000000000000000044461305262755200153500ustar00rootroot00000000000000#! /usr/bin/python # -*- coding: UTF-8 -*- """ Setting meta data for thawab files Copyright © 2010, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path import Thawab.core from getopt import getopt, GetoptError def usage(): print '''\ Usage: %s [plbvRrtayAYBVck] VALUE ... FILES ... Where: \t-p VALUE\t set repo name to VALUE \t-l VALUE\t set language name to VALUE \t-b VALUE\t set kitab name to VALUE \t-v VALUE\t set version name to VALUE \t-R VALUE\t set release major name to VALUE \t-r VALUE\t set release minor name to VALUE \t-t VALUE\t set kitab type to VALUE \t-a VALUE\t set author name to VALUE \t-y VALUE\t set author death year to VALUE \t-A VALUE\t set original kitab author name to VALUE \t-Y VALUE\t set original kitab author death year to VALUE \t-B VALUE\t set original kitab name to VALUE \t-V VALUE\t set original kitab version to VALUE \t-c VALUE\t set classification to VALUE \t-k VALUE\t set keywords to VALUE ''' % os.path.basename(sys.argv[0]) meta_keys={ '-p':'repo', '-l':'lang', '-b':'kitab', '-v':'version', '-R':'releaseMajor', '-r':'releaseMinor', '-t':'type', '-a':'author', '-y':'year', '-A':'originalAuthor', '-Y':'originalYear', '-B':'originalKitab', '-V':'originalVersion', '-c':'classification', '-k':'keywords' } metas=set(meta_keys.values()) try: opts, args = getopt(sys.argv[1:], "hp:l:b:v:r:R:t:a:y:A:Y:B:V:c:k:", ["help"]) except GetoptError, err: print str(err) # will print something like "option -a not recognized" usage() sys.exit(1) opts=dict([(meta_keys.get(i,i),j) for i,j in opts]) if opts.has_key("-h") or opts.has_key("--help") or len(opts)==0 or not args: usage() sys.exit(1) th=Thawab.core.ThawabMan() for uri in args: ki=th.getKitabByUri(uri) #print ki.meta for i in opts: ki.meta[i]=opts[i] #print ki.meta ki.setMCache(ki.meta) thawab-4.1/thApiTest.py000077500000000000000000000016331305262755200151200ustar00rootroot00000000000000#! /usr/bin/python # -*- coding: UTF-8 -*- import os, os.path, Thawab.core th=Thawab.core.ThawabMan() th.searchEngine.reindexAll() # th.loadMeta() # to detect new files and add them ..etc. meta=th.getMeta() print meta.getUriList() th.searchEngine.reindexKitab('/home/alsadi/.thawab/db/uthaymine.ki') ## export to xml #from cStringIO import StringIO #s=StringIO() #ki=Thawab.core.Kitab('/home/alsadi/.thawab/tmp/THAWAB_xqkca0.ki3001') #n=ki.root.toXml(s) #print s.getvalue() ## export to HTML or wiki #import Thawab.core #ki=Thawab.core.Kitab('/home/alsadi/.thawab/tmp/THAWAB_xqkca0.ki3001') #s=ki.root.toHTML() #print s ##searching the index #for i in th.searchEngine.queryIndex('إنشاء'.decode('utf-8')): print i['title'] #for i in th.searchEngine.queryIndex('إنشاء kitab:pyqt4'.decode('utf-8')): print i['title'] #for i in th.searchEngine.queryIndex('إنشاء kitab:test'.decode('utf-8')): print i['title'] thawab-4.1/thawab-data/000077500000000000000000000000001305262755200150105ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/000077500000000000000000000000001305262755200162755ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/default/000077500000000000000000000000001305262755200177215ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/default/static/000077500000000000000000000000001305262755200212105ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/default/static/fx.css000066400000000000000000000025151305262755200223420ustar00rootroot00000000000000/* fx */ #async_tips_div { background-color:rgba(255,255,200,0.9); } #overlay { opacity:0.7; } .showOnFocus { opacity:0.4; } .showOnFocus:active, .showOnFocus:focus { opacity:0.75; } .showOnFocus:hover { opacity:1; } .blurOnFocus { opacity:1.0; } .blurOnFocus:active, .showOnFocus:focus { opacity:0.75; } .blurOnFocus:hover { opacity:0.4; } #minisearch input { background:rgba(255,255,255,0.6); } #absnav { -webkit-border-radius:20px; -moz-border-radius:20px; border-radius:20px; padding:0 20px; -webkit-box-shadow:0 0 8px rgba(240,240,240,0.4); -moz-box-shadow:0 0 8px 8px rgba(240,240,240,0.4); } #absnav2, #absnav3{ -webkit-border-radius:20px; -moz-border-radius:20px; border-radius:20px; padding:0 20px; -webkit-box-shadow:0 0 8px rgba(240,240,240,0.4); -moz-box-shadow:0 0 8px 8px rgba(240,240,240,0.4); } #absnav3{ padding:0 5px; } #minisearch { -webkit-border-bottom-left-radius:8px; -moz-border-radius-bottomleft:8px; border-bottom-left-radius:8px; -webkit-border-bottom-right-radius:32px; -moz-border-radius-bottomright:32px; border-bottom-right-radius:32px; padding:0 8px 2px 0; } #container{ -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; /* -webkit-box-shadow:0 0 8px rgba(128,128,128,0.4); */ -moz-box-shadow:0 0 8px 4px rgba(128,128,128,0.4); } #absnav3 { position: fixed; } thawab-4.1/thawab-data/themes/default/static/ie-fx.css000066400000000000000000000004131305262755200227300ustar00rootroot00000000000000/* fx-ie */ #overlay { filter:alpha(opacity=70); } .showOnFocus { filter:alpha(opacity=50); } .showOnFocus:hover { filter:alpha(opacity=100); } .blurOnFocus{ filter:alpha(opacity=100); } .showOnFocus:hover { filter:alpha(opacity=40); } #absnav3{ display:none; } thawab-4.1/thawab-data/themes/default/static/img/000077500000000000000000000000001305262755200217645ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/default/static/img/about.gif000066400000000000000000000021571305262755200235720ustar00rootroot00000000000000GIF89a:;<<>AAJADGLXY\Yk fcfpl X\]^^ahlrtvx|p i}} ̎ۇ܈܉Вݏޓ  ΄)ې)&$8\R& 3bMLy!Ťq͙/Wx@G-u - P@P0@0 4@;thawab-4.1/thawab-data/themes/default/static/img/bg-d.png000066400000000000000000000035501305262755200233060ustar00rootroot00000000000000PNG  IHDRt}WsRGBbKGD pHYs  tIME6YߜtEXtCommentCreated with GIMPWIDAThђ,?ar{! p2nm^5Il#jZ߿ef A/LB0. wa BcZ g`F< ^L_-gZ }c9^yǥcؒ\"g1wdiK>[0xw1p_};^}g` >~ܳt14~]s#>y:sWbd ;\ >'zslps ^t̀@1ƽ1e`;3fs?;XZѮhw_IEMUf#Tk3\CFswstD=Pe+p )j=:4C I E:=@PtmA3,0l{Mvqg]μRq;c8\) b$~܉CP=,35M hOIB^.FnLV/=&ŏ DnYNڀ@( .ZKN1+/KΌ_ן1q?@6~veA_^+9ibc ̵ljgY6Xz]Hb$Ucږ']\YSJ;+($U,;ʇfKex|ٿPYZRbBHj(u9WKV,DRcҖ,%ݴz{ቧqGJzIR=g:>VNL oNo%Fs®4(=<2aad(E.ӊd)Ͻ9q՜,uրEVc|e jVdh 0cك4da")bkY=ةӒѲȔ5!ˌsCڊ%3JJaQB>DfmpD[*Ŝdn'2I0YUeV۱uJva+3hZeGv*,qКhfK)9{?Vj%G99zs G FyP3_>p,XTtfD>NJ+[1YhЙ$'J l-R}Yh`W$p8NDx>3UE*( X~b#w8βQʟ~Q"(FwYYP*f'dvNoG%-e` \Q3e*=:M U@feqc˫_̵DIENDB`thawab-4.1/thawab-data/themes/default/static/img/bg-l.png000066400000000000000000000064441305262755200233230ustar00rootroot00000000000000PNG  IHDRV4sRGBbKGD pHYs  tIME&+[ftEXtCommentCreated with GIMPW IDATx\Yv8Kץv}FwB1H )JL齲eJ Bş? xSʏ?V}Mw3 <lׂ6=`7)lYG~>> 3V{=I.?X?2#}oRNCS6'Ja>^,w)snY|eY{!>^c%^wQ`XPfF)vDf9 =P(@a"RX}{{[rǯ_S_Lo*Ɩ)e ʬFÈKsooRH HALߖ e2(x{}U"0+'K?>>hB' M>`Ab-?͖X~}}մ#N!Fs.oZʔ ټ^^^V}}}؈b"3J B [Y~}yĄ͉YLK*.̰ lѰfy/eS,"񸊲0x{ @:$e 5lx:B]}Zx;Ii崴Pv Vl Zʖ&=2;N4i|ǝSʣlv^]oۭ;ϷG( lmOY&"p l ιֽwYVuRx\&n)lZأlf)\ l˗eiX-|UmRN=-=E P0 eeKU뵫8Gْlam6fs9 O[95?k(SNgk2AXf/VR6AW#8b3p;|]U ,&~2G3(+qU@XUJt-8S[2^EOY^{H)k(zZ^#9Q6oѵ-ޚ4p+O?P o>> 1ot[kܑnynI0/CfA g܊ilo.cp"+0Ɍң+X3i]/f) ̝eO% ˦AxtySV/0[m|9aQrtUYWmW5~1V?m|=y{scglݲyF笧[n*whf$TÉUlw: ֐rUvr:AO D=$ W{[ee/I~++#"|`nMy^Ouv[*JOnQIx帺g҂$nwQ{j)~9~phٜnM,u˹PA+5M@ | rベq~-ej1.]\Up쉖ɕ#pgLt~JCN`ZCk3[AIQn4K.`"6z.̤F'&#sv|Ӳ(dҌ;u C3&lMiZBZR(Ӡ$q.VIiq!M+g^ΗCГ1n㶕O H%4An%DKXOo21xyHBrU#vrhW{6t@uk}>6=+wŰYMak%7 ۸ua%ÁYmNJ@ȒykaE5n1ADnMVQՊ4yev~ѭ&̞}Mr5*p?_TmNFu`𣖚$ Rv.]kiaSA3`N}AS&Uq+~AߍJezSƴV5RTy(,ɌS,i#õjf&n) ZemD"V(:m'D_MŨճm]fٓӪ44M3ᜎѲfoM ԪA`*!dϞR:t_r!L֑$Àg~择0SIdjNqCKmTj &-BLW.?@AHL$U4n]_"djqM.fSM`_mQ&6uQ2ۥQAw/)6@Dc 9EeF۩u)5j 6a( W'KDRͦiMU4 su+!2ET/xk * H3F3gδ%XIDn0 k_#f9N87ɪ̰ʡ9wC1N _RZRp;&+TM?Iw`gb$VLuV#YOD=5ֺ0ic(#=;5IENDB`thawab-4.1/thawab-data/themes/default/static/img/close.gif000066400000000000000000000023061305262755200235610ustar00rootroot00000000000000GIF89a     ''%%6+++3/00723 8811955:>9F @@;<B:6I= ?@IHC>=GP@L CKHQ%T;VRNPQSQWGWSSO:XUOOW7TTZ^Z _]]ZZa7`4j ^^]]di4fjeOePkml7rtsoQsnly8rr}7}grrqqrmxYzWgzl݃gyrςGրo~s߂s݃qw̒ߌጌ⌌⍌⎌Ꮛ␋㐌ܖՙޚ夤⥥禦㨟覦ݭ賳ӻ赵ijźʿѷ!Created with GIMP! , HH!@ gٶQAVkզ0 c1kldȊ UeANnl``Ie+Ps ۽rr2͞Jl @$Jɓz-#T~DHZocd&ˎ dNMvUˮ1WX P(x*eZ{8&J xx"G(CMF"7VPH HA{zZn$@d` k>x>SM%#( Qsc*x``'4qF8D+B/qa BKlX%4 0dAU`ă#p@BCЁ31Dl1` 8q \T;thawab-4.1/thawab-data/themes/default/static/img/code.gif000066400000000000000000000022721305262755200233700ustar00rootroot00000000000000GIF89a'()* 5: X Y!Z#\$]%^$_&_&_'_&`'a'b'c(b)b(c)e*d*e+e)e*e+e*f*g+g*g-g,h-i,i-i-j.j/k/l/l/m0l1l2n0m2m0n1n2n3n3o 3p 3q 4p5q5q 6q 4r 7s 5r 5s 6t 7t 8t 8u 8t 8u 8u 8v 9v 8v 8w :w ;w =y :y ;y ;z ;z =z | ?~@} A~ A~@DDDDFGEDEFEGEFIJLNJKMONNPTVWYXY[\\]^dc b!d d"d$i&l)o*p*p+p+s.s/x2{4|6~69;?ADKOORRUUT[_bbdffx|؃كߐ!,q HYȰaC=b%ŋFQPZ ɓ(Kvb$P%K0-QӒ$J51yE$H@ɺ(Fd$)0b-NRM֡Di8!C}n 2dmAMD;zȹW_wnNlظrKǐJ|;oW?;Ų,(i[YW˿%%~Fb}zVyPNǴ56oW \[ǹ''hZ ph"}Gz bxoYxPN˹47|hY b`Ⱦ$"yfXpn}F blWxWT̽-0mY fe##hYtv~}Gwbr}VSɺ-.s hgƽ ~|\ovtzz}F c*0&(86}}~} XB@oo}GvZ| i L"#~PRw~L~  z ~ ~{  |?F;thawab-4.1/thawab-data/themes/default/static/img/forum.gif000066400000000000000000000021721305262755200236050ustar00rootroot00000000000000GIF89a9z&7v(={-<1/U5^3c=m aCaGiLlTqDjJkCgHkUsYqp{ImMqOtSqRv]V{b}zt|bgzfjry{tzɃӃģÅ͌Α֚џߤ!,# H#]Eb! <"5 !F y,RQr΁f%1CJ :u@#Jr1R@! 3p rIl1AqCgPF$&yԨ6d Dq"7K:'X[ù=a v~ Btf@ "k Qpftfrͫ ǡ:cp~"<X%#^Zn:ʾctN^>X܏?p]}>ɽkJuW =^:2PJ!NZ hxB7raLQ°]ӥg&z96RR겳n ߾KIZXHZtW㏍@=k8cIJyP*bϼɅ|S;hD<"}>żQ۟MN14u,^&x!#+/X?B {9B/WE]…x^ƾq5APڃ׷/L}uo\㫏V:cH5|<.9Mre'|9^%z[ 6> l#m{QmWL=b]11q\E~|_~dz'iW(nD["#K5F,SuѰktOv'sחVrYW̩#gDmZxxZPV!Gk@ߟOj)vL2E:os\ɨWwo%16>HQ Z 4{l-x5:c6bU`!1= 03۰86!Fj t_zGd?H$F-JpěuP7=\][u9_=' ꞑAsS KxJS I-I#7G!h_nx/ /δ?ٚ=#|z1Dž+mZϻa "62l7zV0Z3=]ebfmAbbC$k"CC1V^'OU !a3[\l9<~v50 Q(Aő`pt`eY92}P3{F8rFeo-n՗9}h _?NroYX*ɀsp.'Dk vR3mLb4:]O݆?p΍ |+::,35-fq($vmd`n>=Go=_i\bqu4䇖B}oCwž`+Q\GΚ̊? :cyˇQd8vk XL$lu|^u8j"bxqLu.Y]^nQϧ+W%e2{m)Y<;3+2|LY4hI !HK`$ Jbj4j-^>OP#vG!62b#CHW1DuHDMWCu;ѷbp\v!d"0HxL3Vw|7ZZ@3I}Rwcwq_is,O99_{66t8 J]s/9?~Ŝ:o~+z,q_9,p $&&hafDi0 xsXOctt~mVӧYSlyE0 э9Sqbxs \K:~32O9(Otџ;B\.GL'UNϙ]尃.堃$Izd=|/DOh]t y;3g?88\u??\/ZWO>STL ~IGP hcrOWsX7O? cxڢG{-z,AHR>!q=#{M!>P2)Dau峸{G?dmpƱ#/W`Gg-[ OI0βbl>BOu >ZkBB|Q#řc8Noy^`qD(!b!gζuw,^ƷϼpcT8[O5V2*̊f*}C֎7|;(r\sʠ</|O t,E3> h\wP9]\"~~>ј$1T9s|Ύ:AJ0ÏAFNbX!WP>;voI2"eOM/rّ۸~[Ɨrr,SГԅ#|* N* .D╈l®޾u1FbS.x A]gbm}HӿYkβcj%$}ea%͛t6}gH% S>zKVLź*OL5?^He}xמ)3T9g0x"He5{6Q CT&(yY4D;z3>J8i */B ^hPG|;elMNwL(,;-EW%*4^8;IV]*#:1EjVx%]yl9P{SPNHIصށ"a΀qiipL+'x()TGt A a8rs=xt-;x'ٕRCrϪNZwVE]{)X2k._HۆH4Z5ڧz!DMoRSllS\*4*YeeOHj|Vv͢wf Z4"!n5K$/l@Wm \1@YJyS˒+}BG5 n𙃯vë+a;|xS!+GS"*|M'MVS`zQ8p TzC(͜eܗ9)YF6m8G_W/D@ ̽cBW*%i!/.-n45gMP Qq"[t.6VttA;~)=aXr _m5ˍcG)J`^G\PNtEe/RIgi V7k8GYЯtmƩ*CR jD7F$3jo% Pm=揃v\V4 "=/`(c&NEu  es#,r@)(vN|z"(t{4TXynzgo6fIENDB`thawab-4.1/thawab-data/themes/default/static/img/go-home.gif000066400000000000000000000021001305262755200237770ustar00rootroot00000000000000GIF89a).0-23.34.46/56044167477=;728859969848:49:7::6<<6<=6<>9<;:=:;=;:>;<>;:=<8>>=?@>I`Ya]d]jDFIMMNRS[Yb w?^ahg}DHJPRy|!, Ha@P 64 \R@Bcta#2{4,ƙ4i0#g:>1n(] !իV4ᣧٲwb000 ɝKw T߿! AU ĊG -z B'B%F%F &M*N0I*S-Y+Q/R3S4U9Q1_1^!7X5a4c6`8k=q@uCz"An&Gy(IyE.P0S;W6W7X8Z M!P"R$V+Y;^<_>a%W%W.\9b:cEaAcEiQmOyPpc{FkGl@jNpMqOtVv[yQvRwRxZ{NuW}Z}YZ\^_jejqqnl`bddowy}q|ghjjnkoquswvpprrutvz|ԇÆÀńLJǍŽÊƇˈˋȍɎˉ͊͏̑ǔʔː͑Ζ͖ΗΙ̘π؁؈ޙОПҠӢҡԪ٪ګڌ!,Q H*B7yXS>P#N6BiQ(pbS'P04*md!K8QѠ="qE ?5D!A|`B+XТ}1B@*r8RE˘6o޸I+",DEH)cƌ2^A@fM:rlA2A$H1#O P`BCѓ)F P4ؐ"`|`@;thawab-4.1/thawab-data/themes/default/static/img/go-prev.gif000066400000000000000000000020331305262755200240300ustar00rootroot00000000000000GIF89a - 6 6 : > !<*;$A$D $F %G/C'I5K.S-U:Uq"An3Lj$Du!E|!G}&Hy&J~,M}.Q9Xv6\zE!G1S2S8[;^ M!N"R$V-Y;^5_?b?v<`%W&X2`>g;gBc@c@dDkDhLmHlImDoMqTvQwQxUzU{U~V|YW\^j``ma```bgjtqtzppyyx}efcfiijjlnolmop{~}}prrvtww{nw~ՂÁĂńņĄljǍďǀȀɂ˅Ȇɇˇˈˋʏʌ͖̍̎̏̎Ƒˑ͓͓΂قڈߔЕЗҜ՝֞։بީް!,i HCv8D8BV,*/&+C24tJIPEp "B B%JOWrੑO6u#E0 3(Ud"Df|B#A Uɓ'YwPC'8b :plH*H(p "Lqf ,N" &0I3 >s䠉$ =$H@@쑣%J "ԇΓ "i5(5jPiE`%€l(] ճg;thawab-4.1/thawab-data/themes/default/static/img/go-up.gif000066400000000000000000000020221305262755200234760ustar00rootroot00000000000000GIF89a ( 1 8 < ? ?"? !B !C "D "D $I %J(L(O'I+U*Q+P.P/Z4Q2V0Z2`3b7e8`8i9j >j@oBw Eh$Gm&Kh Bt#Dt Gz)J{/Mx,N1Lp)Vr)Qx.\y8V|D/U0R2X6X9\#S$U=b%W0_V[6o2DӮeiB",B9q؈0 55--P+#E*'ڵQ,ml_wl l96TD(")^MkD-XDTx-Q&hG"S*Xq5o)iID&O#@nL!;l*A 7aÊ Dq3x32NlD 7(KT&\p5۸B| $233Qd4baI`CLTpTXCM6C49$8) 4R'(T#d{|UT  6lCKU(@$*F"%{( ^\H>I*p.$AH'T@&{TPECCEGaĨ@0 i҇F /`H­`0B Pit$}h" ֖,H[ f b 0 @iR SI~nB2Bܛo"o; A0@@}6HVf\@Ӑø{q)ȟ-[ s#h 6m ?̒Rl4*pF|Ia.f Z̵&%xms؆`C[Qk[ۃl1۝C#:kς I q_4/ 6%8; DAux((xΞ-]њ8Dm*{mhrssij /֊}׼8 ڳ|#nHO-6(3LX˟ֿP|xAtibaA O K}Ay A9ׅAa@!X-z8'LA@!Ct 3( u끞k 0B 0Qp(`6`f¢]  Zk >@ 0!T dd )/@oP*X["2^hCd 1A!2D 6ET a"Ff0!6A"i ̔ nz 8 ;thawab-4.1/thawab-data/themes/default/static/img/loading.gif000066400000000000000000000066221305262755200240760ustar00rootroot00000000000000GIF89a |}YZbcЖ`a "79ة۰И45LNމʊRS@BCEܲȆ)+;<ݴijϔ9;')޶5702%'>@ΒSU,.Ȅӟӝ24љGIĠԠWXܮڮln<>қ̍ǃ̏$&ŷ޷ˋբyzz|rs໸߹ɈӐ͑jlIJBCsupqƁ! !Created with gimp! NETSCAPE2.0, 34 @9A 5 *<&+NFK  %!K'!"чD , ƚB .6 #>;$*-$^+!BA;LLxэj R GT[ nĂP UABDhׄQ c r:W *1A},lpQR!@| p(JP4(: P*U8IA>@*)FjgG FZ CG 4ǃ_2` ȏ,uD ~>/ &"['Q\-< [ NT .%OϯM :%%  &N,G*A9&: -:K] F4 ^2 ' H} 9"X!"76a䊢 CZ41!N SP!BT[rP-%hPA C+A&4IY-1#,چ^KW|,A7%5rD CCD  "% A,cKHa $2(ˆ8"!B&BA2qur!$پ! , MSK@[b% DAEQ%b--Q *"NA-SGX/0cI=*I5< M 5 E=VL5/3L*!QL0!(`HC0DMQ/Q! R a +] =@)h@BrA"%Fศ@F` Adŋɇ%E(82 AeCz' xeC;4I+bC }D@%?!XN\`""  :"H\B7K#* =΄FŇ^pG! , ADA< Q<EQ00FAE<@>#Q ] %%/DV :[C b@?Q2=X3+2?MЂ=5ȇO.G9'DB %L% D@aAQ60@.D# dGP B -B`[ #0UbហFpqbC%NU61p $b25xT \GLAAZ#qXP"Jp !Jy ldCD^^5dT:$dńx,A &lFDPF'o7* F%  8(wwУ ! , _SB/9@=F]D[%&PD/F2A3 J fEAW]B2H)SI [ ·Z+% >  \, P#B Xjl.L+v2$AQxqJBJփKR^BP?PdLHMfDmJ (8 (A*AADD] L#J wDDK>; =drsn$!np_D@*FQA&*mLC"7.( &8REAH 5NTaJ;8|n6Ku! ,  (4 %"N RPFIaDF.0 ]!Ȣ+Q6pY902 F4,+?6 à ^b+ ?J Үt1jLaBR #S躌D#( `2&&x@DR CD1KÉ*B+fcF^P Pb`(aK*:pBDR ! , >P(OA!(D- -\N-[$ D;MԴ]V>Յ;(@X>99"%+Q[G"XD=~q`\#\sa! J0Q %hAQFPF Ѕ4GSȀ1\*aA'p!8lpQBD2(!P-\`8E lŢ HcAz>Q! uwPxĒQ*;thawab-4.1/thawab-data/themes/default/static/img/print.gif000066400000000000000000000013361305262755200236120ustar00rootroot00000000000000GIF89a}&&&&&''''((()))111222999===>>>???@@@DDDGGGIIIOOOUUU^^^___aabccciijrrr}}}!~,~}}yqy~emtzytmU`hmpqpmh`$GPX^cc`^[P('&9@GLLPPLGD!,&~#+  "8+$BiR0RiBJ`1YIzЁ ,R0y?`'ƊM,6sb &͋2XztzXz ]TXQ  ^acC Q '8@"~C7`Az AHD<!(aԊ.Z|9H@$@HL=C@%Bq4Sg5)T$Ϟ@ zәQ959_nA5R- K~YeVl+q[[A*&Xכ Jb.~x"Ƒ%WF[`ʙ5AptmkIչDzٴk۾;ݼ{ <ċ?<̛;=I;thawab-4.1/thawab-data/themes/default/static/img/rpm.gif000066400000000000000000000021701305262755200232510ustar00rootroot00000000000000GIF89a!,%-&/'/(/(/)0)1)2*2+4+0*!1*!9/"70%91&:2&>5%92(G<*@8,E=/J?.H@2MA0MC5QD1QG6RG6YK7UK;YM:YO>^P8`S?bWDq`EtbErcKtfOkLyjR{mUmNw]wYyY}\}]^\ioacbdedgdefgnhkihmmeijjjkkkllmmnnoqqrtuvyrpqqsrssty{z{y{|rvtuuuvvwww{~x{xxyyzz{}||ŬgƭhƮlǯmvxxy|}}~~éŪɱqʲsɲx˴|©êªêŬŬ«¬ĬŭǮɮîƯĮȰŴŰưƱɲεζζϷҾӾȳʵ˵˶˶̷θϹҹҺӺջռл׾׾׿ؿѻҼҼӽҽҾԾտӾտ‘Ñ”˜ØžßğàġĠšŢ!, H*\H0^vٔڤS.e}C޷fja¤U/jl'5%bcQv zϛDRk- )d"(KZ]R\ GdAR†-'l\1K, š3wM0rB:$*:Fx3ez*VP[+qE(a}ج'7nlAJZt@ 24Qa5>`FP`b  :к,L8QP,Ti-VI'K JZRcLdI"@ԷNg T$$XG_f!_4AD С h"B;thawab-4.1/thawab-data/themes/default/static/img/scrolldn.png000066400000000000000000000016731305262755200243210ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsaa0UtEXtSoftwarewww.inkscape.org<8IDATxՕ;lU}><ڎE $@G"F  I)B@JEMeY&IJG( ޝםsKӝ)s_0${D7,-,gc!v.dj/߮>Q uD)F’nWl  %2TTXkkl_ǫ?S]usXJEGD"7'D4-g{fۇH.!ul)Zxy;wüy}K7ndAByR`c p2a931y;5vUf~}C(ѣ$K<[rLYv2Ga_.ƪe AeGe:n3ZxP %d$ Xkw꾌g&f܅ G8k|,$`:.iܛ Qe*ƁLm~~3V1 ! ,qfбk\-c97dLkWKH3AlZZ& k/xO2ݾSswQH[DoL!50ElO3˿@/>ktٮg+SSG I] m4YW$oh1=;!t +߯i "'tu _t#~iǵD%$R R'cj[?ߊYhɊ&ډ^oo됉DPv\]WO"KO4NGf1_h7=:w_zl?NIENDB`thawab-4.1/thawab-data/themes/default/static/img/scrollup.png000066400000000000000000000016131305262755200243360ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsaa0UtEXtSoftwarewww.inkscape.org<IDATxՕkeǿ}wfggN7%zHM,lP*ТZ/^)CxbuNJZۃÇ|xxZk"B 9'eFV߭˶|&>-ɏ4ܿ7 ڙކoS<'/'1gx (džǠ32ZyN|&1b;Zd DY .;"[~͏Jƛ;4q PP"?Rw*"3щ@Zhha=QZu%U93}FFG`he* \RDg.S<0:4zaD6N @[Ż6.Sub6Z~?{b4֠4}yWvUJKWwǶUA]=ٚwib>LA<e9>4~H\[KJ^U/VHǐ]Sm7Z[j+gЉ6}۾nwdn8#6: L~]]r JV@j 9m99dᛅYӚ5M7|7`IENDB`thawab-4.1/thawab-data/themes/default/static/img/search.gif000066400000000000000000000012611305262755200237200ustar00rootroot00000000000000GIF89aU  $$$---...///333444555:::FFFGGGSSSVVV___gggrrryyy}}}!Created with GIMP! ,!.NRK7*&IUTOE;6/ $USL?404-=USM>414>% )OTRJ=9ֆ+SRNG>628BNSPJF@:65;E'N'Ϡ5I%DYa0 W f"Pʠ"CA+MΒװV 1+؍# 8ܿ[#>|BJeYh!94$m {g?N~?Mf㠹vAM"e@XnhzkEs?vR04>M[lHr( @_:Wo| /@d{s>jHD2H'=t+Y;]r`wَĥO4ps;WϴʗALfWωO~Lm6>w˺m_/bt@ק/py[#-0:e܆M)joQq&B0Ylzĩ=B$ YAhSz{RvNt4oLl)70:rc tIENDB`thawab-4.1/thawab-data/themes/default/static/img/up.gif000066400000000000000000000001261305262755200230760ustar00rootroot00000000000000GIF89a ! , - ւIYWSyI,o%P]x~xۭ;thawab-4.1/thawab-data/themes/default/static/img/valid-css-blue.gif000066400000000000000000000033371305262755200252730ustar00rootroot00000000000000GIF89aX ! ####"'+$*.((('-1(.3+26.5:33318=;;;5=B8@FfC2׽*)6Qѥ j*Pb 5L+Dx{Z5snAs=V6(Q"@haެ!ˆ3oLPÕ'YE.&Hj*@*hvJRLwR :p[p-NmoF\n(i+ a.5a9Zg v$iPX+io\Hm@FH9`EwE~F[@E'Om@ TÒS\$o.QS~e * gDEŽEm AH*D ]tD1b\|T56؇Hz(摪S6Ô+ ԺqEu@ ípXyr @fJ'j`uU^Ho-FK BDEy Ұlnqg!xo0BPp_y>u^GN6St{1;H+Inl7kaV@AR\oES/S_}EMpk0@ @/6 ;thawab-4.1/thawab-data/themes/default/static/img/valid-xhtml10-blue.gif000066400000000000000000000040211305262755200257670ustar00rootroot00000000000000GIF89aX ! #### %)#)-(((&,0)/4*15-49333;;;6>D8AG;DKKKLAKROOPFPXJU]TUU[[[D]oLX`P\eY^bUbkYfp[ireeelll`nxcr}vwz{{{Z_[ ` behkm2m!o%r)u1y9t5|8~gwiym~y}8 *Tr$ .@T`5LM*,Yd?bUd8`6 >  G@L'= dbC7vJ OfvDogmKSlf5iMgzĀ7y 39L T% ;thawab-4.1/thawab-data/themes/default/static/main.css000066400000000000000000000175741305262755200226640ustar00rootroot00000000000000.clear {clear:both; padding-bottom:2px;} a img { border: none; } a[href] { text-decoration: none; color:#150;} a[href]:hover { text-decoration: underline; } a[target] { background: transparent url('img/external.gif') top left no-repeat; padding-left:12px;} body { font-family: "amiri", "Liberation Sans", "KacstOne", "Simplified Naskh", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "sans", "Sans"; background:#000; margin:0;padding:0;border:0; } h1 { margin-top:50px; padding-top:5px; padding-bottom:5px; padding-left:4px; color:#303030; border:1px solid #d4d4d4; background-color:#eee; background:#fff url('img/bg-l.png') top left repeat-x; background-repeat:repeat-x; background-position:0px -50px; clear:both; } h2 { margin-top:20px; padding-top:5px; padding-bottom:5px; padding-left:4px; color:#303030; border:1px solid #d4d4d4; background-color:#eee; background:#fff url('img/bg-l.png') top left repeat-x; background-repeat:repeat-x; background-position:0px -50px; clear:both; } #async_tips_div { display:none; position: absolute; background-color:#ffe; left:20%; margin:0; padding:3px 10px; border:2px solid #aa7; border:2px solid rgba(170, 170, 119, 0.6); } .match { background-color:#acf; } .term0 { background-color:#acf; } .term1 { background-color:#fca; } .term2 { background-color:#cfa; } .term3 { background-color:#fac; } .term4 { background-color:#afc; } .term5 { background-color:#caf; } .quran { font-family: "amiri-quran", "amiri", "Simplified Naskh", "me_quran", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "Serif"; font-size: 150%; color: #240; background-color:#ffe; } blockquote { border:3px dotted #aaa; background:#ddd url('img/quote.gif') top left no-repeat; background-color:rgba(190, 230, 190, 0.4); padding:5px 50px; } input { font-family: "amiri", "Simplified Naskh", "me_quran", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "Serif"; padding: 2px 30px; border-radius: 17px; font-size: 12pt; color: rgb(0, 0, 0); border: 2px solid rgba(245,245,245,0.2); background:#ddd url('img/search.gif') center right no-repeat; background-gradient-start: rgba(5,5,6,0.1); background-gradient-end: rgba(254,254,254,0.1); background-gradient-direction: vertical; selected-color: black; caret-color: rgb(128, 128, 128); caret-size: 1px; width: 30%; transition-duration: 300; box-shadow: inset 0px 2px 4px rgba(0,0,0,0.6); outline: none; } input:focus, input:hover { border: 2px solid rgb(136,138,133); background-gradient-start: rgb(200,200,200); background-gradient-end: white; background-gradient-direction: vertical; outline: none; } input:hover { transition-duration: 300; } #loading { display:none; background:transparent url('img/loading.gif') center center no-repeat; position:absolute; top:16px; left:8px; margin:20px auto; width:32px; height:32px; } #logo { background:transparent; position:absolute; top:8px; right:8px; height:48px; font-size:24px; line-height: 100%; text-decoration: none; vertical-align:middle; margin:0;padding:0;border:0; } #logo img {border:0; vertical-align: middle;} #absnav { display:none; background:#000 url('img/bg-d.png') top left repeat-x; color:#fff; position:absolute; top:46px; left:64px; width:200px; height:40px; margin:0; padding:0; border:2px solid #aaa; } #absnav2 { background:#000 url('img/bg-d.png') top left repeat-x; color:#fff; position:absolute; top:46px; right:64px; width:160px; height:40px; margin:0; padding:0; border:2px solid #aaa; } #absnav3 { background:#000 url('img/bg-d.png') top left repeat-x; color:#fff; position:absolute; top:128px; left:10px; width:32px; height:110px; margin:0; padding:0; border:2px solid #aaa; } #absnav2 img, #absnav3 img { padding:8px 5px; border:0; margin:8px auto 8px auto; } #results { background:#838383; color:#fff; overflow:hidden; position:absolute; top:4px; left:64px; width:500px; height:22px; margin:0;padding:0; border:2px solid #aaa; border-top:0; } #nominisearch { display:none; text-align:right; width:200px; margin:0;padding:0; } #minisearch { background:#000 url('img/bg-d.png') top left repeat-x; color:#fff; overflow:hidden; position:absolute; top:0px; left:92px; width:220px; height:32px; margin:0;padding:0; border:2px solid #777; } #minisearch form { margin:0;border:0;padding:0; } #minisearch input { #background:#ddd url('img/search.gif') center right no-repeat; width:70%; height:24px; border:2px solid #aaa; } #searchHelpDiv { display:none;} #searchHelp { background:#ffffa0; padding:16px; -webkit-border-radius:8px; -moz-border-radius:8px; border-radius:8px; } #searchHelpArrow { background:transparent url('img/up.gif') right top no-repeat; margin:0 24px; height:16px; width:32px; } #wrapper { background:#fff url('img/bg-l.png') top left repeat-x; margin:0;padding:64px 32px 32px;border:0; } #container { background:#fff url('img/bg-l.png') 0 -10px repeat-x; margin:0;padding:0;border:0; border:2px solid #eee; } #footer { background:#000 url('img/bg-d.png') top left repeat-x; color:#aaa; width:100%; margin:0;padding:32px 0 0;border:0; text-align:center; } #footer img {border:0;} #contentheader { margin:0;padding:0 16px;border:0; } #contentbody { margin:0;padding:0 16px;border:0; } /* nav */ .navtoolbar { overflow:hidden; width:100%;height:32px; background:#acf; background:rgba(240,240,255,0.4); z-index:10; } .navtoolbar span, #searchbar span, #tailnav span{ float:right; } .navtoolbar span.otherside, #searchbar span.otherside, #tailnav span.otherside{ float:left; } .navtoolbar a, #tailnav a { padding-right:24px;padding-left:16px; } .navtoolbar a.prevLink, #tailnav a.prevLink { background:url('img/go-prev.gif') center right no-repeat; } .navtoolbar a.upLink, #tailnav a.upLink{ background:url('img/go-up.gif') center right no-repeat; } .navtoolbar a.homeLink, #tailnav a.homeLink{ background:url('img/go-home.gif') center right no-repeat; } .navtoolbar a.nextLink, #tailnav a.nextLink{ background:url('img/go-next.gif') center left no-repeat; padding-left:24px;padding-right:16px; } #overlay { display: none; overflow:hidden; position: absolute; left:0; top:0; z-index:1000; background:#fff; } #kutubListing { border: 2px solid #aba; margin:20px; background-color:#eef7ee; } #kutubListing ul { display:inline; } #kutubListing ul li { display:inline; font-size:18px; display:block; float:right; margin:18px 16px; padding-top:64px; width:130px; height:50px; text-align:center; background:transparent url('img/kutub.gif') no-repeat center 8px; } table,th,td { border:none; border-collapse:collapse; padding:6px; } table { width:90%; background:#666; -webkit-border-radius:6px; -moz-border-radius:6px; border-radius:6px; /* -webkit-box-shadow:1px 1px 10px rgba(0,0,0,0.3); */ -moz-box-shadow:1px 1px 10px rgba(0,0,0,0.3) } thead, tfoot {color:white; } tbody td, tbody th{ background-color:#ddd; } tbody th { color:#222;} tbody td{border:1px solid #000;} tbody td:first-child{border-right:none;} tbody td:last-child{border-left:none;} tbody tr:nth-child(odd) td { background-color:#eee; color:#222} tfoot td, tfoot th {border:none; font-size:130%} #SearchPages span { display:inline; float:right; margin:0px; text-align:center; background: #def; color:#777; border:2px solid #acd; width:42px; cursor:pointer; } #SearchPages a{ padding:0 0.5em; } #SearchPages a.current{ background: #acf; color:#000; } .mini table, .mini th, .mini td { padding:0; } .mini tbody a{ display:block; padding:0 5px; white-space:nowrap; width:380px; overflow:hidden; } .mini #SearchPages span { font-size:80%; } .mini #SearchPages a{ padding:0 0.2em; } #SearchContainer.mini { padding:36px 0 0 0; overflow:hidden; } #rollup { background: transparent url('img/close.gif') 0 0 no-repeat; position:absolute; width:22px; height:22px; left:0; bottom:0; } thawab-4.1/thawab-data/themes/default/static/main.js000066400000000000000000000126741305262755200225040ustar00rootroot00000000000000/** * * copyright © 2010 ojuba.org, Muayyad Saleh Alsadi * **/ var klass="class"; var animations={}, ani_c=0, init_ls=[]; var autoscroll_dir=0, autoscroll_px=5; var overlay_d; function text_keyup(e, evt, func, bool) { var charCode = (evt.which) ? evt.which : event.keyCode if (charCode == 27) { // catch ESC key and clear input e.value = ""; if (bool == true) { func("", true); }else{ func(""); } return false; } } // fake trim for IE if (!Boolean(String.prototype.trim)) { String.prototype.trim = function() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); }; } function get_url_vars() { var vars = {}, i,j, e; var s= document.location.search; if (!s || s.length[0]==0 || s[0]!="?" ) return vars; var a = s.slice(1).split('&'); for(i = 0; i < a.length; ++i) { e = a[i].split("=",2); if (e && e.length==2) vars[decodeURI(e[0])] = decodeURI(e[1]); } return vars; } function animation_loop() { var i,a,fn,r; for (i in animations) { a=animations[i]; fn=a[0]; r=fn(a.slice(1)); if (r==false) delete animations[i]; } setTimeout(animation_loop, 100); } function slide_down_cb(args) { var o=args[0], h=args[1], px=args[2], cb=args[3]; var t=o.offsetHeight+px; if (t>=h) t=h; o.style.height=t+"px"; if (t==h) { --ani_c; if (cb) cb(); return false; } return true } function slide_down(o, h, px, cb) { var d; if (!h || h<0) h=o.offsetHeight; if (!px || px<0) px=h/5.0; if (px<1.0) px=1; d=o.style.display; o.style.display="none"; o.style.height=px+"px"; o.style.display=d; animations["_"+(++ani_c)]=[slide_down_cb, o, h, px, cb]; } function get_scroll_width() { var w = window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft; return w ? w : 0; } function get_scroll_height() { var h = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop; return h ? h : 0; } function autoscroll_up_cb() { if (autoscroll_dir!=-1) return true; var h=get_scroll_height(),t=h-autoscroll_px; if (t<=0) {t=0; autoscroll_dir=0;} window.scroll(0,t); return true } function autoscroll_down_cb() { if (autoscroll_dir!=1) return true; var h=get_scroll_height(),t=h+autoscroll_px, hm=document.body.scrollHeight; if (t>=hm) {t=hm; autoscroll_dir=0;} window.scroll(0,t); return true } function import_script(url){ var t = document.createElement("script"); t.type="text/javascript"; t.src = url; document.body.appendChild(t); } function overlay_init() { var d = document.createElement("div"); d.id="overlay"; d.style.width=document.documentElement.scrollWidth+"px"; d.style.height=document.documentElement.scrollHeight+"px"; document.body.appendChild(d); overlay_d=d; window.onresize = resize_cb; } function resize_cb() { overlay_d.style.width=document.documentElement.scrollWidth+"px";; overlay_d.style.height=document.documentElement.scrollHeight+"px"; } function getAjax(url, q, success, failure) { if (window.XMLHttpRequest){ // code for standard browsers Firefox, Chrome, Opera, Safari, and even IE7+ xmlhttp=new XMLHttpRequest(); } else { // code for IE6, IE5 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange=function() { if(xmlhttp.readyState==4) { if (xmlhttp.status==200) success(xmlhttp.responseText); else if (failure) failure(); } } s=""; for (var i in q) { s+="&"+i+"="+encodeURIComponent(q[i]); } s=url+"?"+s.slice(1); xmlhttp.open("GET",s,true); xmlhttp.send(null); } var needs_external_json=false; var fromJson = function(t) { return eval("("+t+")"); } function getJson(url, q, success, failure) { s=function(t){return success(fromJson(t));}; getAjax(url, q, s, failure); } function html_escape(s) { return s.replace("&","&").replace("<","<").replace(">",">"); } function re_escape(s) { return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1') } function search_entry_focus(e) { e.setAttribute(klass, "search_active"+( e.getAttribute(klass) || "" ).replace("search_active","").replace("search_inactive","")); if (e.value == "نص البحث") e.value = ""; } function search_entry_blur(e) { e.setAttribute(klass, "search_inactive"+( e.getAttribute(klass) || "" ).replace("search_active","").replace("search_inactive","")); if (e.value == "") e.value = "نص البحث"; } function rm_class(e,c) { e.setAttribute(klass, ( e.getAttribute(klass) || "" ).replace(c,"")); return false; } function init_get_by_class() { if (document.getElementsByClassName == undefined) { document.getElementsByClassName = function(className) { var hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)"); var allElements = document.getElementsByTagName("*"); var results = []; var element; for (var i = 0; (element = allElements[i]) != null; i++) { var elementClass = element.className; if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass)) results.push(element); } return results; } } } function init() { try { if (JSON) { var t=JSON.parse('"t"'); fromJson = function(t) { return JSON.parse(t); } } else needs_external_json=true; } catch(e) { needs_external_json=true; } init_get_by_class(); if (document.body.getAttribute(klass)!='body') { klass="className"; /* hack for ie */ } setTimeout(animation_loop, 100); animations["_s_up"]=[autoscroll_up_cb]; animations["_s_dn"]=[autoscroll_down_cb]; overlay_init(); var i; for (i in init_ls) init_ls[i](); } window.onload = init; thawab-4.1/thawab-data/themes/default/static/manual/000077500000000000000000000000001305262755200224655ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/default/static/manual/all.css000066400000000000000000000057571305262755200237650ustar00rootroot00000000000000.clear {clear:both; padding-bottom:2px;} pre, code { direction: ltr;} a img { border: none; } a[href] { text-decoration: none; color:#150;} a[href]:hover { text-decoration: underline; } a[target] { background: transparent url('../img/external.gif') top left no-repeat; padding: 0 0 0 12px;} body { font-family: "amiri", "Liberation Sans", "KacstOne", "Simplified Naskh", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "sans", "Sans"; background:#999; margin:0;padding:3em;border:0; } .export { background:#fff; margin:0;padding:3em; border: #aaa solid 4px; } h1, h2, h3, h4 { font-family: "amiri", "Liberation Sans", "KacstOne", "Simplified Naskh", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "sans", "Sans", sans-serif; color: #437f22; font-weight: bold; } div.toc { width:30%; float:left; background: #cda; padding:20px;margin:10px; border: #999 solid 2px; } div.footnotes { margin: 0.5em -2em 0; padding: 1em 4em 0; border-top: 2px dotted #CCC } pre { background: #cda; border: #cdc solid 2px; margin: 2em; padding: 0.5em; } table { margin: 2em; padding:0; border-collapse:collapse; } tr { padding:0; margin:0; border:0;} td,th { border: 2px solid #999; padding:2px 10px; margin:0; } th { background: #cda; } /* syntax highlighting code */ .code .br0 { color: #66cc66; } .code .co1 { color: #808080; font-style: italic; } .code .co2 { color: #808080; font-style: italic; } .code .co3 { color: #808080; } .code .coMULTI { color: #808080; font-style: italic; } .code .es0 { color: #000099; font-weight: bold; } .code .kw1 { color: #b1b100; } .code .kw2 { color: #000000; font-weight: bold; } .code .kw3 { color: #000066; } .code .kw4 { color: #993333; } .code .kw5 { color: #0000ff; } .code .me1 { color: #006600; } .code .me2 { color: #006600; } .code .nu0 { color: #cc66cc; } .code .re0 { color: #0000ff; } .code .re1 { color: #0000ff; } .code .re2 { color: #0000ff; } .code .re3 { color:#ff3333; font-weight:bold; } .code .re4 { color: #009999; } .code .st0 { color: #ff0000; } .code .sy0 { color: #66cc66; } /* notes */ .noteclassic, .noteimportant, .notewarning, .notetip { margin: 2em; margin-left: auto; margin-right: auto; width: 70% !important; min-height: 40px; clear: both; text-align: justify; vertical-align: middle; border-collapse: collapse; border: 2px solid #999; padding: 15px 60px 15px 20px; background-position: right 50%; background-repeat: no-repeat; -moz-border-radius: 20px; -khtml-border-radius: 20px; border-radius: 20px; } .noteclassic { /*border: 1px solid #99D;*/ background-color: #eef; background-image: url(images/note.png); } .noteimportant { /*border: 1px solid #ff0;*/ background-color: #ffc; background-image: url(images/important.png); } .notewarning { /*border: 1px solid #d99;*/ background-color: #fdd; background-image: url(images/warning.png); } .notetip { /*border: 1px solid #9d9;*/ background-color: #dfd; background-image: url(images/tip.png); } thawab-4.1/thawab-data/themes/default/static/manual/images/000077500000000000000000000000001305262755200237325ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/default/static/manual/images/important.png000066400000000000000000000043121305262755200264550ustar00rootroot00000000000000PNG  IHDR00WbKGDIDATx{W?. [X Ei*HK#&ElJFc#hLFG(j)j-+/AX](>fgv޿73;3+TIn~{{=;|(4ik=o |t֣H2pi1Vb'SNu,ߌi(8?//V0~N>..kK~dmd]HE )%"i,)n^?$TȾm[r8$P.B^Kc8np%ٞ@xf9aRGiN }#Ԩ0H[0}p9.j7n`CtLGTF!\AHLX5b I+qvNA|i!Of:" f%Xne"1M(\o;U*k)6ncaq9ԭ|݆b&Z_bUY_g_k]<:SQNGɲYQaFSS]# 6c֯Α3!BO )sP_W Dirh6@B Q95W/@kGm!UDR";g=cq=}AoΟLAN)E2LCcd/Vtr 4mM<dh{{7J"?V &:-Z}0عN~otz_ePsWVI|vy#įRNd&`u&|~JV)ow> 9tx,KGAZ,jQ-=ǥa꣗]5 Ze[A+>S]9_D?s9*t"MMۋhwk׵!R![15GjAV*|VX_@΀܉ UZ:ip/g8ޕUN ]ih=WO7d}Ϋ!OA:RzYL摮>Q%)@Pә/OlZk.{xhm-!ܿ($g0݋t8;W/kA&)g\_SY @ijN Q!WW*ĸVڥu5@Řι&X,t:Y|$S38ӓlƲ.ñ4%7.|9abnide. W{WN\ѧޢ7@8U6!!ʇװWњfbJcu Wz_;&H\ìk!"r,p͜VQE.%[+'-*F#1H:*s,η*sb7:X2zd6UG}tgqx\ZGJI*--KB \U0jRVmU&s X̞gJ]Eix%Ns .S㏝K*@L>ETq`3\'>O mAv8]Y胖; l@Z.EPVpQ!X[n0p8nf3d# HdªFfT`*gjn-&s*'QC6"1`HI[D:nO2۲W8Lx,L 2q>ycv2.m3u^5Tp)iPc6}XKWH83Q*3ŭ8W߰lcF2*W 6m2h)U8pN[Rc< ?-%5$o<;"VIENDB`thawab-4.1/thawab-data/themes/default/static/manual/images/note.png000066400000000000000000000047301305262755200254110ustar00rootroot00000000000000PNG  IHDR00WbKGD pHYs  ~tIME :9@>tEXtCommentCreated with The GIMP (c) 2003 Jakub 'jimmac' Steiner'3X IDATxYWsU3,' fqv㐘(X@x@""@gx`y@B!   %$, Kl !=]uU=3 v!#u6r^hhhUF rU>Fj(NO297͗Fk-/| Ghgmn\6Wvċ @w-yW>~"`G_m/@IYšv,ĿMD03cz@NqAQp^ѵk1 ȟt"=I)ޗ&yNbzzZ@D8}1t{99qz_ ==5~r z{jdY"R#KYN3u:7t/>|=AG6P \W3Sg>~>C{k,T1WhhH(`iڡyѕ(ݫr\M1tp ;9X'O%Z.1+71`8LE$ t)P(@ġ>チw=zf?+Sٸ:p6Lє"*_Ʋ8M JZ4oⱩY߿z_^l̄=8Ϋ723Wly[(4 XP @@xOp޸|S<̾?ayu3[fxvMH-hRn*:JJ!рHH3^in_ޣ1S\P(`|E3l1cu shBBjGؽ2^y-yn\K׀3]@NHp΁8+)|Q(Nz-chpyQj8J N$ j@k5Ay Vv'$i(Rss$TI E'F%/S[Vi<0;;"8'$> (A4"O4j.& %MJQ1?C(RABHNRb 18uhZƍ:Sl߶ :0i^u KQ ?ꀪ"Zf xwܱy?uFh疡~^~| 7_Wu<Nhpα !/:  8PZ^Y؃y6@?1l#Dլ:27枟?>~X\~&F֬ G|ʾsD),aVfG6X72PNYU]Ȳ^PB=7_* ){ `]/Y3P_awClYYmIQŠ6ĢQ#9Rίmfff6 3B)Kr-Rt@NUfDU͐ /X?Rc("FbNU|߫F ;יaf$Iҝ,tml޵033GJ:ifK,bCFQiR[ F+SA\6#J `vv+4jZs'0=:AxbL8c1Ӡ k|ňڑ =!"vQ=jm̌gƏo~ MS ~{#:c'NEnYk97_^wúl3zV4X?2̦sŖKlq?2>щ<ã㌎$i~x1PyL4Ml6gggOEQ@R=]FZ5!939UeÆ.2;T׷*ggӍ%;lň]X/?̗Q*ݲ{y9/弜t0 ?IENDB`thawab-4.1/thawab-data/themes/default/static/manual/images/tip.png000066400000000000000000000055351305262755200252440ustar00rootroot00000000000000PNG  IHDR00WbKGD IDATx͚yluμGR4)ы6,9*R-P*l,+i9AiQw Wiq Fn\^buE[ɒBUU17q7s9c#1e;lwhXaŠ ܆+\g-EX ( j:p5`fVX!Y mnݷ{w6omͺu;{mŤi >}C^W@@"'r`>~ "(*x8q_?s@qՒ2Сy__uNr/lWyUz"zy柿mRR*EJ(XkQD29``ͷnػNMMN w ؐ]ۻwߟ=~juQ[8΀fsUIhEv/j]~lo YJ*!sOlpSdž8QUS㜠\xQ+> ru߇ &! {ol۶ZxK~4rQ5(wr<Qjb^W=ΝQd(EU}edd]H=4jSBZ-Q[9^&50|^~."KI$E"f>go60f& I ϟ:y),)T{s-i;O/IB;uIf}o2c3;.ZRrQhx'UO*)w39~Ki\ {RiFhf ?^ eN@|駞{XeRMIq)mh;G4ɋlE,`EkG2F SXxӏ?4<2DI% #!E}T"Aw3f>0 C tN|hr!h#ǧ2QISng/`Ȫ`K7N ><4-GL/p_/g$S't{O ^p"Icyw3NUH6B*u6md]ЈwM ^-` hY"8w_|oZ&#gH}BwjD\2KJdR)”'#^s:"M`0XvE%^|޽ѯg7]`Q%(gM6ej<_k_3\@k_"q `HS%2-"ڈՍO9vy> _ ;r#uh\viō poB{&DYE}Xk R28CN%h"P kQ+bmȷSP~dL c $fvMl9&jtc_nyꗸ1f*$C\fc6h5W\m[zHq_*5Oyܰi ׾HC\B-?uxz_D+625z Q-|{V}VB$TkmZm*ӃLk j4fh* ı~ܹ]9z_jSu?VoE5Aõh FM'jKrʭL7mb'5  I;9O2:Qlʡ]/ PȵzWjҬѨMnHRG;qiAz$zM7_S`g] B2K8%*L a|L/i4?:rzSUQ٪#M*xTKP[W.~]~螮U+{pITk;VkmɊ<+@gM+`wq?l*^3::::ah-Q LB<7l_z8ccc9|8Yhf|<WadIit9\9䖿7Zyt9r7ˉ_mrYx/ڥ>1WIENDB`thawab-4.1/thawab-data/themes/default/static/manual/images/warning.png000066400000000000000000000062611305262755200261120ustar00rootroot00000000000000PNG  IHDR00WbKGD fIDATx՚{pUU$ Q4t0m(Z!)-i3ִ `V%GA@`(MmHDBHBnrs9??Vdٲedeem~Wꍄ7xwΝ MMMލx5߉Xi"dgge˖ɋ/V ˀPXf&ػw;v|cc#~;6t wwIZˉScRPP09''YTTt.xGcǎQ]]EY>aU0 !=3ܹsٺuO.]_|QD4eݿ,..޶m۶`+WPy ڵ Up)3ӧ1dƬ\n:e…=F@9r/BYQQ!['?e|{JXsQ^AN9r@;f4MScϗo?EKlgm߼ysܴGanOE1љ's}ئ0Qr+[_-c|<ΰYb3ev5%: > R[[KաChaɋNw׽ :I\ZI?J77FY7-=i2l,^y)-rPx[l|ee%}d8[x",Pl !LlKG nUub-=y2:ƍ|vmZTM6qرnRT1cD7 ;|_~~ N$E }W1>H B* i}|S~=yWzi T|fML;tPRRRF^5:,ˢ{iOJ F )BHawKaH!@8=)ᄿf=?<2AoWN''|wؖmY Hm[ٹg~8qzO@ O0*~\.W<*Ѻ]!c*^Im l!m+/^(Eahۚi5$x'g%@2|8'vՉ-T,D5A&{OԞז a@X&O*#n!ԁp8U]6u\(((JCIE іՙ@tLY;%#4 iCF1b*t FiiBSs3D R {vIo`!oȵfʏtPZ;KÅPUÃoq!vO4=a`SsӉLf,ZBkvD'%JlFPܨ@\.z:-Q{=DZ jO*& !_ #|?T+7|M `EAA)QvHKTkVhl|f9u5! i(/H `zN:BH) Iz(Sד P%FFۇMi[ \ 9gf8[ov_3v?RJ2e0t ] {!vQU5DwnzNkl+б4JGW'ЙgȜ934` TA( *t==p8T&h1zO|F"1 =[ zVSL+( sƌGg1}X6:z);TGJl+DH4l3w1wvԸc]]k;yN3f ř˹2\ o ]}JZ nwEPm0е zOcc-Ql};a\qYF;IK0RbFsEA%K._VV… cփ%u4Zϟ:ba~84 0jHmrk &ښv}ޡjѝ~c1g߾૪7oNZ uIfggjnMM %>Kmqqܚz¾jТFx@ƌqFJdggSRR*pT+B*pO@\3gl4Dee媬, @ 1i)-7|Y /]3gάOt:AիN'--JJh,g?cAXי6 DLx'e(2]׻.ߥAY\\,'M$`BO[ʰ:?䓉RS\FtK8јg/R^^I.uu}mu]}ÇOiiiƙD*uEb].yɜ9sn xEQ(,,ߦ4iȿt~k4{2ǃ<}Z} $1 H< دليل استخدام ثواب

    دليل استخدام مكتبة ثواب

    البحث

    لمحة

    تستخدم مكتبة ثواب فهرسًا لتسريع عملية البحث. الكتب غير المضافة للفهرس لا تظهر في نتائج البحث.

    عندما تفتح كتابًا ما فإن صندوق البحث الصغير في صفحة الكتاب يبحث داخل ذاك الكتاب فقط، أما الصفحة الأولى فتبحث في كل الكتب ما لم تحدّد لها مجال البحث.

    في الغالب لا تحتاج تحديد مجال البحث حيث يتم البحث في كل الكتب خلال ثانية أو أقل ولا يزيد الوقت اللازم للبحث كلما زادت حجم المكتبة كماً، والنتائج تكون مرتبة بحسب ارتباط النتيجة بالشيء الذي تبحث عنه (مثلا يعطى ورود الكلمة في العنوان وزنا أعلى من ورودها في المتن)

    يتأخر البحث عند البحث عن أمور عامة تُخرِج الكثير من النتائج (في الزمن اللازم لتقييمها وترتيبها). حاليا المكتبة تظهر أوّل 500 نتيجة فقط (يمكن إعادة ضبط هذا الرقم إن احتجنا لذلك).

    لتسريع البحث ابحث عن شيء مميز. مثلا: لا تبحث عن كلمات مثل حرف “في” بل ابحث عن كلمة مميزة.

    البحث الموجود على الصفحة الرئيسة يقوم بالبحث في كل الكتب المفهرسة ما لم تقم بحصر مجال البحث بشكل صريح.

    لا يتم البحث في الكتب غير المفهرسة.

    عندما تفتح كتابًا فإنك تجد صندوق بحث مصغر في رأس الصفحة وبعكس صندوق البحث الرئيس لا يبحث هذا الصندوق إلا في الكتاب الحالي. الكلمات التي تكتبها فيه يتم تظليلها في الصفحة الحالية فور كتابتها ويتم النزول إليها لإظهارها عند النقر على زر الإدخال أي أن الإدخال له فائدتان: البحث في الصفحة الحالية والبحث في كل الكتاب الحالي.

    الملخص

    الرمز اسم العملية شرح مثال معنى المثال
    & و تشترط تحقق ما قبلها وما بعدها معا كتاب & قلم أن يحتوي النص كلمتي “كتاب” و “قلم”
    &~ وربما تشترط تحقق ما قبلها ويفضل أن يتحقق ما بعدها دون اشتراط ذلك كتاب &~ قلم أن يحتوي النص كلمة “كتاب” ويفضل أن يحتوي كلمة “قلم”
    | أو تطابق سواء تحقق ما قبلها أو تحقق ما بعدها عيسى | المسيح أن يحتوي النص كلمة “عيسى” أو كلمة “المسيح”
    ! النفي تطابق إذا لم يتحقق ما بعدها عيسى | المسيح ! الدجال أن يحتوي النص كلمة “عيسى” أو كلمة “المسيح” لكن بشرط أن لا يحتوي كلمة “الدجال”
    * أي شيء صفر أو أكثر من الحروف استع* يمكن أن تطابق ما يبدأ ب استع مثل استعان أو استعمل …إلخ
    ? حرف واحد حرف واحد تماما ش؟ب يمكن أن تطابق شرب أو شنب …إلخ
    ^ قوة زيادة وزن الكلمة عند ترتيب النتائج أحمد ابن تيمية^3 إعطاء كلمة تيمية 3 أضعاف وزن الأخرى عند الترتيب
    كتاب: تحديد المجال البحث في الكتاب المذكور تاليا التيمم كتاب:(“صحيح البخاري” | “صحيح مسلم”) البحث عن التيمم في صحيح البخاري أو صحيح مسلم
    عنوان: البحث في العناوين حصر البحث في عناوين الأبواب عنوان:(بدء الوحي) البحث عن كلمة بدء وكلمة وحي في عناوين الأبواب

    يمكنك طباعة رمز ~ الذي يأتي بعد علامة & في استعلام “وربما” عبر الضغط على زر SHIFT+Z في لوحة المفاتيح العربية QWERTY

    الاستعلامات المتقدمة

    عند كتابة أكثر من كلمة فإنه يتم ربطها معا ضمنيا عبر علاقة “و” (ورمزها “&”) أي أنه سيعطيك الوثائق التي تحتوي كل تلك الكلمات معا (دون اشتراط تتابع معين) مثلا عند كتابة كلمة خمر ثم مسافة ثم تحريم فإنه لن يعطيك الوثائق التي تحتوي واحدة من الكلمتين بل يجب أن ترد الكلمتين معا (أي كأنك كتبت خمر & تحريم) لكنه قد يعطيك وثائق تحتوي على عبارة “تحريم الخمر” أي بترتيب معكوس.

    يمكنك استعمال العلاقة “أو” (ورمزها “|”) حتى تشترط ورود واحدة من تلك الكلمات مثلا لو كتبت كلمة عيسى ثم | ثم المسيح فإنه لن يشترط ورود الكلمتين معا فواحدة منهما تكفي.

    يمكن نفي العمليات عبر علامة التعجب “!” وذلك لاستثناء شيء من النتائج مثلا المسيح ! الدجال تحضر الصفحات التي تحتوي كلمة المسيح لكنها في نفس الوقت لا تحتوي كلمة الدجال.

    يمكن استعمال الأقواس كما في العمليات الحسابية مثلا (المسيح | عيسى) ! الدجال.

    لاشتراط الترتيب عليك وضع علامة اقتباس مزدوجة مثلا “تحريم الخمر” وهذا يسمى البحث عن عبارة.

    يمكنك اشتراط ورود الكلمة في العنوان عبر كتابة عنوان ثم علامة : مثلا عنوان:“بدء الوحي” للبحث عن عبارة “بدء الوحي” في العنوان أما البحث عن كلمات في العنوان دون اشتراط أن تكون عبارة يمكنك كتابة عنوان:(بدء الوحي)

    البحث عن عنوان:بدء الوحي دون اقتباس أو أقواس تعني البحث عن عنوان به كلمة بدء أما كلمة وحي فلا يشترط أن تكون في العنوان

    يمكنك تحديد مجال البحث عبر ذكر اسم الكتاب بعد كلمة كتاب ثم : مثلا

    • بدء الوحي كتاب:صحيح_البخاري
    • بدء الوحي كتاب:“صحيح البخاري”

    البحث عن بدء الوحي كتاب:صحيح_البخاري دون اقتباس ولا علامة تحتية _ تعني أن اسم الكتاب هو صحيح أما البخاري فهي كلمة تريد البحث عنها

    يمكنك البحث في أكثر من كتاب عبر استعمال أو مثلا بدء الوحي (كتاب:صحيح_البخاري | كتاب:صحيح_مسلم) كذلك يمكنك استثناء كتاب عبر ! مثلا بدء الوحي ! كتاب:صحيح_البخاري تبحث عن بدء الوحي في أي كتاب إلا صحيح البخاري

    الحصول على الكتب

    قد تأتي بعض الكتب مع النظام (مثلا في نظام أعجوبة لينكس هناك عينة من الكتب المميزة تأتي مع التوزيعة) لكن الكتب ليست جزء من مكتبة ثواب بل يقوم باختيارها المستخدم حيث يمكنه الحصول عليها من أكثر من مصدر.

    يمكن وضع الكتب في مجلد db داخل المجلدات الخاصة بمكتبة ثواب.

    المجلدات الخاصة بثواب هي

    1. مجلد .thawab داخل المجلد البيت الخاص بالمستخدم.
    2. مجلد thawab-data المجاور للملف التنفيذي الذي يشغل ثواب
    3. مجلد thawab-data في كل من أقراص ويندوز من C إلى Z

    الاستيراد من الشاملة

    يمكن استيراد كتب الشاملة ذات الهيئة .bok والتي يمكن الحصول عليها بتصديرها من برنامج الشاملة أو بتنزيلها من الإنترنت 1). ويكون ذلك من خلال زر الاستيراد بالأعلى أو من خلال سحب ملفات bok وإفلاتها في برنامج ثواب، كما يمكنك إضافة الكتب عبر زر الإضافة Add وبعد الانتهاء من اختيار الكتب، اضغط الزر التحويل Convert.

    من موقع أعجوبة

    يمكن جلب الكتب من هذا الرابط:

    إنشاء فهرس البحث

    بعد إضافة المزيد من الكتب إلى ثواب لا تكون مفهرسة مما يعني أنه لا يمكن البحث فيها. لإضافتها إلى فهرس البحث في تطبيق ثواب انقر على زر فهرس البحث Index ثم تضغط على زر دفع الكتب الجديدة queue new books كي يتم التعرف على الكتب غير الموجودة في الفهرس وإضافتها إليه.

    التخلص من فهرس لا يعمل

    إن توقف الفهرس عن العمل لأي سبب (مثل تلف الملف بسبب انقطاع التيار الكهربائي أو ترقية إصدار محرك البحث whoosh إلى إصدار أحدث غير متوافق مع الذي قبله) عليك حذف مجلد index ثم إعادة فهرسة الملفات.

    1) يمكن تنزيلها من موقع الشاملة الجديد أو القديم أو من موقع islamport
    thawab-4.1/thawab-data/themes/default/static/print.css000066400000000000000000000001071305262755200230540ustar00rootroot00000000000000.noprint { display: none; } #wrapper, #container { background:#fff; } thawab-4.1/thawab-data/themes/default/static/th-main.js000066400000000000000000000077511305262755200231150ustar00rootroot00000000000000/** * * copyright © 2010 ojuba.org, Muayyad Saleh Alsadi * **/ var resultsPerPage=50; var async_tips_div, mouse_x, mouse_y; function main_search_row_factory(u, bu, r) { return ""+html_escape(r.k).replace(new RegExp('_', 'g'), ' ')+""+html_escape(r.a)+""+(r.y || "-")+""+ html_escape(r.t)+""+html_escape(r.r)+"\n"; } var search_row_factory=main_search_row_factory; var search_done=function() {}; function showSearchPage(hash, pg){ var j,i=(pg-1)*resultsPerPage,o,h,l; var u=script+'/ajax/searchExcerpt/'+hash+'/',bu=script+'/view/'; l=document.getElementById("loading"); l.style.display="block"; window.scroll(100,0); getJson("/json/searchResults", {h:hash,i:i,c:resultsPerPage}, function (d) { var c=d.c,a=d.a; o=document.getElementById("SearchResults"); h="" o.innerHTML=h; for (j=0;j 10) { pages = 10; } document.getElementById("SearchPagesCount").innerHTML=pages; o=document.getElementById("SearchPages"); h=''; o.innerHTML=h; if (d.c>0) { for (i=1;i<=pages;++i) h+=''+(i)+''; o.innerHTML=h; showSearchPage(d.h,1); if (main) {se_t.style.display="block";} } l.style.display="none"; /* should be faded */ search_done(); }, function () { l.style.display="none"; /* should show error */ search_done(); } ); return false; } function kutubFilter(q) { var o=document.getElementById("kutubListing"); var old=o.innerHTML; var l=document.getElementById("loading"); l.style.display="block"; getAjax(script+"/ajax/kutub", {q:q}, function (d) { o.innerHTML=d; l.style.display="none"; }, function () { o.innerHTML=old; l.style.display="none"; } ); return false; } function moveMouse(E) { var e=window.event || E; mouse_x=window.pageXOffset+e.clientX; mouse_y=window.pageYOffset+e.clientY; } function asynctip(e) { var l=document.getElementById("loading"); l.style.display="block"; async_tips_div.style.top=(mouse_y+e.offsetHeight+5)+"px"; async_tips_div.innerHTML="..."; async_tips_div.style.display="block"; u=e.getAttribute('rel'); getAjax(u, { }, function (d) { async_tips_div.innerHTML=d; l.style.display="none"; /* should be faded */ }, function () { async_tips_div.style.display="none"; l.style.display="none"; /* should show error */ } ); } function asynctip_hide(e) { async_tips_div.style.display="none"; } function async_tips_init() { var d=document.createElement("div"); d.id="async_tips_div"; d.style.width="60%"; d.style.display="none"; document.body.appendChild(d); async_tips_div=d; if (document.addEventListener) { document.addEventListener('mousemove',moveMouse,false); } else { document.attachEvent('onmousemove',moveMouse); } } init_ls.push(async_tips_init); thawab-4.1/thawab-data/themes/default/static/th-view.js000066400000000000000000000075021305262755200231350ustar00rootroot00000000000000/** * * copyright © 2010 ojuba.org, Muayyad Saleh Alsadi * **/ var last_highlighted=""; var th_hash; function mini_search_row_factory(u, bu, r) { return ""+ html_escape(r.t)+""+html_escape(r.r)+"\n"; } function mini_search_row_factory_st(u, bu, r) { return ""+ html_escape(r.t)+""+html_escape(r.r)+"\n"; } resultsPerPage=10; // defined in main.js search_row_factory=(is_static)?mini_search_row_factory_st:mini_search_row_factory; function doMiniSearch(q) { doSearch(q+" كتاب:"+kitabId, false); } function view_cb(h) { var l,n; window.scroll(0,0); l=document.getElementById("loading"); l.style.display="block"; if (! h) h="_i0"; th_hash=h; getJson(script+"/json/view/"+kitabUid+"/"+h, {}, function (d) { document.getElementById("maincontent").innerHTML=d.content; document.getElementById("subtoc").innerHTML=d.childrenLinks; document.getElementById("breadcrumbs").innerHTML=d.breadcrumbs; n=document.getElementById("prevLink"); n.setAttribute('title', d.prevTitle); n.setAttribute('href', d.prevUrl); n=document.getElementById("upLink"); n.setAttribute('title', d.upTitle); n.setAttribute('href', d.upUrl); n=document.getElementById("nextLink"); n.setAttribute('title', d.nextTitle); n.setAttribute('href', d.nextUrl); l.style.display="none"; /* should be faded */ highlight_words(document.getElementById("maincontent"), highlighted, true); }, function () { l.style.display="none"; /* should show error */ } ); return false; } function ajax_check_hash() { var h=window.location.hash; if (h==("#"+th_hash)) return true; view_cb(h.slice(1)); return true; } var harakat="ًٌٍَُِّْـ"; function highlight_word(o, w, i) { w=w.trim(); if (w=="") return; w=re_escape(w).replace(/(\\?.)/g, "$1[\-_"+harakat+"]*"); w="("+w+")"; var re = new RegExp( w, "gi"); a=o.innerHTML.split(/(<\/?[^>]*>)/); for (j in a) { s=a[j]; if (s && s[0]!="<") { a[j]=s.replace(re, "$1"); } } o.innerHTML=a.join(""); } function highlight_words(o, w, scroll) { var i,a=w.split(" "); highlight_words_off(o); for (i in a) { highlight_word(o,a[i],i); } if (scroll) scroll_to_first_highlighted(); } function scroll_to_first_highlighted() { a=document.getElementsByClassName("highlight"); for (j=0;j([^<>]*)<\/span>/gi, "$1"); } var highlighting=false; function highlight_cb() { if (highlighting) return true; var q=document.getElementById('q').value; if (q=="نص البحث") return true; highlighting=true; highlighted=q; if (last_highlighted!=highlighted) { last_highlighted=highlighted; highlight_words(document.getElementById("maincontent"), highlighted, false); } highlighting=false; return true; } function th_view_init() { var l; if (!is_static) { l=document.location.toString(); loc=window.location.hash.slice(1); if (loc=="") document.location=l+"#_i0"; else view_cb(loc); } /* hide mini-search if not indexed */ if (!is_indexed) { document.getElementById("minisearch").style.display="none"; document.getElementById("nominisearch").style.display="block"; } highlighted=get_url_vars()["highlight"] || ""; highlight_words(document.getElementById("maincontent"), highlighted, true); last_highlighted=highlighted; } search_done=scroll_to_first_highlighted; animations["_highlight"]=[highlight_cb]; if (!is_static) animations["_ajax_check_hash"]=[ajax_check_hash]; init_ls.push(th_view_init); thawab-4.1/thawab-data/themes/default/templates/000077500000000000000000000000001305262755200217175ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/default/templates/footer.html000066400000000000000000000017471305262755200241140ustar00rootroot00000000000000 thawab-4.1/thawab-data/themes/default/templates/layout.html000066400000000000000000000040721305262755200241250ustar00rootroot00000000000000 {{_r.title or '::'}} :: ثواب :: {{!_r.render_css_links()}} %if x_js_script: x_js_script() {{!_r.render_js_links('head')}} {{!_r.render_js_links('begin')}}
    %topnav()
    /\
    \/
    %if typ!="main": %include minisearch %end
    %include
    %include footer {{!_r.render_js_links('end')}} thawab-4.1/thawab-data/themes/default/templates/main.html000066400000000000000000000100361305262755200235310ustar00rootroot00000000000000%_r.add_js_link('th-main.js') %def x_js_script_f(): %end %def topnav_f(): about get package contact us forum code %end

    مكتبة ثواب - بحث ذكي وسريع في أمهات الكتب

    البحث | الكتب المتوفرة

    البحث

    في صندوق البحث أعلاه اكتب الكلمات التي تريد البحث عنها. يمكنك وضع الكلمات بين علامتي اقتباس " " للبحث عن عبارة (كلمات متتالية). عملية البحث ذكية تستوي فيها الحركات وغالبا ما تستوي التغييرات التي تطرأ على الكلمة كالإفراد والتثنية والجمع والإسناد للضمائر وغير ذلك. يمكنك استخدام استعلامات متقدمة عبر الأقواس والرموز "&" ، "|" ، "!" من أجل عمليات "و" ، "أو"، "النفي" على الترتيب. كما يمكنك تحديد مجال البحث عبر ذكر اسم الكتاب بعد "كتاب:". يمكنك البحث في عناوين الأبواب أيضا وذلك بكتابة "عنوان:". للمزيد انظر دليل الاستخدام

    الكتب المتوفرة

      {{!kutublinks}}


    %rebase layout typ="main", topnav=topnav_f, x_js_script=x_js_script_f thawab-4.1/thawab-data/themes/default/templates/minisearch.html000066400000000000000000000025331305262755200247320ustar00rootroot00000000000000
    فضلا أضف هذا الكتاب للفهرس.
    -
    -
    thawab-4.1/thawab-data/themes/default/templates/view.html000066400000000000000000000026641305262755200235670ustar00rootroot00000000000000%_r.title=title %_r.add_js_link('th-main.js') %_r.add_js_link('th-view.js') %def x_js_script_f(): %end %def topnav_f(): prev up home next print %end

    مكتبة ثواب - {{title}}

    {{!content}}
    {{!childrenLinks}}
    %rebase layout typ="view", topnav=topnav_f, x_js_script=x_js_script_f thawab-4.1/thawab-data/themes/neo/000077500000000000000000000000001305262755200170565ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/neo/static/000077500000000000000000000000001305262755200203455ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/neo/static/fx.css000066400000000000000000000025161305262755200215000ustar00rootroot00000000000000/* fx */ #async_tips_div { background-color:rgba(255,255,200,0.9); } #overlay { opacity:0.7; } .showOnFocus { opacity:0.4; } .showOnFocus:active, .showOnFocus:focus { opacity:0.75; } .showOnFocus:hover { opacity:1; } .blurOnFocus { opacity:1.0; } .blurOnFocus:active, .showOnFocus:focus { opacity:0.75; } .blurOnFocus:hover { opacity:0.4; } #minisearch input { background:rgba(255,255,255,0.6); } #absnav { -webkit-border-radius:20px; -moz-border-radius:20px; border-radius:20px; padding:0 20px; -webkit-box-shadow:0 0 8px rgba(240,240,240,0.4); -moz-box-shadow:0 0 8px 8px rgba(240,240,240,0.4); } #absnav2, #absnav3{ -webkit-border-radius:20px; -moz-border-radius:20px; border-radius:20px; padding:0 20px; -webkit-box-shadow:0 0 8px rgba(240,240,240,0.4); -moz-box-shadow:0 0 8px 8px rgba(240,240,240,0.4); } #absnav3{ padding:0 5px; } #minisearch { -webkit-border-bottom-left-radius:8px; -moz-border-radius-bottomleft:8px; border-bottom-left-radius:8px; -webkit-border-bottom-right-radius:32px; -moz-border-radius-bottomright:32px; border-bottom-right-radius:32px; padding:0 8px 2px 0; } #container{ -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; /* -webkit-box-shadow:0 0 8px rgba(128,128,128,0.4); */ -moz-box-shadow:0 0 8px 4px rgba(128,128,128,0.4); } #absnav3 { position: fixed; } thawab-4.1/thawab-data/themes/neo/static/ie-fx.css000066400000000000000000000004131305262755200220650ustar00rootroot00000000000000/* fx-ie */ #overlay { filter:alpha(opacity=70); } .showOnFocus { filter:alpha(opacity=50); } .showOnFocus:hover { filter:alpha(opacity=100); } .blurOnFocus{ filter:alpha(opacity=100); } .showOnFocus:hover { filter:alpha(opacity=40); } #absnav3{ display:none; } thawab-4.1/thawab-data/themes/neo/static/img/000077500000000000000000000000001305262755200211215ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/neo/static/img/about.gif000066400000000000000000000021571305262755200227270ustar00rootroot00000000000000GIF89a:;<<>AAJADGLXY\Yk fcfpl X\]^^ahlrtvx|p i}} ̎ۇ܈܉Вݏޓ  ΄)ې)&$8\R& 3bMLy!Ťq͙/Wx@G-u - P@P0@0 4@;thawab-4.1/thawab-data/themes/neo/static/img/book.png000066400000000000000000000026361305262755200225700ustar00rootroot00000000000000PNG  IHDR00WbKGD pHYs B(xtIME+IDAThˋU{z8fF#AD! [A0Bu"]W\[A@@⍕e8~aulVvWkΩw.e=zM3yN-֘{+g>N :4VD(JL9SxLK6qo{w7E6uRC>Ў#_V>+˱tD6H|Zn]J"}_QA՗'woj~pݿ<)z(;;L&A?OUV:EB1W1:~{_7VB1a[PU/A]t%IENDB`thawab-4.1/thawab-data/themes/neo/static/img/close.gif000066400000000000000000000023061305262755200227160ustar00rootroot00000000000000GIF89a     ''%%6+++3/00723 8811955:>9F @@;<B:6I= ?@IHC>=GP@L CKHQ%T;VRNPQSQWGWSSO:XUOOW7TTZ^Z _]]ZZa7`4j ^^]]di4fjeOePkml7rtsoQsnly8rr}7}grrqqrmxYzWgzl݃gyrςGրo~s߂s݃qw̒ߌጌ⌌⍌⎌Ꮛ␋㐌ܖՙޚ夤⥥禦㨟覦ݭ賳ӻ赵ijźʿѷ!Created with GIMP! , HH!@ gٶQAVkզ0 c1kldȊ UeANnl``Ie+Ps ۽rr2͞Jl @$Jɓz-#T~DHZocd&ˎ dNMvUˮ1WX P(x*eZ{8&J xx"G(CMF"7VPH HA{zZn$@d` k>x>SM%#( Qsc*x``'4qF8D+B/qa BKlX%4 0dAU`ă#p@BCЁ31Dl1` 8q \T;thawab-4.1/thawab-data/themes/neo/static/img/code.gif000066400000000000000000000022721305262755200225250ustar00rootroot00000000000000GIF89a'()* 5: X Y!Z#\$]%^$_&_&_'_&`'a'b'c(b)b(c)e*d*e+e)e*e+e*f*g+g*g-g,h-i,i-i-j.j/k/l/l/m0l1l2n0m2m0n1n2n3n3o 3p 3q 4p5q5q 6q 4r 7s 5r 5s 6t 7t 8t 8u 8t 8u 8u 8v 9v 8v 8w :w ;w =y :y ;y ;z ;z =z | ?~@} A~ A~@DDDDFGEDEFEGEFIJLNJKMONNPTVWYXY[\\]^dc b!d d"d$i&l)o*p*p+p+s.s/x2{4|6~69;?ADKOORRUUT[_bbdffx|؃كߐ!,q HYȰaC=b%ŋFQPZ ɓ(Kvb$P%K0-QӒ$J51yE$H@ɺ(Fd$)0b-NRM֡Di8!C}n 2dmAMD;zȹW_wnNlظrKǐJ|X\\\ZXXXXXXXXXXXXXXXXXXXWJ$a )"+&-(/)N5(}K.N/O1P3S+M.N/P/O-N*L(K&I#~G!~F }E}D&}H#}F!yDf: -$(-%+$ +$ *%+%`;9EœFFE4f*_*_*_*^)^(^(]']']&\4rDCCC›/X J1#*")"(" (" ("_:;FBàDƨ?̻CBBBBBBBBBBBŧ?̻DƩEB5j0Z 1( ,$+$ +$ )# )#_:<eB=CɰEǫJC@#ZTTTTTTTT+gBHEDȯDɰ6m3Z /#PARGI@ K@B<63c@7}CʲAͼGģ5i$[UX ]$e$e$f#cYVV$\:A̼B˸Fġ1b3Y /!L>NCE=H=@951c>&]AG?*` ]%g.w,u+r-u+r,t,t'k$dW2lAI>}R4X /4gU*l_ ]T/aS+TL@>gC%]B;u#[#a*p.x+q"[(\+o(h%W*e,s+q(k Y%]?>}S4X .' -%+$ +$ *$ )#b<&^C:u%c-t*n,q'g6dIs_(MEizqDn+e.u*i$Y@?U5V-")")"(" (" ("b=(`D>{,l1e5]8e*U2}TPrc^rnv|b})UBh4VAA W 6U,$*")"(" (" ("c>)bE˜EW|wu]t7f3aFmsoexNzdDB#Y 6U,& *")"(" (" ("c>+dFÙQ~RjPnm[vRyjA}`uHj[E}dGC']9T+1^N"]PPH&TG$JB:8hE-eHÜ\j2iCdCxe[~s9_2kEsqqdzm4dLǣD)_:!S+7fW(eXXO-]P,SKA?kH.gIÝX~\p9bHt_@eUfz>d4:–Av{BhXnLxdr?nMǤE,a;"R*#H=C9>7B9?9;6&nL7nKPpQsd@cPFhZEg[h{Fi9?TxXykY}oYp|7nJŜF/c>%R)Izh6xj(g^9m_:bZ-ON/vWAvYȦUwCt[\~pTo3^5cQsL~:VuqNqcKpaSufTs8yMȣH2g>%Q(@68/6/919384+qPDx^ͫ^Hpaj}NxCQpCg:xc`vHrdoyBc5uPʨI6j?)P(50 0(0)2,4.61-sQG{^ͩb̠OȌOap]Y˓Zbzc|pY~[~XʒlҴk͟[/L?~akӭmmt׹mx׺nӚiӗlԙn՚p՜r֝t֟vנxآڰֶNbUK:UO=6=6"?9&@;)C=Abkճt׸qrn{zظ{ٹ}ٺڻڼۼ۽ܾܿٽUk\EHc\7UO#B<(D>,E@/HBGgo׶uvvy|خzح{ٮ}ٯڰڱ۲۴ܵܶݷZq`-?aU<_zq`xrdytgzuizwxޕܷޘ޿ޙ޿ޛ߿ޜ߾ޖ޶ޕ޶ޗ߶ޘ߷ޙ߸ޜޝޞޟޡޢߨ߭߱߰ްũ̸e8UFWff#\jj$\jj$cqj$gu%u%u%u%|%|%|%|%%%%%%%%&&&&&&thawab-4.1/thawab-data/themes/neo/static/img/forum.gif000066400000000000000000000021721305262755200227420ustar00rootroot00000000000000GIF89a9z&7v(={-<1/U5^3c=m aCaGiLlTqDjJkCgHkUsYqp{ImMqOtSqRv]V{b}zt|bgzfjry{tzɃӃģÅ͌Α֚џߤ!,# H#]Eb! <"5 !F y,RQr΁f%1CJ :u@#Jr1R@! 3p rIl1AqCgPF$&yԨ6d Dq"7K:'X[ù=a v~ Btf@ "k Qpftfrͫ ǡ:cp~"<X%#^Zn:ʾctN^>X܏?p]}>ɽkJuW =^:2PJ!NZ hxB7raLQ°]ӥg&z96RR겳n ߾KIZXHZtW㏍@=k8cIJyP*bϼɅ|S;hD<"}>żQ۟MN14u,^&x!#+/X?B {9B/WE]…x^ƾq5APڃ׷/L}uo\㫏V:cH5|<.9Mre'|9^%z[ 6> l#m{QmWL=b]11q\E~|_~dz'iW(nD["#K5F,SuѰktOv'sחVrYW̩#gDmZxxZPV!Gk@ߟOj)vL2E:os\ɨWwo%16>HQ Z 4{l-x5:c6bU`!1= 03۰86!Fj t_zGd?H$F-JpěuP7=\][u9_=' ꞑAsS KxJS I-I#7G!h_nx/ /δ?ٚ=#|z1Dž+mZϻa "62l7zV0Z3=]ebfmAbbC$k"CC1V^'OU !a3[\l9<~v50 Q(Aő`pt`eY92}P3{F8rFeo-n՗9}h _?NroYX*ɀsp.'Dk vR3mLb4:]O݆?p΍ |+::,35-fq($vmd`n>=Go=_i\bqu4䇖B}oCwž`+Q\GΚ̊? :cyˇQd8vk XL$lu|^u8j"bxqLu.Y]^nQϧ+W%e2{m)Y<;3+2|LY4hI !HK`$ Jbj4j-^>OP#vG!62b#CHW1DuHDMWCu;ѷbp\v!d"0HxL3Vw|7ZZ@3I}Rwcwq_is,O99_{66t8 J]s/9?~Ŝ:o~+z,q_9,p $&&hafDi0 xsXOctt~mVӧYSlyE0 э9Sqbxs \K:~32O9(Otџ;B\.GL'UNϙ]尃.堃$Izd=|/DOh]t y;3g?88\u??\/ZWO>STL ~IGP hcrOWsX7O? cxڢG{-z,AHR>!q=#{M!>P2)Dau峸{G?dmpƱ#/W`Gg-[ OI0βbl>BOu >ZkBB|Q#řc8Noy^`qD(!b!gζuw,^ƷϼpcT8[O5V2*̊f*}C֎7|;(r\sʠ</|O t,E3> h\wP9]\"~~>ј$1T9s|Ύ:AJ0ÏAFNbX!WP>;voI2"eOM/rّ۸~[Ɨrr,SГԅ#|* N* .D╈l®޾u1FbS.x A]gbm}HӿYkβcj%$}ea%͛t6}gH% S>zKVLź*OL5?^He}xמ)3T9g0x"He5{6Q CT&(yY4D;z3>J8i */B ^hPG|;elMNwL(,;-EW%*4^8;IV]*#:1EjVx%]yl9P{SPNHIصށ"a΀qiipL+'x()TGt A a8rs=xt-;x'ٕRCrϪNZwVE]{)X2k._HۆH4Z5ڧz!DMoRSllS\*4*YeeOHj|Vv͢wf Z4"!n5K$/l@Wm \1@YJyS˒+}BG5 n𙃯vë+a;|xS!+GS"*|M'MVS`zQ8p TzC(͜eܗ9)YF6m8G_W/D@ ̽cBW*%i!/.-n45gMP Qq"[t.6VttA;~)=aXr _m5ˍcG)J`^G\PNtEe/RIgi V7k8GYЯtmƩ*CR jD7F$3jo% Pm=揃v\V4 "=/`(c&NEu  es#,r@)(vN|z"(t{4TXynzgo6fIENDB`thawab-4.1/thawab-data/themes/neo/static/img/go-back.png000066400000000000000000000021511305262755200231310ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<tEXtTitleOptical Drive>g IDATH_lSUǿvٝ3 doƚ(du 8aɖAHH#[!F@9 ,B\nݟhus|av~w!BlI@.va$Q(-3?kz)@!DQ텯$ss.>"[د@T7XXltaA++4[Ji׮R>g%˴ e4_4W eN5Њ >DŽu~ <=f0q Σ ޹{g2i" p !i β2dil]_WE!oMcO2#ȷ_~ 3Vp8pڎRςRs9 G"iدL)r N!Z?V)H@P4 Gt4|==}GKjP8upZ[J1=;"LbpxhxIvJ)*J|}/)X'T*M0P4ިގ,*JhZ CU7/ @ !Ulدp03; 'nL4[*Bcb^4 3?涸{_&Oޚ&OP,`1E&.]!6*yJMU 0lIENDB`thawab-4.1/thawab-data/themes/neo/static/img/go-next.png000066400000000000000000000021241305262755200232070ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<tEXtTitleOptical Drive>g IDATH_HGǿ3R)!YBCZYl OppE}8C=A -=(tɃ`桶@z&m '{3ۇؔ$\la~ H,4vB |b 'gc "Lk>V{pI lOLkZr1|fvf׻z%Hijnczf慂gKʹF^PcRtIIq ӳrsGh8qfPB2;n/y:roσٶb 'DB.F "RM5w|+3:P(D:z*Ya{{@i3JY3jÕ kFgUju](p7x 8%~hxH?3 W[[nnVdd~cc3Sx\.UT8Tq ع\JLҍ_( y=@2y6]Gf|쮤K7#ZVY]>v @V[[ϛϿ,"'?G9 `# 'nj{Eœ/onn_^\<0yЧO!"(u7(bxq*ޞ̶fIENDB`thawab-4.1/thawab-data/themes/neo/static/img/go-up.png000066400000000000000000000020621305262755200226560ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<tEXtTitleOptical Drive>g IDATH_h[U9Iz}PxNI8QAʘK۴ʠ?CaT!HBQum.qcEDUL%A)fM6}9KRсVgvhm, fv|aLJo` ޽u}mOdG# !kV s{6Stנi::#QB##u JϳOTZ>JKu=e2!5e%~BpeL{h(qSdA& wwy|2buP; -"|ޙNիv8s9|~נw0A=X,FݯFYr\Bos ML;y|>,.]7p&ysKM@x^O{ki__G0IǓ<;@,֗c 8gPa8˦x,y](s9N9sC>ɧgaLzݢOscF,,\wKX8c.!8y*p b}`(xW^מ|bW4:ߟs%Ǿtr? @6;drߜRۇ+0 o}AZacRzRDc^UWRG茶reV}N{SJ55u7z3`)V-6R}ic^an7-@HgtVjmA녷oA+2ݴz^5&BHKR9?1.e,!TDtrdk^Pt.Y$"B@3 @PPQJzc۴|;[IENDB`thawab-4.1/thawab-data/themes/neo/static/img/home.png000066400000000000000000000035221305262755200225610ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<tEXtTitleOptical Drive>g IDATXW{l圞K/\;. 6 Lk]lbhĒёՀY@`̂JPaB+4EA TZenzsΥ4Z1{7_}{~{=DJi?ĹXcRb3^~_8 2ܖJ*{ν_hmm~P*95}j47{bWXw@SS]ɍ?|ŦKcD|7#,֕Cs!H˙ BB8}e&|ic&Рfwu8[vp1!0 |t o ~/--PԺY3= g@؈K)SHiEQygۏ._jMw"pXsn˼-pnXyEQ /"oLhRUUa{5vTWW&8zqgԜ%BEN>)!ݦ<:'SF,QZZEx芪㺮qY0 &MQ@gW0.W,mߵkg'ngg"$if=DJvg碂 SVUC!:ouxqٲ`tMmgpns9\.~dggA_]6H)qË*Zb V(]ݗq7Tʏ6i"g/,\BNLR2'9.Ap!pΞ;70-8Tj @ΒSU,.Ȅӟӝ24љGIĠԠWXܮڮln<>қ̍ǃ̏$&ŷ޷ˋբyzz|rs໸߹ɈӐ͑jlIJBCsupqƁ! !Created with gimp! NETSCAPE2.0, 34 @9A 5 *<&+NFK  %!K'!"чD , ƚB .6 #>;$*-$^+!BA;LLxэj R GT[ nĂP UABDhׄQ c r:W *1A},lpQR!@| p(JP4(: P*U8IA>@*)FjgG FZ CG 4ǃ_2` ȏ,uD ~>/ &"['Q\-< [ NT .%OϯM :%%  &N,G*A9&: -:K] F4 ^2 ' H} 9"X!"76a䊢 CZ41!N SP!BT[rP-%hPA C+A&4IY-1#,چ^KW|,A7%5rD CCD  "% A,cKHa $2(ˆ8"!B&BA2qur!$پ! , MSK@[b% DAEQ%b--Q *"NA-SGX/0cI=*I5< M 5 E=VL5/3L*!QL0!(`HC0DMQ/Q! R a +] =@)h@BrA"%Fศ@F` Adŋɇ%E(82 AeCz' xeC;4I+bC }D@%?!XN\`""  :"H\B7K#* =΄FŇ^pG! , ADA< Q<EQ00FAE<@>#Q ] %%/DV :[C b@?Q2=X3+2?MЂ=5ȇO.G9'DB %L% D@aAQ60@.D# dGP B -B`[ #0UbហFpqbC%NU61p $b25xT \GLAAZ#qXP"Jp !Jy ldCD^^5dT:$dńx,A &lFDPF'o7* F%  8(wwУ ! , _SB/9@=F]D[%&PD/F2A3 J fEAW]B2H)SI [ ·Z+% >  \, P#B Xjl.L+v2$AQxqJBJփKR^BP?PdLHMfDmJ (8 (A*AADD] L#J wDDK>; =drsn$!np_D@*FQA&*mLC"7.( &8REAH 5NTaJ;8|n6Ku! ,  (4 %"N RPFIaDF.0 ]!Ȣ+Q6pY902 F4,+?6 à ^b+ ?J Үt1jLaBR #S躌D#( `2&&x@DR CD1KÉ*B+fcF^P Pb`(aK*:pBDR ! , >P(OA!(D- -\N-[$ D;MԴ]V>Յ;(@X>99"%+Q[G"XD=~q`\#\sa! J0Q %hAQFPF Ѕ4GSȀ1\*aA'p!8lpQBD2(!P-\`8E lŢ HcAz>Q! u:++''n.'\(+5лW>C<^K1*I^ p$颼W}؄Dz6u\N (}/@%|e@J鳿_G/Pv/zr^O@/K\Zr9nd`X1.@;%x@Uo][_UzW kK.?pP j}͠lj_36Aѐf#kRqNWǭm*ZjRKEKK>ߚE-zn>֋|r wAX ( H1^־l}REWK}@(% ]piۚc'ommXY6446_U827۝yng{^8У SZ/#Ҿe");+xjO/娼ZR*u[zSNjLlPY;QTDp\ıQGs{uf}}ߛjwt .9ms{d/xDQH^_RT+QW_5r҅ l>m[s]*9FQtt!i>2UH"ej5T@MBMjԥ*/"M}͠vvOvϷ SQt,a.ĬJMMFʠ_ eD`<,WUD-`'׎,T|lwt hA ^}'Dp'2۷XFiu]qZ[b3K4ZE/wxñ{n]KF?e.6@c@c- bc.O,+A4Hc0CU0\]}|5V/yR,`ozKcxu -goJN9vL+-?/m|m$B!0X FU)KCU **4BycP"Ic 6tDJ f.$x׆:NG|P;6xj~X3d[_ȅ#U4{@>o|}fI-s\+˰xW%P^upr9+]|WVƂg5-f_ܫ"''`>6ah`&PhhP 0"+'P@4JwoJ2(#(Rb ӑ`9Ih+C FkMAB-tlyf)u8c uCU.3R;"|٧y}([ztTO["c>M᷷^~%ۥ\<6/xdAK{!lLrZdgFZ]r!: Y[+.s - 05,*&VNM6y֌I wPzhz=hE A#IHZ0hO/Lv cH?+{-0 8|Rk?R&ZcgOvy{D'?XV~Jn^p VϏs;I.@kN\~'l4KmIbm\ I!yր% Lkt @0,`"hYJRbhi\bF0C<odLDr.LO RrJ@BP cښd.HAJ 0)v;fb0 ] U!8kţuN.luMߜz ErBX‚ "(R5^D'c蹲/{;lп|ygo. wM+~0+ (6ʄ@@E a0'W P/c8_4"cHmQgNA'K\r]. Fh+%ԋMM}jCi K^WVbjc_6I V$WyDz7w__;4e"(n?B'B WF r/}~/;c{߆rZX7)}*j s%' +6n?5jv!NXeh r2Y֕qG 8)9?d]#`"Se`B] mY Soab%orDO>޻>cI_KS5jLHx;do򊑭kO\Dr4<4gm.LE𩝚4%+u`oW;[=6D[3W}z⻢*8z"׻&48/d[_ȖFH=.:W'53fVSƚiM?Owr3;,,PT=pn$݂Wx1ߗ~w-He`~A wl ߛG,HEzJ,dCC2R1!X_C<1oxehk|_SV=W"^dp,Do<5wm&N 9w(dC# 2j<0bdT&W_{_iߞgȉ-kbшlW (%o]?~fo~`o'*n'4~ Z `kCЗ31<`xbCW+Ns<1 ʶ~5"8:5PN'6hD93V&;@;b)$Z1BZ3 @.iS{cgf7~p~^AY .(|ŻX+|u- 0 n/i_Wn=plHtH* . \>bb J9K}?;X o^&Ur"X-()@`Hh0^\0Rc[DsK1^bO+f1Q|p7\_g\Rkc[~7,W'^]/Sx 3VItO||ە~E8y7iɯ[!oM7H6^i6|a/.'ǕTDX%X6 &i:&kkٹsdnţ Z`ta"y.iS<ir"XH?П*/k8 Q~O?{X؇yb:`2xP|%\pȼaMUpɨ`}n;ԅ{gl 1*/)u/#+#XI , LyLFe? 1cn;٥K&2U0jQ&Jnhx筟̾H7 4]W l (\o[̷^{ع~屆N(@ /%'7% &v}]oZ+h)2'/łhK~ۧD^- c4Af'ln`@'ËY^ATzC o^7[ Q)eG>[Oܸf9r%HxŐ˒1~!RC[g\es}ܯ9- ʷ F(X\9!Y[Q Zaрr/~2x1DS p V D[!WL48Y!},RRaCB[)& X56ppq ];#O=$W $P`T~8+^vp p]~CЇ?墏kwb?S<BވP0J^7pz{mop(6h@~Qz|^KS'rHyǗHB 4C }Ww<{8[|u = jc Uro_s, ?J$PHeE/:_En3z͛F+j!8!{;K tTHb& v2i_ EI,+\ޫs /mh)WlKpH̤ݯm5kUXLb}x'\>Bi}}͊WO^Ù 7=~)`\ Ů<C!~g}?H޵7 C''ϸ~X)מ׶V $rPQ W "myBbcx8&{E(=.;_W Ox=gv+`-K!9P>SݭRDK%xk {:S'{ǂbd(xYQOuJK>-or?We ~; ^7pV@|Kq4Lv!2v#h_M4O2`/'޿kqɎ>l^.$,F2~&Ϯ+GMx4$`P! zȵ_C,C Lu#:`a_}-]vG,nE肃~3_$1k59~盯=ۯ گi+Q O \12Lߛ2<6Ȧ%$_j3 ᦰz՘@f})K D~Z{tBfcz^ 켶MC_EOg2+n98C-B$bߜ[;د|v4EqkHFG ~a`hd2ϞӿU r0យA(`"b,3<0 sA ۥwVE{/ Xfُ%GށT%$<ȗd?#(Zm'MFײ#]ϑ!Zecu.#f oX3ȭfOl[֍EGw/?ř0KP&8t$M/.ҟY}`xӆ/~_vJst.V|~b_G"4AoK]2QLE/2 ^V5-}/WJ@_zmzo}~Aϳ7v8H#dQk' !jW$` <aP[!obmv̗vOU7Wាv'!WMT=;0P'Il,tzVd,~IxQ#X ˲ Rr=pg ¸ޟ[gG5s;[3T2 )l#0V\~Ft}sjuha!I50P|贍lnoط;=ǟ\ L3$3^ xH%!#GF.t蔿at$c{mO5T iRa}6[gJGYy BC#M]7HUfHCF;۶LsXwpp%Xc㏝GHeOe?xW_l?JHDVS_Lv rаk|c5,sKnAA&.N\[/^Aؕ@nq}b1&Y]M܁l/oT ho9h6.>\!()hjUMìih|i/vii2*>6WO^Ec5Z\R}/' xŐ.g%{֟o{GO}ǴT ^)lعC!WT<}R1K;,/Ecz,EנphW&.)J`[doH!fJ,=dV]phi-.1BIph &6iZd!VԄ8-\f8ݮN~;X'6$zOgr~䤷ތ=7L{JJ?P\<rO)[дL#%/ݝ[,ԝ͊, '\2V,خ .zq/N;P(;> l?A`L ^%`@B@خ} ,r~n( 4M/}TȔ" n@ĆC~$R>-?g?3oփb IDAT3O=aoʉ*׬rˡ.O/W9)9|cP:5`~([yq]/k_XKF k(}?ЗS?#e9X0\ 8ky; Y2o{@ <]wPF/mgs_'[|a$0<-\|贍 t:?40H% iƠ= #H2p^7c_:>u{\_gwuٱ~ `:6<8y mb"i3ȯ~؅z@HQm<r/_9@SCA&Q$ƱL^V`jH^"W`9c4ׁߺ `g6Ƒ e ]\7~1ٵgkMJn܁%3$yVI%q~p~?cwkG:oW_7$wn1Z<2|`tLvcgNpD% R`h]}kz[B=E0{,\-=/o?)'{o RRЩtYt6Sh+ c#ѹ1)`3[G9ɡnħ9.)LJ'I'griV S7%(~7=GMjKw;J-}o0J|oR1i."ED '_7U7=C16Ey/~+6Kyb*wAMV#'D.۔oLw6tJ IP_0*ONҳC"mGbQqĎҟeOݳ 8p12R{{E? W ~c** 5)XM5B4ߟV,Gc}*BWs`h),i?ҭ]w+Ǻ]z)hVڊ N" 5y$P^Θ Ik.G$Z&se4".c00T%WoM,D~XW^`޽'܃+YGXgxOX s~^gy&\(`. 5+wO',~k%;_O}C\%4daӶv!sZv˲SJY}ߔllMX;'LJųGgڤ2}}1"M$djHm'[03(F' ĠF( *ؠbMiT̵4gocֲQ*{f.0]NigZM] =ɪ'nz(R;E ~`7NHS _9;w1V<0L’ ^{X i(Ys .KwA*ɣ_m{NEfHH;kqke Rk+˱JhHaqqW=?#S8&M#5" 9WL۶T4cxU>JY@l0& I4qǮcZxl=#[{ckU~u+tN]Ǻzco;[ٽw9[?}$eE.! #&ȻKȿ#K< lh&_Pyۚ'wL&,$?Rw`#ϏIJ 's ٿ@}uyJER/0x=JY#)&Cy.l&&hU-V^kRepKM"_g)aHadLAeGiѨDhV$$GDa0u1M_5&5|aa^7>HKEb c?=cS6Wںt@wcHq%#Hr?{^;tG>X]]"mû7i)\yDv.iy0f>qG'Sk<->!z%e* bM̯R`Zv`^LVa(U) T}YEBFUh"&RoFP mJ+x@I.(1@>HIƺkN u;Rڀzh>`J J&I4(KIbI0Ouae 3nK/\ÙCM޶qf{DtGi쩽goȇg}j~p3p\>c@7^o ϵ8 T|uoL eE9 t<|lN_[XY8_ .{F0V4d`- i ajԇOw#fQX)$9hѬ8R i-`,V4Gk3T:bPkmwڠ2p J)2$IG#Z.S7Br:gq._@czJ`X;Xesmgƞ$>wNDbUo/$9î{83?xqS=5#A`?97N ^7Z*ӱc".^?mh꿓.řvOQB| ' VpEk@r}_bHPB=}a[+Xk&V*BU `1ukG1Y[K+;TAkDhѐhm?ǐ`Љ!Q}MRItbab1QvW 3ҋ.璋.gdx ~zf)|, JeO/)~f7o'gݿȗ)>aᎩ8 v󇫼aB]uIK^Hm~s]?e`r@y;zY{GPo`mHB :a_a6*.)-0R(4I4L zd}CP @ BH5kc1ﴝ66S4G]|f%i*RvD(Htf޵ VsqEs<1hY(l&V| v:޲qEsr9mM4z͛Fu9 zܘk{;߷|]4t蒋~췷^*5_"Iz5bm}aG wխ8]V,ur'ss~+ `FHe}KY5)HAET:A 3qLu~diNe[yfX$.ib>AIۥ^s|Seh3i#P ٵ1§OS1ha|I<*= ZVhq4FMb#9܇FzsIsrEq9Zͧ˾{x{`m֫K1}߾wng;6<{?'|췷^s=so=^ ~?6 j)_8M=/`_0W63Z)be~iէ*dl\BqɅsŗqi'kj ;{{~p'.Uai BJ͎6=nL;Q(37Oe>yW^wߧK0k+8*x*n3?Cv5&GgcWDJbDAedWYοnxAb`P8 WR? W "Jh$;^&.KIBE BRV%ej5*vQ릢Ul>RCZk:ɮ󙧝Ch=ftި_CzW6h>l2ٸtˢKUtБr^$$1$,Vh>DMZ80/f_֯ȥK^s $'fzf*7zaF 2!(ozU.FΡ9+!>i 3yjMel\O|//V9g-/V ?n{Gt?1k\2Z/w8w( w'cTf|{k_Ji0192`mHX!LA%::Q Q';*!x50 UNW+梈qz nn mg~gn `͚u\re}kD& AU kmh!a0dmxXL;Zcm-R$MV/ }&[e _rW^FN=4zVǽKZtnDMa¦!&70$|c 5.`RO=sn7g^{ع{̾{)?k8Lz%&‘}x߯;-k?!3]f{)02?myp߀ދw`/, \a:`jfIBM85VgM΢NihF~GtP8w B c#&.hx'aJ$LG]f][m:1sXB-d}[~6#b@eF0 A hVGBM#a~7iq"kTb`x^aE\U_缦'"I5Z@/B3& %20 IBd2}(S g^~>_ #eXmI>&ƫ/i I[í:p176J7FI<rrY"(eJ7#)Y_QIn'cO&{ Ufݝ,qdN"& d}XcMpP@b! 댅5n%ZILhQLmg"e~Or y>=8iE;h!$&2tT̂(jF0"C ;-tIfE!3. lxVͽ=3=Hk26`ٷ^D / l'.H6VT4$b7n~f?q:,Ċ{}O||U'oĂK>_@%/ a و׾fpӻ_sѹ.*dw٘YS<>xx.!yTBDR-gsvx!-SaM  wYJb@j l)~2P`!ws\~ ܖY/D*fgVX {[mZ*Ž6&iϥ\'B ;l\[,-JHڭ'̼yȹ*Ѿ3HI% 6/{#ckejzmn6gy\ -"@B T$ e,fӦ]☑i\,J` N-pr GxۆQg88%]aKZ~E=r,,  U/VQ+/qf[5!8k¹C!]Xم,gngz郚ܒIGa}y䔆ܑ1,1G#ɀTdob:er >)`!J׊~)Yu`1N&ILcwƒν0&8ٺvz첾GSL]thvL-󰸻Ҿ>T;M000寽7\FN9i-~׷xGM]J 0l#I+{A2w;G4H0i ڻ7l]f>VӞi64\<ϳͷs`v"/qW9W~q 3!Pe7?PX"CUkj$Fp6ӑN£,C 3'vc|/r ] ~!  .!Z\ok"] :) @P:9͕fA`CAMHw[L%ABXcow8$ZUV筷;.F&k/ zE`CaN' |QazVB׿uos.\+xos'Y#"#;An\`E@41Bhc4coaD6Df񅝇6 #<`'w&17iyolo}7E|p/FxsTF~烛8>Y5ɕ5C]{vL y/ӏ"@m,r7;՝lkSwZwK~#Ι^8!8G(϶ik0Qo J'p}*H6՚ %âpPel;YB)Mq(C8چ} Вvŷo]{}f+ܗ>4Ct@ϸh0e:6ԓBpigKȥ_NQ|L7s;;,K/Nza1bt9D/)R !{Ū|UF41.d`,7`IFG @X"~厩y^3O+-q^7>D͎<n~gb7[P[\< H/JE/<`Fy2 FU#Žӱ}J/8"CA|%n|ɯT{un0rjްϴYLXueޤ)+>Y`10&& {#jmh"dmؠIYR1Z[Ya$ uֈ:ED R` 3kd}@J_ _AZF ~?[o $U)iʐ &]`DN"NnJ7Qg N1v|i9awɂ^ٸR$o_< /I Wa0b) s=o1.'w3噕uԤ$Ƿ]{Oݻ|P\נ/SK9j!]1rֿ gp6ss/s)a&V]m.KtnϾL6.r9M \jVgnBx|P|CԛVaB_ʀ v_j!ƐEl цV,(nh47!tla'm<(XVwn Qe|7ï}' wf(Kɵk4Àwr%I|[Od=.OE˟)\id!ES(ϴZc3*U*aL!1t4EȴH"*BP>) ֠ ÎaMͥ7͝_:K:a>w 7O7~)d$*&(D `b5kl7;6mseES0MN" B  DthR6c; fq !<5`#n;4[֏|~$ qʉ!Ӏ௿e'j};l?c{_fo=(Kњ͐Lv&tt~#yn/DjϷN^)>"C #ab$a^,$0TeDXgN2X ԃATY[k4hB:n Ji/Y~d&sD t;' 7*뼿k|o!< QdVxqEV}[Ey?hD" &̩JUJU|=Zk{+~?ϭ3q̿yxK_/?C[iU4oyr%?OpV] щfAnY@SHK}*G4"%B2"6L!Z‘YiZ9iP^sޠya4HQ`E\!51~b'R `+\49œ#ܵe] B|Eg^q?.?jfoõrwn"?x1ܻ9"Qml"2:JkV%R/?'IPrbA?TqyZΘ Y ; ԥd1Kx G.m]}R&l!Mcx1KFhf>mWY}$ɮhAte2N-' !w\tkx-Cp/}+w=ggv=aTF!@'x/" $3$m+yR0zzƅ>϶kvKRkA$#zeR@ln6l"# PP|@MxR%Iyf깅ѐQΓ/{*"hDu9o5GmfMߗ.q&Q9Lw]x)Oy>{yZWWP0Y|2@y3@JT $MI9W"J9GhRsJf AĞ(ַ']z uá14 k^~mC^"7iTÊe_J2rsc[ƞMݛRcDtخ\1R̀T~eLgdC*j_h}};ȏf;{liN[}>[4('w8]w9 w cp |* TecT,&+sKOP~=[h_D8|ڇ*+P d ˺7RHAx~Yݦ=9grb !zkv~w/s<*[{h4b<./Rrv<#SRBh A$ B@$Zm!1Đ}irӧk}MJZSHBz*_hn_ g2μHwǠBʔSjf2Z4ΏWPwO.RvLT3kVrj龱-?oHۺuOa$gO9pr \{/Jn:UiOl˨@176oV C*3 c=E*{ESbpeTr8k&P9CJfJA %*!$M 9)%/E\|%ܾ G}'9r)uO~%FGr H#J ve.T+AES˶X>IYC /ʔp1;RZ\:|DYijw-[N=7g9S~l+X*(0-*cg`a<<鱣Yg2 OpǜӾv%Mt֚ՖbUڬ.g~RU}9(-:E5H'zH"R^/D)Pq&g6-G }lf 9u~%zwOR9%1Q2UU|:UZҥOz>ޥb~tVߴzi0"C jSD|NjR C*̜^CCWIӔO՟rr8ӓ3EY4MGwV!O= UD++h6Q&?ZdS)Bdm LtڎX4K`9%-"0k2hZ<Չ9cVUt3í'K?Uϟ8kL4AT`0hp.-[Ϗ"δziza6θr?b%ɒ8Ԁ‰$[DQN*/,ֲ18>Biwh SAbMuQ9W46Ӯ!Ej]imi9&- Bzr(S 2  AKC$3! wcǏ؏|_܅y_mjS\.O%Q9Q^g8ɩ峔Ọ** 6]U=Kbi;}ФM}rBSK=vYv ܛ\K9#2n[v*-~/8H Kڍ"-[ϧ4s3n? x87|Kg1\4R6vBf'/{=W,?~پuGNcIEJ7ۃZXDz E_BY SC_̗PHSk8^ž[D.&xjְ#pp%|[rS tV{Bߟ]LՐr\L7xH |qar<|[gsodWEb w=[:IJjG f54Yb\kԒ%}O;`ěf,ZJPJ Z$h@IASFdV" $_vezf}zYy\ bhLN>3{S^y=j IJJ(t `2pX^ AKF  Ӗɠ8h49܋$w16:VLMi|`q#6`Z<9kz#e~ k/n=p`$s Y{[[/<dkp4`nOi&@5oGtebcyp9#`w+Yu3^I+~]7V+6$^+;wFPVxCQh,5YI Ԑ q_4IIMgdcY[Mߣ# Q!|? ʒgTѮK%E0 ؼ;d˙֦͊!xݷQ*z#Qhl %DFXs'dr4l\g<ۛYV9g4KKOE6 凜?K^SBغtIXqJ/eԮ@5҅Z([@}# _SU Q+!Har;`4PHuOGbSM]@*+a@Zs]쓘RZv6CF"ɡՔNf*oMPvU_=o! >& !yaUg׼d@b4tЏ34%%)~J$q:1BUfΫMTҧwu_HAߦ TB/R4ZH1s>8 u:Ԣ( (D)I\PHTDΝX$ӓ3ZWxHɵ\(kZM2)KkW iKt$\x)[ ?6wص7:TŸMeEUy*&BfhC+Wka5KQj*_wp\cϛd?(.t/7E㯸~f'3. 0uW?k7_XU&#`{3d!&Z =VTBSᾔxYNՈCSJ[<Ljб&԰&wRm͉OZXy*H,' 95$i㉂iY+ö I4DH9} tH;:i^)ꍈe{@ KO~T j!AJ% dS䄠cQ:][2:XX߮:9K@fqD]xɚ oU0(!I2fWc{)"Dfa2|/QY"P6K~ q*gqlpzjc~JXxr:"LC4$q~d4kR9:uL_fn/enGwEs⾈MLZ,syo \u]qO O✂ǸJGE"ˈ%h'Д@F@2.Z*EFFشsUc{V*TBR D( RlC09Y4zƤ1,0)l Y5٨F &U Q~)(ZD:2br${?=Tsbq:KQB0Gog@*=H 1jKEIY]r*\4Ѣ1wΗр[ϡBoX0O *_4^y̹9w]]OX`gƦhW΁?RmwA8pe`+ AE+~uVasXGk˱4WPMRKa%X|BD{gx/frbrvmwJ 6d-t@EzTKKyup(C%57'spڥ*wTT;Cu{ᅴ/(jjCY2@eIITige~aMKDC&FJ6c5MIp DBڡgIL7Ըз{|=ꗽfxe|{7^J$n"͙T&INbʅ3>%G*V8! ,Kμ@xFk)'B|(Q:aJ#KISӵ,+ʾ%h:g9r7e80̇90bRг)2 ɉ?itٖCEM M֐BpRLd:FA' v kxsAϯ:M`)FI1N IjjW`s/lênaVl6€ ɐPIyM[Mա}1ԺV_*V.rB6PB2C..-pZMQEZ_J+ 0ʕRJL=ShB'N0UL$}20=VҘZ)I`멗_Â!+abch֋491ͫk?wnfy 3Sev8f6 ?&0g^:_HJY7;$\:30nH P%r 8=aCy' 8wDeFp*F\65ʱ^LҾ8ܑmRR3s&PuO8sS1uNok5UG}ԥш6lȿuu(_?oRPؠtcq2a,–f,ӜHyT&4} T\{uKت5!2ӘHِFX#Rlb2.LAgSz6o*C֔5걐 mXgN8?ȁfl:`֐˞3J,cYw>`J:@xpQJ*T*V"Au|cD:~5$._ۺ*=շK+\Ϲ\ȟycr zͼk׼0twꗿ/ +A/JXW;@ pD\rDXWn%ֲKOkΙht3ShtyjuNoE'*ힲZ31|_kv3É^V*GHqɻV ../Koje?Rjdwt)__4F8юcI 9#No^[_{yYRr{U6(yn'.͵3eug GJVgf"kkQMcp||I4y;Y/89gdž (?&.׌rKx˶҈BW'@"*A\[r.lZ@nYfIJkk䉹^mN!Y,CWW+휳cg9\z/:O;\|"ˈ ]R)DSkֿ+T."Z}X%@H{XȀ= vjgV.p293[[Pj`xl4'Kb9tu -J(+"^ /A|oKY[p1j-vmRcHVaAGAw~˟{s'?p\H­6,B:̋cT;Qs5T/nqҬdH>1Ӝ́\81qdY NA!iTw6}FFB K B.[gm|lwүB˷G򭛾87u! e`%Bܡ\,siXCmY\/ڜK}G XDJŚ,f`t\\?7rypwG9v3!?װč~nj!EH83@IO6 8l3wuW0uq#'PJ W3}o@bqr l3dt6$xϽӷA?|(!FzCʠhe7dXھҡf(*dSEUH 4f8O\*O),FZ@1: oqtoO>TYH{o~7 3%Mhn蓡`"]BSf$ k)1xHw 2a3 3Cv56R$=͍WC$ _~y/m}C lDxX{Jd(kUė;/$z$>iʕ`ȌaLg,?,.eibs. ۶n-6_ꗽg57#j|;Lg4G<ϛ0Dkɬ%IS%q<ҫxb3G*N 01%d^SRfB N"2VTuϑL ,@ `S[o &\[pZ!:M tE)O!IVˡ'?M^WzHq;{tyN|KˍBSS:5 $6 \ T;*DRH&@T1?q4 fS-HTUH/~Î/~,k kkSp`W_53y.Xt3Z@3kbLQQ}-Whk\'_cb"$I4,%Ӛd@X7-j5_ FbVgH)y _ʫ_ZuM_w- \j#*]<6vQǔEaPv>AkD=Q)ū_:}7sqz _}/ )5V&iae9>+=OUh6Z%Ӟ# *Q \PD(muil(io~_w~nk6@ls-6;Vs*cvŶV(VPSʵ/tE$NINkVyo~^L3PlE~l$X&g0FF@x鍢ܡv\>{hph5%5PK)νb~Tpvj1£Lq.W5HtjA[:Y-gu]p qdE@ڻc+/9g% CR]*$EMH"Psx>g 3Ȭ{:Oj;OKdf{.Fwdڲco_ /׮2, U4PJ`V]H%jK1:[ 3$J(hH*? !ȍIi'0~Wo $3d#!E-3TlTțLEmdKJK!Efuj;z6",Zߑԏ Kb 79¹ v*{E+qOUAVeNTQi[EK[T:5B?M?͞~~ m}r iB R_8B(ek%2 '%mfkxx]2_7~'Wl?mZnFGF{z>1>IE$[{e&:Q !$uВ5BUp;Rk $JRNS_*g#1??Wk.%PQJ8{k|&Xf> ίM ޼a3#5lDDH]JW PB6VeB#D KCVY8'~Bje"S}f+^5 ژ sd6*,ojsZ-u=j U?gKBk̀r5f@j,ֲQ'JN+i1`*sc+_ e@ BI/s6ӣ?Ϧz ʂ8zŀc5:OHeiX&f47B}L3EɧKꬽ{^;\|;m?ܧxǛU_98m #ƌj14pR^Xڒe%: VL=# 32]zn߽R5()\rI]$̱6B+h:}™V/h1IwVrKbd}qNjۥW>lV^gg*>r!N>=n{C&d) LmS@{΢qryap)XqLQ-ev_j{,dE#CjvUߞ@ h.R-|4SxB hh癙.˂ْBS$Zp%y3hX#FsvȬEKz1tР,eԚ)N-AݸbM(d5KQ&@H/a)%9XB!H M 'A.-0Jg`%;PXgSP!|Zׁ%T l,'* {kϳ6J{9b(@pL~Tn,#@"_l1E\Kٵ`2Љkt-?hP2 JF-ҋ.Ck5cT-mvK WW 4}0,m:9 khVB}7f4"?RVLj, `RIOr2+,sB:s QWNhZ#ytTΙ9!5TYܐPX ~ROWU7Ql¡SaY=UdHͭg4'lQofKD5EHHRLR4bTa|r} u~%PhB!oWdH')5%h8ckgiHir8#*Y{=}(,>'+LC^2`KI+ -#$pq~Lutf1SL s̗ ];-~+!N4ˊٱm'338S '>?ҫ3e=7)N>V'N];/?h$/$OZtXZ ^+̺2iAc:+ş{S֕xde>]4b  3bPLcX%T١6V%XAw1RFIt}ZjVdI:̊>6lZh1V JERb4UAx%1Bj9iWVrv2L[(D+ն81:P/Sc9˂mk9vԕqos>}sf xY]50c雔 ֒8$՚h2>s/DHؑ &ߔR\Ǽ`lt٧|_C^ /""Lј\'bBR5R"z"Pa\k`c%' JgEfc׳8F`ԭZ0Us$^e!#dj1!hѩ![{{H2mɴeeNt(`qIoI-Icʹac_ X-*¥@t(Y7`U2[É~ P "@Okh>Gd -kEGg`<-qyݿ/g[ hXto|5ڟK@q=6S[+(('ws_p)|_u]it??{ͯ0~}1x>: ɭ3b@kâ1-[6u cdKI[Ag-|=0Bp9=}}}Ҿi4*vV4L+OLXA 铲"V:`zW0NV y7 I.ff3JhVL\ƍojx n ,e!HaW_ZMo.d޸b&c]1b艌%rZΘXJcd+ (<\H%O.߯Xh73LF`H8Q zKZm*?綄ڴ1jE*rgM!w"lqv:0k?,iD޹7 _X<]}SIj5*w/o55W;ン4MwwHzQ}D_"%d$4U@-@+bRNg9u#e豧}Jᡖ~/\F8uB)jtio鑲";.%|EeAÐw՛]XGH d.JZE[$hQcљE2Cf,Y]V뜚:1dZ]9j鞐㮺bx3W:-DeD)ÀES/ b& r р KTBN80Jh06pǂ榍QɤU֚U뮝<p"ʁ{M>'ӕ{v"˜%X:Wn=kCa=řьsLEwzE(G?@7ʪuƟio6 _|gx#J+mx6A<&gv:  8$8vm}CgT4<*SyEm7Lۉ IIx^x4l6X:)mŤs yĤBnPAUΜ46,=vRZLcqz.38#I2~FoE3[kէ=(vl?vmٲ ν}`Sɉ'8p@qdC[l֮$o˰]ί0(YO|͌a)ɨ+I$'ͪ5zPL¦8fkC `H\NV04u,s.Bw7 YbI5eLodӟӲCiL]NIE(6"z.__VR>$_֗y5N;0[~Cq9s9%,s<&3`DPAQ @TDu|UD|0E @2O$!swθ=ܪNUνg\k]k}ףnB7b"]F6(MY1zК: X5`Nvdt`z;-<֭]Jv!R!Q(HkJMJK%)G5 os:M LIKEj:`1<`>HN]22|scֲp 54L{Vg]\v6ǖLzq+—?똂"fא<ÈWUdvi?k]=V敝ݢ0H Ao 8`Z㔓ꬪ:uTiS uH)ηȑz:.]P)#u/m!-ԽlK܀ߩQ/Ƶ1Ƈ:K|krn͕ ƒU~ +`*ՓUŴ;{?{ᅨ tW%钝 w`O36*ڀ;YC{5Z%DhmOdL17`C 2κ_RHcX]1s hG-bׅ+^Ъcw? B[K̀v?Z[b4:_ݔcKgdkJ"Zg{^0fH ~HH< 4dZ! s ҖzD%پD.JvJN$ETN^y՜ri?`€ƃ 9 u t^ܡ#(uex]\ /qF@H'hÒu\W(#x&}}]/1B@%2H&:hMedpg1q/s ⫊n.5A>u?4*Q[ lI|҆;#]6m̯[_+ʛF`k%ԳΥy$e53GK8yBz)-RO,K-[o`u9q,..ؓ:"ȋʍ-P3hS1՘|N'lae`P1d)&. YXZHZ1 AH )dN=tY}Ie@^gr\^Uǟz4XP YU\qq|C9uM#|lAMue2#uv5kJKwE;.yLJ`t=\7wݔu @eX "Ⱦ*?"k$ݹajz(22O3௢R /Ka\T;2t·yst"6*2âȰҒ*Քêӛ?UsW,Glݼ{C]¯tGdOjcҝ? zvhQpA?5pm޴׽[nB3ڧf Rm(?aC;7uz湧x}g}k6U RU*F< v1`a~:γXЌ[F[NThRtYX22I"zsfJ$MU,wVsw5h-nfnس CwpigVGk8ST];$v`Ί/([Ngk1@WXZuN]-`K1KzMWc}ksA-7Mu @ZjN0=qlf1a~Yn`VlBZ QM1pme&)K?O~2yUk7ﺥ[mmS\5ɧx-ؽ9|~gK+ږMxZՅ$7Sp~@0OSRޒi7NZ2807o-ߥ1Z0t&gYj^($[Tw.wQЃ<X'@wZ=W٣p8=gū+=_VW\:6DR;oK%$:b0E<- iw-?AHX7? MbHTK"mH0YuQO>?O\s헸;_/et{Fxx~ŧTP#CCJ)~Ȱ[un&I[](Gv]e9 K*,KKjY9:NLh,f(OPWcMISIQ<,`U4?ɌAqq`X<۷=8Xs%-S5Qai,BQI3s~9~ \vѕVfwM l\N Q`d%Yvo-Q[g\!a*~ 5YQ^;G+XR|:]#;+X Q21b*xJn/4l8i%K*'4XQ2(ʰbP5UdHuF5fC-7ygp|8ꈁFNuֳʮY$HHju.|\zKٸwr7pwuHP !Ȉ:p>>}Ck>CӴbQR5He؉|x>IP,jY˲ vCwU,FXWUDuD% ͒ɑ \ZoG[~PBpKVNm,2e-,b:Jh5@]7oZ~F~\on"= ygdƀTmR`t-|ɋw_mR|_j/U59+Bj T(K'Pv+)ĕ ߿zgLFe\7Yfs8U#l=x.ۋo<|?p󢞠@MP|c@Hj^B2XO\ ÝoM6;λ8?n`ff{Jl}ۉ\ v{a1ׯ# zM|J^FUօפH:3`H IkXp;﹕/캵d| s Rk R8 4E7gNvP@- ~rl g *J,MȲ(/y:zMX>L3{B$+kf>b tMr2.xۛū_|˟6hI8!SIBU$Q,<_Zಋ_ʯ;Bk% MU,k $ QКmZ!ԺS<'ZT !h/y_$'ضy;jd0YR]:5 j1JE7uW;69=ʱλ>YzM ҈Z柨uc8!׼W;_k8mj;3ī!3O7O[g8>>PoМv9>w7.f^980 VuyO~o,uxR$t;I{ ꌅ"B|,FxN]!?14f-ǗW'>) : UlO_gx7qV9~trjDe8eh]Ɩҷ̘uX+ ՍO9c}}ƹ=~ۛ}탣DS5TTH ZG`\9F:}( nnx.`K2y>pP^%|! F,F:IkX?Eh?'rx gCw9T0zFHPss{3]xۖk7n\O<9r\;o*~-nGfF`'8Ups—k=EkZSLe/*K*vAA]Ι ^!t \ftlVȈe`Y#Ic>nu5]b6Ƴ"u) C*c^Hd \#UNǥ5zE&O_hstJaZ+HRfQ53m=Ʋt.3SˌŚ5=|y{f1[/?^C)EC !)+v_z (@/v937?^v^ΦḰ5cZvtsy^9=| g 5 qiWuG>>:|yQJؤrr|z-vWM kN|nܓ̊s4BZ~N>J ݾZ"B*IbxLjdh}2>S-Uonk*5x5 "R2xRXWBSD2)&NAM>IIaڣӪj$H)b󎄱6 EPB ɰbStb"j1FHXę#6SR12nEX]1)FԺ_꿲gs6m }slL093:$JLtKWIhDYضIG| .<_Wt:eyTI$),inLu㷝X PUsꉧ}㻊6-rIv;.aL:,U6fƄۘ3V CֆBӜ2~&٧2 ҆!leaFj8V\(`P xg)RfݧCW3O;L 88uTU0դy"Ӂ#kOTK }}]0K^ g& 4?a¿<}mbL ң]U]k!7`sO|kf8cG/PR"<&$YOH+^${~T|~fڵ$m\ڌ9IZAUW(-<$ sE:<`_z :Heo!@ZZ2bQd6`>ae0 i͈Ii̼Aj48Zf6CY>E?" CPװXnEў 鼕7! u&C&՘xpjzc>g*󆨙)Iyc~Ѥ)M_)3 SBKz5sd"sjwD+Hhe,&dK/G n"]ڸn.U#G :m}sMD 7 X䗼i(º8ߓKndYF4*{a ފ ,kNO8sUUwCtw Yf0$4uFBwA1gheZ^"u} uf)RsR]T RnS@K\># qġF?F),#g*>4!AN”81dj0F`HO+b@>p_/;-a]̦n1]Ci Η~! 54 v!ApsEV6=\7;_]^WV- XBj{ M+f,0<ÒZj^zK n^^҅EQI-n fx(;ŰU[Z2xl|73:;) ==x@癌4sƘ{>ObҐ!pzTuc5~~mܰ,D!bEjl(Ė(MIt![Pf^V V[[ R=l ժ {d>uG 6!&.x7XT5#Lh63;cJզkݔ"B RRKkn`1L[$H3Mf$iB,! ivJRݒ]Qr;+VbAA==3lXhԩ¢@G?U*Vj|QutI/y/4M>C@rx)j1TU`.h˴eՆ=CX# 3D%LDmt9':>@'i_/Y=5hV`cf6d{l۩8B3 !57҆c@u0MSw$}.d%y];NF@=(Aʽ;'/Z)yNT-1gw^1:"0T/Iǭ9ˮx/}U~s&Oc, @l3YFEW-MfQ7 $cG46($iV;adv:fzw9߽J7 A噀`R4Xd,d3M3KH3Bg!z_y/ٸ~?ۋ0Ui6$!K Y35m]JOp rvnh{u ,d!d6<@;i Q>**y+3\v_5eus%/sގfkjK 2A_v^ .lt@w9Bg$Aɩ,x0m@ڍD T`щ@kͣ=TT|ɥ\:lӓҥ,n&DDwR°~xEZs݁yMJ=|JHV($*!`0(<,$"Cdz6!*$ZZd* fG&jGLȈ[sig_zHp7 SS9ӋFJ`'Ҙ~41'բ[m'_G&qևd>hMqէRGn|ڴ1n\& %Λs.8Iʒ 5i2g(<\0?P-Ыnv fy(LV2 D$:`$qJf,+>(3?k;$er$i^UԺ!$:d*|34*^=q΁ј!0pD@)ypAܰ >'hnȺUbC9krʂ’!y/$Eg"M h16 ZIb 0=c;$ܪyo]Wx@N/ʀ-N g?jG~o&}};}$IF^M~)mqYjtF[[D^{Z3={Sf߁Cd|V5GxBlCsň_dr̗{=Ş:ϵՀFs ?qOEub6 Wy~mo{a22\r ϗ l< yDbe4KM1P iig1M IA( 0LiMyB%׼` aRI&,pj*brM~ڔrW>\c>nFGX`kQGjԩ$n,\3o{ӻ|UwVp7Toؠk S|ˆ* JTꔔZL bҶyvVCӈ(KYH[nu:6, #, 7K{3۶lX$MBdAVܻRjkHuؾĞ}[:i'^OY~?ϭwބ1EHR^?uKKx^PݢexNf(, <̤w*bD"TJ#qDDqJ{И$QyTk5^c+9憲4͘Q[ȜѲuǥVOL?F'$Yy}=ctc4Z֬^U^3=)Wj *ܓjHSc ڪ[htY/@ c]eqTPa@ܞWz]Dm,X69Yv>)%aV(甋IpŮx􉇙" _gy[,/5y,+Vzk-ClY]{G2rT9JP0 @Wٗ)}c]-O 6݂w8p$eu2+[|DϿ'n`4ph= гKAt4 XR!Y2+Zu| @_vx(n`u(HV.E*@ UHYU OҊSLfBZ$nO1O%3Qk{!8U\# k2[$FIBef "K-YN8YN)~G~xw\^_3R v!!UP<"H\=7.xxʁy<ߥ)--4#}Tv:5(S5Ur+NHZkrYʅ>pc&M}#:\5?rSG(G"K :μ,Z׾ۗ> @M6hsXbZS1T8Gdҩ> y>H˼I@JT*!`6u-R_ꢖCE/V Xl.{i擦[VѾq,8ZTZ3ޔfӎbМ_悞v\}غ#חt@JPPcqxGG/|')*0->{@mvhD3gDN:J,B0+Zљ[&kѓLS|1s1;`T`Xkz+˫2c&+ dl꫸BrY,|ϹuRigY$kK'k^ <<T4 Y[XI|@y@jg -䨾DB#c\Y9Qb:g IDATr:?5 6 ,F 8H'o步 3H HH- ,FiB3NhhyEy˞=!gv?Wy1/T83G~= *2Sy]iu) uSֽ0`L%|rУ n>ODC3Jv(Yֿ=]y BUI<žV̐g:71,ӳRXG ;Q>;p0KkFl鯲Dgk˝X27D(0iQ"8Bw}~LL9ka+2A`L&@zPf,˧j Xa85CF #^QUgTIfFg1,j } yw?tO1&c5( Tm@P1S#>T: UJ2Ϲg^3l8J! ) H/0mT]k_6R>ePEb,b1Y$Xll@k,K[ˀo!>ٿ-4v:nt^u ZqЙ={qås|rCU A, iH݄A]>n地$n~ZJ~8>u&w2PYk%BA-iJA@.PYN X4㤧~U౺nu~%ݤ;n*6L88YS̷x d$ȒQi܂ = 8jgTΘ#|LTa簿dѠ6`CY <'mUK/zCS;)\%vzp {$^EPRX "`<xSHe *5_bjइ9pED_'HG% _|*~N9Kv^Qk\w{Irs*u;I$*k2A ؀ 65_ǖ05`El9畴Үyfw'wx?NUuUl|zTy{.3sLlyB!>3N:puHAԁcVM@Q܇1:ҴЎ?PeC2dxt(wjדyQ͓{ŽGG=קNyvgWVlfkvNP4$ڬ)XW`[, H兤kP$ @Ow}G #^w/*>fϣGr 0mLA[E4f5j+퐖:7ޠy*(<3HDk% $P6ta0":khp 6]̫^6rq]O>·uz 3wT M,%0Mxn(bXZJ2b:MTNކw5r:E.:n!hxzyr҈V=—JB%N~ޑ RihnSIGdKS 1!7Gngw;QU aϑBDd ?/}y͋"֚\>:Ȇ"O0/sI e_@$tu-V36_|oWs7 1kPjZ!ڥqBz!Ha aJAP2ĕ{ A=piz Щ :W n֬^]̅/kNff#=N8%SX66څ*MІ[3Dzkez\V b* )w,悶.X {M/Cv0JZo`1W,p>c^cST~ NWEn&4gcbPhPfG;)WD)ZkEc[ЌY#`.=@)[w]0`lu!%R0eǔ4bFjR :FlU_@09uoDP(>_NRQ#4=k:- mIw.DUP"cw,G 09cY\x%\p%4ySl:_K(*Lc/) )o L i`+\RfMf1M0l-Lh i#-Bh+@bu蘻i~'"lP^[,5N!;ezBD0tD1 F]3l/3 ,k.Dabd^ ɀmm2ijwt"S$GK^/x6'=o5wOtCzSZ-r/qs @ HTJJ1ײ#Heh6_߳x2ȱhp`w7yۯ:}3߳{ aa&'JCì[s2֜wjrs3G{AY0Ο KƝyei8 \NTMPzCG:/0AIF\`if5oW|ۂ̓^4$ySjnq7sgs/bTs}ʬLE|2CZ-b;m.c|c m9%oUxH@HD],#0E>QߛSv[>i%jFg)B!`bH FA1QO~oW/84||W˯dzvm[xzL05'~ s()xj|f\wo~VfN 1Uixif)*S!~K0uFDe$Kۣ &JqʓNE+ UE[g`.\z{?56Mhp //Qw?6[&NpF*dB]Hڙx=#.zZ leLutg9oHcɜ$_ͷz{ 4X^r8@0xkLF"2?*M%2ІwAsf_c}!4 `zv/~٧G [L=?|qCK>-mcP,s/IK .PLFUj)Wj Wnf1ou-vA"|ywm1r{W;E_) D8y݆1iWnԕhO}ɩC Yç 9@m0Hzn Eyaȣ3f+tomz?10?_k#1F[)N`XH1\0\偙:R?ThrNbB'OyGBD#Q: }n;8I;w=5߿}_9OD$TuKVf_(*#n?B˥ ?#Cb!̉ Iռ^~%_K+ um)xr|/"0H#X.>FzHESrPH)+B oC @Qؖx Å}#% 5APIHא:AuHF"UB+*+c޹ >wͦ5+scy%c1U.8t9V:dP@)2?h.|yf 3|[9*uT좔DȽ L!xbI+G]4ԍfƷA^^x?fsr@D{K-.ՋO6ˆ˔v G$6 # (_ ? sO 4~p? +^ǣv/Aecy>ߏYo~QWtQ^nzIub0.""<:A4b~ڦ87 ɏ1?ͭVu$r"lG|bKxϘVsw~6bmqrX|DCOip`/4d羹NԢaZ H4ެ?׃[^3m k)XyAq䄃ԖA#PĪ!J L#"m2'}>ƻ>^{iswrs) qst%և~<7teQ&|s2r&[IJВx̪mw3̥XJB a 㱑1)HtGxWQ'\u\ݹo#ʃI)u/4XV MDF67= CJR@gp;?yx"WPaɴ#tb_%FdcԱQoQÜfC`&zp ]LO ܀Y@Љ_u6]/L|)B ).ʸDِ; ;Tzi\>/1 <-wl5&"h;I@!#@}SCü] g?kw' uWirV uudC=EaENby2{@ED"xu|z!xʻ?GңyED! uh hyBM.0cJV! 0 :j `iWaxxx9? EKRt% bwS?3Cuе%Bgc^0a3$Ghr̶wC4T{&qcK5;{!%*q܍ɢգdRbD@\[4]PM};{~owjg)nfn&2wsVqA "t®v.Eر>r/*{2l} MwSt6t'CiqXXjH,T9csiv|@yacLGۍ,?oN?M=3 $QA Gg-i-`9\8< >̶g g*òbà ХmX9O`\dB@4X-1juĩ+y|n>io}f?QzfsLx@֚ H^d@߽e 3,* dGC6 P@,ȉ822tG!&Ў↏~2 "c<5=Q[~翌7үAAqyGt^!8SP Oz%.Yx.tF_5\O-\rS`4jM0i1<"ቈ"(EEac&:̻>vD09Fl*h6gwnKuwSnWgNX2%Wt@|>a{n Ӫ]8mO’:IM&P"U[Tt?5q_eLaP Ӷsibx ϑc E@; HT3;O-/xI5ɪR{jl( 4}= TH "X!!1>Ien f}arwpwf:^w9 Y:܅ÿ( IDAT Ðv੭Զ-l{#@|aӴD %Vm%ZԢN*Z,46K~SO׾E@7-m \b֬^"nϮmn)Hf" SFF./0۷/CyUcݚ{>zByn~?LUFTHC NR n!tͽX|b,i )1b&7 8!rѳ TpT=_xz/hڋtQ3;[j"KXLW u SO'`cĀm2_7֌ B%Ny!Ze+)R7#KRjס!IFXa!D'g(adʻ>v]YgYo9f㡹`< n2M?^ <$ptн -Ȱ,W }_cz e,B 8; >7zRv[ ޶OGxǛͯ^4?_~ Fv:d>egA @`F2KB.7GNG{o~k%=8E]EqZ/TZE"EFܼUI]\_-4#:I].Q:‘H;m7`MaUz}N_ɔbts0#& É %-Wlt)Cr fՙe>[ͱRe<09ki [Q 54%H)Jô0}MA(@ ~y7p7µ1[X*?p]'BJ`-a4C.w@*4  ԏP(r'btwB-{}|CL_S uoEUv}_%}XW+OuW2ݚ+KknYn{{y9D$11X_!u J(v`hh/(_a `F]s|mww(2HۀJ$ 9["T ._6 n94G=W;|tP7X W8Q7`dc( ~6q߃s;^zgZ8bOeoruI @&B I"&*AEqV2B u[ȉ|EU&((EUзV D`FV5Fd2\"CdQ;` e2VС)ҔqQk g ]O "L~e*Wϥ+wVljOǪW}yk̯Je6_}He7߾9jOnd&+Vn\qۥǗ\%;Kg  0mJe`ZWE<ݜ%B$$ W1DhaC&ьly //t L>.ɀS V -갧:h #]v.`aT,]/{R =g2~HTA5HXn Ԅ*{Fm||KՁEZ*ܒ1SK0D)ƃ)H LD"Cwe+}ޕkY]:Vctk u4.íw7I (Z|O+vTk^^7{ssaY$._v}#) g1\LG`%#(ڂu2-5"t11Pz("̱9XUYhiy0fz˅XTL!<~r~Jfs/dBN\X HԀ]ڮ Ϋx Ï%"v6o+'79F'@%DʔRl8 ` nX[Ub'%Ljd4{zK IҘujQ 3 vzRJq-ytD@0B &M@ª<MIs,L)1Kqݽ\W2?Y8@08 *MXTJWC~T&*0W>Ϯ ?B谾>}fXRn޹|Ek;n:X$& A Ѓͽ;g ^[煆r9޽f)d1U|LyU~iS+ʌ4Bw)Tl)1hٜ]cGA3mD:m`(:fH_dphD~xr5zK./r)Q`S1$.!hb.O_Ipp,V CvaKA <'(Mbw?Wtk?ojwO78d@{,Gb9`9VNHj͓HB*sJ72Kyb9Q[ [퇢O5JB{a=Wb ~_>ͽ5ɮ 3&XPP曧wmy3+^l;ϧI@SLǩ%N/LWEݻkL+3cfVOJF LzQsF=E67mbso7(:j!<26$vQ(9j4^".'a6s>ae91;0O[LDMBRGao @VD#w.Ԯ*"t\W_t罷w?63<ߙ_fp$vQE>ng;sqA7 jǖ dYD/ڧCƤ&a> @T7(v?l!9ĩ%7]Z :&ӻ}\S^oyoӻ ,xӁJ`Fu$o@h]};{m33p?F)Ɔp\^5.!tUKW&8xqR4;F`:C똒]A beCR-8-1 Kz]=״EXD60Gچ# )嬂z=g`ea[2NXES%AqKiE,K,1VYTUH14[ OD_rg?׾K{v5[}쒠PT&iprI-򙏼حgBdg 3:j&#fBr _ϵdaZEA7 gԂ6N4㋗TsY1祿^~'YeWxh~*&+l,hT3zLy.StKØ]פtw9ulvÙÏ=h6<#>UbCc"4U%u#UcBw/2:Ͼ)^UeVn s nd٥*%%ee>2% U! Hɓ9L}׫H YD]S][SbKA?/|4,+ڼbJ=abFbe|} `$Kp-d)W.:^Ÿ)/䦃se\>ĵ3 ҅`2Na8JPQjц8~j'NL<"pv?gy>ˀi1l: l⩈ fc"JU6Xɸ-L -mm쉇yE-oNٖDzR/K=@5ė 8"˜:)eǞ|_{;t7=;۟\4+N6w E pa$l(1l;kJl-Yeia`8^uޛ;~ O=%{y,m:nYzv<}oa!Ǡ b;-)a]GKu +qs뵻sB0Ӡ2nXp /ё/N,]$m)K;J$|WfR$k,n1hY+U&]!#Zg' ,iQ[ mJ ^S蠟P COi!g~3|<瞻ۺ 0>Jo CQbKg0R@h*(*)#%Eyx7{wW'S6G af.,FڂoofHh(ZAvq,3[q\C2!,#w>B%2sʜˢH"}ufPJ%6 vժ6O=q={:wt0@  2/-_@?0 ZXeIsOI݂3M޼zK?;0M+yuB$B@u2?yWt/_uQ@d{r! H}st°{fLJ)6rJlot+ "}p]kw{> gryp8ET݃5#gpE,$=եBôÎLSak8ːb/MU .94cinjn)- .!%sDB,Ͱ'_I8VwƤ,F]\>2}<4lX_=sm|欓Xs~W=t̬?L3k:M?!/^H( Q:837?Zw%Rcl|&=a3 ibӫ%^:\e\g4IH'fVNOSbqOz%P<6?K8:Cs:1 ;9O2k54lN-WuCƥ+zȳwHWm7MKv}khc)7>3Ϣab~?GEqhPa` ^$8ȏ{jSqhbJV }&]yCcȀ܊+ʭʟKHVcsgI1(9*Lg>ZPu+ECrQ0(iH( Յ4P<q,P:M* b y&XoNVx Eٴ`1K BݣOW*c skڵ]Y%J_  ^Tס8ZpR̼x}O*I^~Ӆ c]тGN^RNqT~xcc3OŠ][j=z9I lÉR- i1j\';ngeI;Vw.7@72y-ʠ ͯ"=0;aDnB3M]GjsV.LiOjTLʙ$l0-tP%[)^k8( SxҁEI?鏍0 -Ϛb_WdKlbM}R-m) S1(V eI"( UA_ eS`r%U E~B:` [b:h0QLiH`M̤p}"O 1? };Xfs ?z@Q?Ïs]K*gO r | mAG_'1 zrp?=Aנ4phaLV!0cM//l2(טn)"IBm22U4Uf~w} M,$cviVEb{ߏך(NV"PX1c keVJ4w(D@4f|GZ)3f EijTu }ݛEPN5 E!8RC5;_Y:c5^{'EzZ=8,Or_1 Vz~0;l@oj`_sKr~!؏Ap݁8v`m)qYٿOLމϏBesXj?LU=Q%%8Tx%R:LٔH2C%”:9Y PX-"$JU$˜Sb>y9KN-0pjbX/ÖaE UUK4A'N !/Wx0 E6wNN%q<=B$' 峮eĘ_ Xb[}Rl) [\޿nY_[j`/EdSNIpsj߻v|ˍN?|Z3Q l#3uF6W.%bI_m:b;^,[m\6u2"7af>EWE<8?C# [(+ J@ZFާ"jM:ը{iHJTM %K0`b %⺅Z(&8j?'JL>:ڰgD\I)X^(aN)z ?6btg>Xzw?珇g~!!F6yxA'+_|2U~ou];.wnH/E 22q*PAĪ@l޹^UX3y--l>~͏Oqgcr{Tۺ~JCE+Vscʦ/'s+,m5q {Z_৉ bKHyvaB &VppP .#(ѭpH遙B ]&ϵsQ!c3pElo9q6սG))¹]qO)y~޼r ;y Y6m^Ć)zݻ3=F3X(=nc6 i}} ɴ籵^;JW>>ǪB)l %EFN!ѿ+l6Iz䳽YOc)` rYϰbrpa>+ bR@֡lۧtkLR:Ыs0頋u8`qO޿qnϢ08 2$2|k8gy_ں]}N[w$h懟O@!X@G 0GW__$[[\B7J4T᭫RAv5; 9bM@n_9ߕN.YW,aJA-t84`"hO621{7-FVaE; # de/Ja]`BaD' q#Q:"M+:-B9BePqp0"eZGY,`^ YR\g}7Y_{_%IU=r̬ںޛAPdpA2.< ;7393s+ vS`r\yt)*"(k4%d't*ND R{ ʄt eN`40pH+xdG}5VSyIYF\pT^uQ }=94FѲ0fh8xL%![>W\Rwn9`IP_х6E)<3Fv]ݕ=hfjۇ?#՟ 9?y# k (pb T㹧pyED%_YEv% E ?;QM Tlz)G}j=y>X8K"⪇PmM%Fq[ZqЩ(HJ l*2 <RyEѝX2t%4:U֔<#U@9ed[(6t<ʿ dTAwoZ'5>@寖ҀMLCSgϪHwPvWL$v?Gy=|8rԒ=X&(絬|3>*U?}BTD,"ӊsq(&t~?oU$(2 1T*$#( Px"F5Ȏa;˭^¨ploYET?7-@B9"vfs-:iU|;kwTY5]͊},HLe'MԅE4۸7?]^X>qJ@S!b L_~i&ro8XXp^ڱ6<>0)ͬ?|yZ/_?P9Nqs] nNZy*RT(}?Wmk5#<8$7&qf,äfMS_Zۃj}7xUP?Q;"l` K`gv ylX2>}!C6,?&uO0+enymPuxC@@2aax߬:PxphUdt*:"vNjKEhZ@t7g5|ʹsTnL` _G0V=9_KԂgH"; / * |gݼpЫS9ܳwM1-C)lK!kس"g6 HYu݀jZ~/TV|j}ǻOy.UE\8(6҆llcd(LǁP*_68qYql`\0i4 s̶lDO9px\qL ܳw&c0 mEITGl5xM4O>0+S@F $ǤΧ9󖕧;ȅKgϡ/ⶕHXݚ9@m,\P%sԩs<Ȧ*Tt*‚<#PiWȘJ%쵕 jFݟ+rQW.oacW Y={F<KpJ^%q5ߞ}ѫ1j_? ؛Įl҉,<8&J^'M_}@M]{PW3!ug*M~l8AyՐ11ujMk׿ԦJx_OZB ܻo:d nY^ٟJv$̗NMg??#J$@1q.eqMᐠ{F: IH~][u7`wcW|&Q <kl>~Â'k~CuЮ$V8p(K:LۛbC,^]-揿 /NA}uG%AzPګuT^v \`R$Eko`C`PS,YHe[} IaYH֞*!@-ﮫ\?in.u`iHl?x6lu$W/d4_Ml'-?N %cA?^ $$n̯ӪEMm;7y HuKZqn|M}p(N@x<=4ƧK"J2OoZo?IJ/Ͽެ %@! zS׆ݹ:8{>c8T,768|ij J@S$@O ĮmuIF xH%lOC"=#7&1Z8K('7Bj wF#\7-8i M)r@y,}ItdtvJ'/͎G?2Q'`H.h9'?4IVT^J~_>'*?pK I6_6~#18Xp׮CB9x.C6s=9|~)+Cl} TaỦ}#ܖMq{JQ,Jv},0͓;@@@LQĽwt>M4}#lpET\;0^2$vfEV^3lIΠ|A"5jER0mxjx M`Su,Cn|NOO'/M},i̚(B:0l}Iz15 ~w>UX џpyW ţ9`GƝpd;10[iDTٷsr85 `;˩ NcB7 F/ixw jcɄ8=I @.DM6ry-~דDk<> iY;#(XVpmw+B Jߎgx)݄>_]MH]B-o_{vA-}~Os".NpI2U0$4, yOWuǎG_zez.}D''~~"Y;8Y KK8$kWnl=a;<<_ <ه9_YrE_W'kӭ>i?I `о:,D\V=6T!H&~=Ƨa0AwY C V85e!ð 4:Î|f_8[hHA9< `mK$XT 4F&s؛+"g3a!WxJ/:ˢ疖؏ox7S)#*v:F}? _vit8Yxݹ5Ͳ-)<76]4 = F 3a[!p "O+S@Y6t۩j ˭̻v}P:ĺ<,PIB(Y6~;6ci|%ۆݙ" VF nKft%l?Wf~q5v`21GɎEA^(w)bDtE/|sϊ.T`G?Ndz*j!QSM8VIX++2B"pi;&2)hQQMf90mtXN*϶fÁp\\G9;=A YBTD<8@޴1؛+b[IDɲY.="aQ<Όpa[kqW^y~ gO|bugu6l{/|@dː' ЙBB(?>w;ΏZ\m6'ؙ)x)ݬ8_2CyNEXK Z$!A"TñLYB01Q21\bѰL,Eӂf;"ĹYsx3 aAD@rJT 1IDDypef9[  h: Y0VU<=BԐ8kA\YpMW6?3^Ht.=Gu>-'I@)<^B.~;be˒M4ŋJFPpN  zt )݄XV"!. H"TȎ. / ~v`?uwegܫkd )¸f``Bkt r`pwYCE ջ2.hH7x2^^=|vjէ#"hGF~D@|69v_uvUmYꮗ@A˓Yl0T|[CUޞVIIVA,VxEcC9$d qYBHt5j2C*'ilWw\,mh(Y6򦍔n`J7LͰ{v""iLhUbcI业 @wPhFK%ߤqCOj{~: = o=|zGTէ%im7 $ Mb2qZYr%uv-1kZؕ)bg"FK3k$da9l&h v;WV9IEBTMLw.w9:,KYXp 7mz9=§Wu#{0iiPe&hȨ>̳Stߡsn^u'v3I~'"4 %?2 ,{6tC#2FϚM3SLS zcIP<>tnxk>41h;,bm45 DQV?<țo@RB}7|$T . ^-]>4d@B* T>[^Sׇ{BA_dB3'[HHIhIxhz"H*nrq6% ;T{@F+ iBJ%ر+?7<;o&vMיrV/I9<!&:KK"KlmK:#t`gFR\ǝf(# 5+U9]C-B R!e(mSyRL C~ |8GO_~r?sA|` R@k7%{/}gen5ޑ[$5;0kt\>4TJ/?K6 euZ޳< ֩ڳ=v v~#9 > h2M2 IL]p6i=Hr`@k%թDEoj#,0m}n=Pcj_k9"4!0m(C <dGA3קGXbؑr9X@nsM4h=Z_TS `҄VԒhDhmicZ,v3Ž5ZglK,YPK:4="A$_K2Q~g"~q~d[eV=g~ke@-窑E^ 0eȀYnSTz3z& b_ϚU ?ÒEGi Z e4[wFB>6pƦ9X`Uyk,ŏvd4 1!~P|0zv߰zfm"菌,Q2֤w/V*Wި5+k-"!#?7*~,~G] 8&̳sxRDfeSi*Տ؃4?EH LfI G]}epIENDB`thawab-4.1/thawab-data/themes/neo/static/img/mail.gif000066400000000000000000000011721305262755200225330ustar00rootroot00000000000000GIF89a!G,׀GF B;B $ F2;2F #)%2,2;,--F,,%,),FG0 %B /1#" ###15&(Bj )A @@KC/0t`3#0!Hȋ^j6 ,õϟ>wPxĒQ*;thawab-4.1/thawab-data/themes/neo/static/img/print.png000066400000000000000000000023451305262755200227670ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< tEXtTitlePrinter&tEXtAuthorLapo Calamandreiߑ*&IDATHݖkUmf2L2&6M4څR,;QE\nTɢjI6mmKڤ3osq3$؂vy>{L /CT,_yS4vttTmۅ£pבCCCÌ +9&&|8L`8ݕZ+ ?=NapObH44M8>864J*ҩRx\etwZkÕes5oV B幓]q,RR4w_58tgʖRb&"SSw0::ާdǎ88Te'&;S(%qZ5=iaƦ;!@0@g8RzgS!UUʮ=Xu݀o҂R*@)圛9Oҝ]$0>>N[,@>l5ssċ \4O[U\oDmO8V?OE6 ؽo߷U} ^ye~-e?tZydK?x/' JƲ,ogeVG/ݐmf]{9|0u'hD[ :!"(pVb$C2Z!$R*rDkWsf[-ґLHtlf~綿N:ǒf@H$t:(] RJ9{aBM. #   eKb-kZ{Y*]oM%yC\A4$Zmձx V~JIj^ gWT0M~? c~C7`Az AHD<!(aԊ.Z|9H@$@HL=C@%Bq4Sg5)T$Ϟ@ zәQ959_nA5R- K~YeVl+q[[A*&Xכ Jb.~x"Ƒ%WF[`ʙ5AptmkIչDzٴk۾;ݼ{ <ċ?<̛;=I;thawab-4.1/thawab-data/themes/neo/static/img/rpm.gif000066400000000000000000000021701305262755200224060ustar00rootroot00000000000000GIF89a!,%-&/'/(/(/)0)1)2*2+4+0*!1*!9/"70%91&:2&>5%92(G<*@8,E=/J?.H@2MA0MC5QD1QG6RG6YK7UK;YM:YO>^P8`S?bWDq`EtbErcKtfOkLyjR{mUmNw]wYyY}\}]^\ioacbdedgdefgnhkihmmeijjjkkkllmmnnoqqrtuvyrpqqsrssty{z{y{|rvtuuuvvwww{~x{xxyyzz{}||ŬgƭhƮlǯmvxxy|}}~~éŪɱqʲsɲx˴|©êªêŬŬ«¬ĬŭǮɮîƯĮȰŴŰưƱɲεζζϷҾӾȳʵ˵˶˶̷θϹҹҺӺջռл׾׾׿ؿѻҼҼӽҽҾԾտӾտ‘Ñ”˜ØžßğàġĠšŢ!, H*\H0^vٔڤS.e}C޷fja¤U/jl'5%bcQv zϛDRk- )d"(KZ]R\ GdAR†-'l\1K, š3wM0rB:$*:Fx3ez*VP[+qE(a}ج'7nlAJZt@ 24Qa5>`FP`b  :к,L8QP,Ti-VI'K JZRcLdI"@ԷNg T$$XG_f!_4AD С h"B;thawab-4.1/thawab-data/themes/neo/static/img/scrolldn.png000066400000000000000000000016731305262755200234560ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsaa0UtEXtSoftwarewww.inkscape.org<8IDATxՕ;lU}><ڎE $@G"F  I)B@JEMeY&IJG( ޝםsKӝ)s_0${D7,-,gc!v.dj/߮>Q uD)F’nWl  %2TTXkkl_ǫ?S]usXJEGD"7'D4-g{fۇH.!ul)Zxy;wüy}K7ndAByR`c p2a931y;5vUf~}C(ѣ$K<[rLYv2Ga_.ƪe AeGe:n3ZxP %d$ Xkw꾌g&f܅ G8k|,$`:.iܛ Qe*ƁLm~~3V1 ! ,qfбk\-c97dLkWKH3AlZZ& k/xO2ݾSswQH[DoL!50ElO3˿@/>ktٮg+SSG I] m4YW$oh1=;!t +߯i "'tu _t#~iǵD%$R R'cj[?ߊYhɊ&ډ^oo됉DPv\]WO"KO4NGf1_h7=:w_zl?NIENDB`thawab-4.1/thawab-data/themes/neo/static/img/scrollup.png000066400000000000000000000016131305262755200234730ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsaa0UtEXtSoftwarewww.inkscape.org<IDATxՕkeǿ}wfggN7%zHM,lP*ТZ/^)CxbuNJZۃÇ|xxZk"B 9'eFV߭˶|&>-ɏ4ܿ7 ڙކoS<'/'1gx (džǠ32ZyN|&1b;Zd DY .;"[~͏Jƛ;4q PP"?Rw*"3щ@Zhha=QZu%U93}FFG`he* \RDg.S<0:4zaD6N @[Ż6.Sub6Z~?{b4֠4}yWvUJKWwǶUA]=ٚwib>LA<e9>4~H\[KJ^U/VHǐ]Sm7Z[j+gЉ6}۾nwdn8#6: L~]]r JV@j 9m99dᛅYӚ5M7|7`IENDB`thawab-4.1/thawab-data/themes/neo/static/img/search.png000066400000000000000000000030301305262755200230700ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATHǵ{lEgfwvo\{ ~ Nv3a<[=q;/ݣ>zB`}c]`旹<Tw2]xgS;Jl 655fm왺 wرMmf#:rO(r[F[nX3PnnYJ Q֌4 -mht0X7*X:#a:y_dlY#-n,ȠuϛW,/ \ @e^<啕  xNF\Ӂi nb$mw@~ŧD>IbФaX+&;qz41$*%m(5 !Dm\N@jB,F rE, "Sîc,|,bdDIO4n2g S+a̪c`Qa1۲SjِEHUMNpYol-" uylʊT*U51N$P(y#tlJ CL 3雏u`bph;eg{9{/'+6ǖP6ω^VOM+jCJ"-OUFsO@\ \~(cGyKeW5Jh6N!\cZ_UbJ;ЩT(_LvYul]¶SI'"'A/dۤy5瑲ͻ/ TaWA͊ ]mA8 KK>V*WHwӺhCZKBH\m2抆t 9\[i[é$  kqgjfCEul}i>7-0Skg(czCNSŇk %h-_xhNWk +!\ LQF[)3PD3\^]i$hf(5^K95;`"e]A%E;4C!3Rg D%#ɩ ƃfuTx\B9[g2Q%`E4Ut`ƃӻ2, -=¶{"֓rJ^m̂Y"ڶJ3WTrZ- {7ǔ:I0{ݶF8:txeNiГp`h=gb"&ɇ` "(JKI߾-%/~b9cIiǟa<3ZÉM3̩X!RHfxo),ΕK_W2z] {<rVx1ݛ0~JV-mOuܘA~=Q(P Gƍ%tW_V*lˆ%mF#X*^;]Ct-Fz`gmFY{GEsMl21R"#5!r3߅ K6cVGPo܌٦(jatї `6|?Nۖhq)u"'r\t QI|+kn@'?yIh`WhTv' 9GyÜTbIIuppA%ۏjnT=N;8.]nYS{A \ @3.)^V GR(h[x pB@,H(Ļ)DE᝹>cei}z&őw>?'2J DD#Gf$ڵkr* 8s~cÇL&H,HNh<9Z[;0$` @OL&) YiT3kq$Jg:38 1BPzz̸?xoO1i ûwnZ^fd{8t}6vv=դ{w74.9b YʡuU"$^Њ vQpN>EfD_fhύ’Qe(u-d%ݸh1NsN(ee;Y grP,IENDB`thawab-4.1/thawab-data/themes/neo/static/img/tree.png000066400000000000000000000015701305262755200225710ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYsϐtIME5IDATxڝOheovI6Ic A*-x(JD&`AQWAh E*U@n667N؍a홗oB;ח?hRLӁr呉7f^{zD|p[iK1h6 Uk ENxI%ϷWZiubapCg[|t^ox ŠRN{~~Ů?XnU4U$)@}d/LR^f7, a' 9>'N'Ϡ5I%DYa0 W f"Pʠ"CA+MΒװV 1+؍# 8ܿ[#>|BJeYh!94$m {g?N~?Mf㠹vAM"e@XnhzkEs?vR04>M[lHr( @_:Wo| /@d{s>jHD2H'=t+Y;]r`wَĥO4ps;WϴʗALfWωO~Lm6>w˺m_/bt@ק/py[#-0:e܆M)joQq&B0Ylzĩ=B$ YAhSz{RvNt4oLl)70:rc tIENDB`thawab-4.1/thawab-data/themes/neo/static/img/up.gif000066400000000000000000000001261305262755200222330ustar00rootroot00000000000000GIF89a ! , - ւIYWSyI,o%P]x~xۭ;thawab-4.1/thawab-data/themes/neo/static/img/valid-css-blue.gif000066400000000000000000000033371305262755200244300ustar00rootroot00000000000000GIF89aX ! ####"'+$*.((('-1(.3+26.5:33318=;;;5=B8@FfC2׽*)6Qѥ j*Pb 5L+Dx{Z5snAs=V6(Q"@haެ!ˆ3oLPÕ'YE.&Hj*@*hvJRLwR :p[p-NmoF\n(i+ a.5a9Zg v$iPX+io\Hm@FH9`EwE~F[@E'Om@ TÒS\$o.QS~e * gDEŽEm AH*D ]tD1b\|T56؇Hz(摪S6Ô+ ԺqEu@ ípXyr @fJ'j`uU^Ho-FK BDEy Ұlnqg!xo0BPp_y>u^GN6St{1;H+Inl7kaV@AR\oES/S_}EMpk0@ @/6 ;thawab-4.1/thawab-data/themes/neo/static/img/valid-xhtml10-blue.gif000066400000000000000000000040211305262755200251240ustar00rootroot00000000000000GIF89aX ! #### %)#)-(((&,0)/4*15-49333;;;6>D8AG;DKKKLAKROOPFPXJU]TUU[[[D]oLX`P\eY^bUbkYfp[ireeelll`nxcr}vwz{{{Z_[ ` behkm2m!o%r)u1y9t5|8~gwiym~y}8 *Tr$ .@T`5LM*,Yd?bUd8`6 >  G@L'= dbC7vJ OfvDogmKSlf5iMgzĀ7y 39L T% ;thawab-4.1/thawab-data/themes/neo/static/main.css000066400000000000000000000240541305262755200220100ustar00rootroot00000000000000.clear {clear:both; padding-bottom:2px;} a img { border: none; } a[href] { text-decoration: none; color:#150;} a[href]:hover { text-decoration: underline; } a[target] { background: transparent url('img/external.gif') top left no-repeat; padding-left:12px;} body { font-family: "amiri", "Liberation Sans", "KacstOne", "Simplified Naskh", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "sans", "Sans"; background:#ccc; margin:0;padding:0;border:0; } h1, h2{ margin:0; padding:0; color:#303030; } .clearer { display: block;height: 12px; } #async_tips_div { display:none; position: absolute; background-color:#ffe; left:20%; margin:0; padding:3px 10px; border:2px solid #aa7; border:2px solid rgba(170, 170, 119, 0.6); } .match { background-color:#acf; } .term0 { background-color:#acf; } .term1 { background-color:#fca; } .term2 { background-color:#cfa; } .term3 { background-color:#fac; } .term4 { background-color:#afc; } .term5 { background-color:#caf; } .quran { font-family: "amiri-quran", "amiri", "Simplified Naskh", "me_quran", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "Serif"; font-size: 150%; color: #240; background: linear-gradient(#fffffc,#ffc); padding: 4px; border: 1px solid #ff0; border-radius: 4px; box-shadow: 0px 0px 10px -4px #ff0; } blockquote { border:3px dotted #aaa; background:#ddd url('img/quote.gif') top left no-repeat; background-color:rgba(190, 230, 190, 0.4); padding:5px 50px; } input.search_input { font-family: "amiri", "Simplified Naskh", "me_quran", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "Serif"; padding: 3px 30px 3px 3px; border-radius: 0px 5px 5px 0px; font-size: 12pt; color: rgb(0, 0, 0); border: 1px solid #56C016; background: #fff url("img/search.png") no-repeat right 3px; width: 576px; box-shadow: 0px -3px 1px 0px #d1d1d1 inset; transition: all 400ms ease-in-out; float: right; margin-top: 2px; } input:focus.search_input, input:hover.search_input { border: 1px solid #56C016; } input.submit_button { float: left; height: 36px; border: 1px solid rgb(86, 192, 22); background: linear-gradient(#80f838,#64e318); margin: 2px -1px 0px 0px; width: 56px; border-radius: 5px 0px 0px 5px; color:#fff; text-shadow: 0px 0px 3px #255D04; font-size: 14px; } #loading { display:none; background:transparent url('img/loading.gif') center center no-repeat; position:absolute; top:16px; left:8px; margin:20px auto; width:32px; height:32px; } #logo { background:transparent; position:absolute; top:8px; right:8px; height:48px; font-size:24px; line-height: 100%; text-decoration: none; vertical-align:middle; margin:0;padding:0;border:0; } #logo img {border:0; vertical-align: middle;} #results { background: #A3F96F; color: #fff; overflow: hidden; position: fixed; display:none; top: 87px; left: 37px; width: 500px; height: 300px; margin: 0; padding: 0; border: 1px solid #56C016; border-top: 0; z-index: 1; } #nominisearch { display:none; text-align:right; width:200px; margin:0;padding:0; } #minisearch { background: linear-gradient(#9cfb63e6,#64e318e6); color: #fff; overflow: hidden; position: fixed; top: 50px; left: 48px; width: 250px; height: 32px; margin: 0; padding: 0; border: 1px solid #56C016; z-index: 1; border-radius: 2px; box-shadow: 0px 0px 10px -5px #170; } #minisearch form { margin:0;border:0;padding:0; } #minisearch input { background: #fff6; width: 244px; height: 26px; border: 1px solid #ffffff4d; margin: 1px; border-radius: 2px; } #searchHelpDiv { display:none;} #searchHelp { background:#ffffa0; padding:16px; -webkit-border-radius:8px; -moz-border-radius:8px; border-radius:8px; } #searchHelpArrow { background:transparent url('img/up.gif') right top no-repeat; margin:0 24px; height:16px; width:32px; } #container { background-image: radial-gradient(circle 1000px at top,#fff 0%,#ccc 40%); min-height: 100vh; } #footer { background: linear-gradient(#eee,#ddd); color: #111; margin: 36px; text-align: left; height: 35px; text-shadow: 1px 1px 0px #ccc; padding: 6px; border: 1px solid #555; border-radius: 4px; box-shadow: 0px 0px 6px -1px #666; } #footer img {border:0;} #contentheaderHome { text-align: center; padding: 32px; } #contentheader { text-align: right; position: fixed; top: 0px; left: 24px; right: 24px; background: linear-gradient(#9cfb63,#64e318); border: 1px solid #56c016; border-top: none; border-radius: 0 0 4px 4px; color: #fff; text-shadow: 0px 0px 3px #255D04; padding: 4px 12px; font-size: 20px; max-height: 37px; overflow-x: auto; overflow-y: hidden; } #contentheader a, #toolbar a.button { color: #fff; text-shadow: 0px 0px 3px #255D04; background: linear-gradient(rgba(255, 255, 255, 0.4),#64e318); border: 1px solid #64e318; border-radius: 5px; padding: 0 10px; box-shadow: 0px 0px 5px -3px #150; display:inline-block; white-space: nowrap; vertical-align: top; } #contentheader a:hover, #toolbar a.button:hover{ text-decoration: none; border: 1px solid rgba(17, 85, 0, 0.33); box-shadow: 0px 0px 3px -1px #150; } #contentheader a:active, #toolbar a.button:active{ background: linear-gradient(#64e318,rgba(255, 255, 255, 0.4)); } #contentheader a.home{ } #contentheaderHome .logo { width: 256px; height: 256px; cursor: pointer; } #toolbar { position: fixed; top: 46px; right: 35px; left: 35px; border: 1px solid #56c016; border-top: 0; background: linear-gradient(rgba(156, 251, 99, 0.9),rgba(100, 227, 24, 0.93)); border-bottom: none; padding: 5px 3px; } a.inactive { filter: grayscale(100%); opacity: 0.5; cursor: not-allowed; } #toolbar a.button { margin-left: 10px; border-radius: 2.5px; box-shadow: 0px 0px 2.5px -1.5px #150; padding: 2px 10px; } #contentbodyHome { margin:0;padding:0 16px;border:0; } #contentbody { background: #fff; margin: 74px 35px; border: 1px solid #56c016; border-radius: 0 0 4px 4px; padding: 0 12px; min-height: 100vh; box-shadow: 0 0px 30px -10px #150; } /* nav */ .navtoolbar { overflow:hidden; width:100%;height:32px; background:#acf; background:rgba(240,240,255,0.4); z-index:10; } .navtoolbar span, #searchbar span, #tailnav span{ float:right; } .navtoolbar span.otherside, #searchbar span.otherside, #tailnav span.otherside{ float:left; } .navtoolbar a, #tailnav a { padding-right:24px;padding-left:16px; } .navtoolbar a.prevLink, #tailnav a.prevLink { background:url('img/go-prev.gif') center right no-repeat; } .navtoolbar a.upLink, #tailnav a.upLink{ background:url('img/go-up.gif') center right no-repeat; } .navtoolbar a.homeLink, #tailnav a.homeLink{ background:url('img/go-home.gif') center right no-repeat; } .navtoolbar a.nextLink, #tailnav a.nextLink{ background:url('img/go-next.gif') center left no-repeat; padding-left:24px;padding-right:16px; } #overlay { display: none; overflow:hidden; position: absolute; left:0; top:0; z-index:1000; background:#fff; } .Box { border-radius: 5px; margin:20px; background-color:#eef7ee; position: relative; box-shadow: 0px 0px 10px -4px #150; } .Box h2 { margin: 0; background: linear-gradient(#80f838,#64e318); color: #fff; text-shadow: 0px 0px 3px #255D04; padding: 0 12px; border: 1px solid rgb(86, 192, 22); border-radius: 5px 5px 0px 0px; } .Box div.info { position: absolute; top: 10px; left: 10px; color: #150; opacity: 0.5; } .Box div.info:hover { opacity:1; } #kutubListing input { top: 10px; left: 10px; position: absolute; } #kutubListing ul { display:inline; } #kutubListing ul li { list-style: none; } #kutubListing ul li a { display:inline; font-size:18px; display:block; float:right; margin:18px 16px; padding-top:64px; width:130px; height:50px; text-align:center; background: url('img/book.png') no-repeat center 8px,radial-gradient(#eef7ee,#e3f9cd); border: 1px solid #e3f9cd; border-radius: 8px; box-shadow: 0px 0px 5px -2px #657556; transition: all 400ms ease-in-out; } #kutubListing ul li a:hover{ background: url('img/book.png') no-repeat center 8px,radial-gradient(#eef7ee,#bcdb9d); border: 1px solid #bcdb9d; text-decoration: none; } table,th,td { border:none; border-collapse:collapse; padding:6px; } table { width: 96%; background: #666; border-radius: 6px; box-shadow: 1px 1px 10px rgba(0,0,0,0.3); } thead, tfoot {color:white; } tbody td, tbody th{ background-color:#ddd; } tbody th { color:#222;} tbody td{border:1px solid #000;} tbody td:first-child{border-right:none;} tbody td:last-child{border-left:none;} tbody tr:nth-child(odd) td { background-color:#eee; color:#222} tfoot td, tfoot th {border:none; font-size:130%} #SearchContainer_tab{ overflow-y: scroll; height: 79vh; direction: rtl; } .searchItem { display: block; width: auto; border-radius: 3px; background: url("img/text.png") no-repeat scroll right 10px, #fff linear-gradient(#eef7ee,#e3f9cd) !important; border: 1px solid #e3f9cd; margin: 14px 10px 12px 8px; box-shadow: 0px 0px 5px -2px #657556; padding-right: 48px; transition: all 400ms ease-in-out; } .searchItem h2{ background: transparent none repeat scroll 0% 0%; border: none; color: #150; text-shadow: 2px 1px 1px #ccc; font-weight: normal; padding-top: 1px; } .searchItem span{ color: #950000; } .searchItem:hover { background: url("img/text.png") no-repeat scroll right 10px, #fff linear-gradient(#eef7ee,#bcdb9d) !important; border: 1px solid #bcdb9d; text-decoration: none !important; } .searchItem:active { background: url("img/text.png") no-repeat scroll right 10px, #fff linear-gradient(#bcdb9d,#eef7ee) !important; border: 1px solid #bcdb9d; text-decoration: none !important; } #SearchPages span { display:inline; float:right; margin:0px; text-align:center; background: #def; color:#777; border:2px solid #acd; width:42px; cursor:pointer; } #SearchPages a{ padding:0 0.5em; } #SearchPages a.current{ background: #acf; color:#000; } .mini table, .mini th, .mini td { padding:0; } .mini tbody a{ display:block; padding:0 5px; white-space:nowrap; width:380px; overflow:hidden; } .mini #SearchPages span { font-size:80%; } .mini #SearchPages a{ padding:0 0.2em; } #SearchContainer.mini { padding:36px 0 0 0; overflow:hidden; } #rollup { background: transparent url('img/close.gif') 0 0 no-repeat; position:absolute; width:22px; height:22px; left:0; bottom:0; } thawab-4.1/thawab-data/themes/neo/static/main.js000066400000000000000000000126741305262755200216410ustar00rootroot00000000000000/** * * copyright © 2010 ojuba.org, Muayyad Saleh Alsadi * **/ var klass="class"; var animations={}, ani_c=0, init_ls=[]; var autoscroll_dir=0, autoscroll_px=5; var overlay_d; function text_keyup(e, evt, func, bool) { var charCode = (evt.which) ? evt.which : event.keyCode if (charCode == 27) { // catch ESC key and clear input e.value = ""; if (bool == true) { func("", true); }else{ func(""); } return false; } } // fake trim for IE if (!Boolean(String.prototype.trim)) { String.prototype.trim = function() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); }; } function get_url_vars() { var vars = {}, i,j, e; var s= document.location.search; if (!s || s.length[0]==0 || s[0]!="?" ) return vars; var a = s.slice(1).split('&'); for(i = 0; i < a.length; ++i) { e = a[i].split("=",2); if (e && e.length==2) vars[decodeURI(e[0])] = decodeURI(e[1]); } return vars; } function animation_loop() { var i,a,fn,r; for (i in animations) { a=animations[i]; fn=a[0]; r=fn(a.slice(1)); if (r==false) delete animations[i]; } setTimeout(animation_loop, 100); } function slide_down_cb(args) { var o=args[0], h=args[1], px=args[2], cb=args[3]; var t=o.offsetHeight+px; if (t>=h) t=h; o.style.height=t+"px"; if (t==h) { --ani_c; if (cb) cb(); return false; } return true } function slide_down(o, h, px, cb) { var d; if (!h || h<0) h=o.offsetHeight; if (!px || px<0) px=h/5.0; if (px<1.0) px=1; d=o.style.display; o.style.display="none"; o.style.height=px+"px"; o.style.display=d; animations["_"+(++ani_c)]=[slide_down_cb, o, h, px, cb]; } function get_scroll_width() { var w = window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft; return w ? w : 0; } function get_scroll_height() { var h = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop; return h ? h : 0; } function autoscroll_up_cb() { if (autoscroll_dir!=-1) return true; var h=get_scroll_height(),t=h-autoscroll_px; if (t<=0) {t=0; autoscroll_dir=0;} window.scroll(0,t); return true } function autoscroll_down_cb() { if (autoscroll_dir!=1) return true; var h=get_scroll_height(),t=h+autoscroll_px, hm=document.body.scrollHeight; if (t>=hm) {t=hm; autoscroll_dir=0;} window.scroll(0,t); return true } function import_script(url){ var t = document.createElement("script"); t.type="text/javascript"; t.src = url; document.body.appendChild(t); } function overlay_init() { var d = document.createElement("div"); d.id="overlay"; d.style.width=document.documentElement.scrollWidth+"px"; d.style.height=document.documentElement.scrollHeight+"px"; document.body.appendChild(d); overlay_d=d; window.onresize = resize_cb; } function resize_cb() { overlay_d.style.width=document.documentElement.scrollWidth+"px";; overlay_d.style.height=document.documentElement.scrollHeight+"px"; } function getAjax(url, q, success, failure) { if (window.XMLHttpRequest){ // code for standard browsers Firefox, Chrome, Opera, Safari, and even IE7+ xmlhttp=new XMLHttpRequest(); } else { // code for IE6, IE5 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange=function() { if(xmlhttp.readyState==4) { if (xmlhttp.status==200) success(xmlhttp.responseText); else if (failure) failure(); } } s=""; for (var i in q) { s+="&"+i+"="+encodeURIComponent(q[i]); } s=url+"?"+s.slice(1); xmlhttp.open("GET",s,true); xmlhttp.send(null); } var needs_external_json=false; var fromJson = function(t) { return eval("("+t+")"); } function getJson(url, q, success, failure) { s=function(t){return success(fromJson(t));}; getAjax(url, q, s, failure); } function html_escape(s) { return s.replace("&","&").replace("<","<").replace(">",">"); } function re_escape(s) { return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1') } function search_entry_focus(e) { e.setAttribute(klass, "search_active"+( e.getAttribute(klass) || "" ).replace("search_active","").replace("search_inactive","")); if (e.value == "نص البحث") e.value = ""; } function search_entry_blur(e) { e.setAttribute(klass, "search_inactive"+( e.getAttribute(klass) || "" ).replace("search_active","").replace("search_inactive","")); if (e.value == "") e.value = "نص البحث"; } function rm_class(e,c) { e.setAttribute(klass, ( e.getAttribute(klass) || "" ).replace(c,"")); return false; } function init_get_by_class() { if (document.getElementsByClassName == undefined) { document.getElementsByClassName = function(className) { var hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)"); var allElements = document.getElementsByTagName("*"); var results = []; var element; for (var i = 0; (element = allElements[i]) != null; i++) { var elementClass = element.className; if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass)) results.push(element); } return results; } } } function init() { try { if (JSON) { var t=JSON.parse('"t"'); fromJson = function(t) { return JSON.parse(t); } } else needs_external_json=true; } catch(e) { needs_external_json=true; } init_get_by_class(); if (document.body.getAttribute(klass)!='body') { klass="className"; /* hack for ie */ } setTimeout(animation_loop, 100); animations["_s_up"]=[autoscroll_up_cb]; animations["_s_dn"]=[autoscroll_down_cb]; overlay_init(); var i; for (i in init_ls) init_ls[i](); } window.onload = init; thawab-4.1/thawab-data/themes/neo/static/manual/000077500000000000000000000000001305262755200216225ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/neo/static/manual/all.css000066400000000000000000000057571305262755200231220ustar00rootroot00000000000000.clear {clear:both; padding-bottom:2px;} pre, code { direction: ltr;} a img { border: none; } a[href] { text-decoration: none; color:#150;} a[href]:hover { text-decoration: underline; } a[target] { background: transparent url('../img/external.gif') top left no-repeat; padding: 0 0 0 12px;} body { font-family: "amiri", "Liberation Sans", "KacstOne", "Simplified Naskh", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "sans", "Sans"; background:#999; margin:0;padding:3em;border:0; } .export { background:#fff; margin:0;padding:3em; border: #aaa solid 4px; } h1, h2, h3, h4 { font-family: "amiri", "Liberation Sans", "KacstOne", "Simplified Naskh", "KFGQPC Uthman Taha Naskh", "ArabeyesQr", "Times New Roman", "sans", "Sans", sans-serif; color: #437f22; font-weight: bold; } div.toc { width:30%; float:left; background: #cda; padding:20px;margin:10px; border: #999 solid 2px; } div.footnotes { margin: 0.5em -2em 0; padding: 1em 4em 0; border-top: 2px dotted #CCC } pre { background: #cda; border: #cdc solid 2px; margin: 2em; padding: 0.5em; } table { margin: 2em; padding:0; border-collapse:collapse; } tr { padding:0; margin:0; border:0;} td,th { border: 2px solid #999; padding:2px 10px; margin:0; } th { background: #cda; } /* syntax highlighting code */ .code .br0 { color: #66cc66; } .code .co1 { color: #808080; font-style: italic; } .code .co2 { color: #808080; font-style: italic; } .code .co3 { color: #808080; } .code .coMULTI { color: #808080; font-style: italic; } .code .es0 { color: #000099; font-weight: bold; } .code .kw1 { color: #b1b100; } .code .kw2 { color: #000000; font-weight: bold; } .code .kw3 { color: #000066; } .code .kw4 { color: #993333; } .code .kw5 { color: #0000ff; } .code .me1 { color: #006600; } .code .me2 { color: #006600; } .code .nu0 { color: #cc66cc; } .code .re0 { color: #0000ff; } .code .re1 { color: #0000ff; } .code .re2 { color: #0000ff; } .code .re3 { color:#ff3333; font-weight:bold; } .code .re4 { color: #009999; } .code .st0 { color: #ff0000; } .code .sy0 { color: #66cc66; } /* notes */ .noteclassic, .noteimportant, .notewarning, .notetip { margin: 2em; margin-left: auto; margin-right: auto; width: 70% !important; min-height: 40px; clear: both; text-align: justify; vertical-align: middle; border-collapse: collapse; border: 2px solid #999; padding: 15px 60px 15px 20px; background-position: right 50%; background-repeat: no-repeat; -moz-border-radius: 20px; -khtml-border-radius: 20px; border-radius: 20px; } .noteclassic { /*border: 1px solid #99D;*/ background-color: #eef; background-image: url(images/note.png); } .noteimportant { /*border: 1px solid #ff0;*/ background-color: #ffc; background-image: url(images/important.png); } .notewarning { /*border: 1px solid #d99;*/ background-color: #fdd; background-image: url(images/warning.png); } .notetip { /*border: 1px solid #9d9;*/ background-color: #dfd; background-image: url(images/tip.png); } thawab-4.1/thawab-data/themes/neo/static/manual/images/000077500000000000000000000000001305262755200230675ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/neo/static/manual/images/important.png000066400000000000000000000043121305262755200256120ustar00rootroot00000000000000PNG  IHDR00WbKGDIDATx{W?. [X Ei*HK#&ElJFc#hLFG(j)j-+/AX](>fgv޿73;3+TIn~{{=;|(4ik=o |t֣H2pi1Vb'SNu,ߌi(8?//V0~N>..kK~dmd]HE )%"i,)n^?$TȾm[r8$P.B^Kc8np%ٞ@xf9aRGiN }#Ԩ0H[0}p9.j7n`CtLGTF!\AHLX5b I+qvNA|i!Of:" f%Xne"1M(\o;U*k)6ncaq9ԭ|݆b&Z_bUY_g_k]<:SQNGɲYQaFSS]# 6c֯Α3!BO )sP_W Dirh6@B Q95W/@kGm!UDR";g=cq=}AoΟLAN)E2LCcd/Vtr 4mM<dh{{7J"?V &:-Z}0عN~otz_ePsWVI|vy#įRNd&`u&|~JV)ow> 9tx,KGAZ,jQ-=ǥa꣗]5 Ze[A+>S]9_D?s9*t"MMۋhwk׵!R![15GjAV*|VX_@΀܉ UZ:ip/g8ޕUN ]ih=WO7d}Ϋ!OA:RzYL摮>Q%)@Pә/OlZk.{xhm-!ܿ($g0݋t8;W/kA&)g\_SY @ijN Q!WW*ĸVڥu5@Řι&X,t:Y|$S38ӓlƲ.ñ4%7.|9abnide. W{WN\ѧޢ7@8U6!!ʇװWњfbJcu Wz_;&H\ìk!"r,p͜VQE.%[+'-*F#1H:*s,η*sb7:X2zd6UG}tgqx\ZGJI*--KB \U0jRVmU&s X̞gJ]Eix%Ns .S㏝K*@L>ETq`3\'>O mAv8]Y胖; l@Z.EPVpQ!X[n0p8nf3d# HdªFfT`*gjn-&s*'QC6"1`HI[D:nO2۲W8Lx,L 2q>ycv2.m3u^5Tp)iPc6}XKWH83Q*3ŭ8W߰lcF2*W 6m2h)U8pN[Rc< ?-%5$o<;"VIENDB`thawab-4.1/thawab-data/themes/neo/static/manual/images/note.png000066400000000000000000000047301305262755200245460ustar00rootroot00000000000000PNG  IHDR00WbKGD pHYs  ~tIME :9@>tEXtCommentCreated with The GIMP (c) 2003 Jakub 'jimmac' Steiner'3X IDATxYWsU3,' fqv㐘(X@x@""@gx`y@B!   %$, Kl !=]uU=3 v!#u6r^hhhUF rU>Fj(NO297͗Fk-/| Ghgmn\6Wvċ @w-yW>~"`G_m/@IYšv,ĿMD03cz@NqAQp^ѵk1 ȟt"=I)ޗ&yNbzzZ@D8}1t{99qz_ ==5~r z{jdY"R#KYN3u:7t/>|=AG6P \W3Sg>~>C{k,T1WhhH(`iڡyѕ(ݫr\M1tp ;9X'O%Z.1+71`8LE$ t)P(@ġ>チw=zf?+Sٸ:p6Lє"*_Ʋ8M JZ4oⱩY߿z_^l̄=8Ϋ723Wly[(4 XP @@xOp޸|S<̾?ayu3[fxvMH-hRn*:JJ!рHH3^in_ޣ1S\P(`|E3l1cu shBBjGؽ2^y-yn\K׀3]@NHp΁8+)|Q(Nz-chpyQj8J N$ j@k5Ay Vv'$i(Rss$TI E'F%/S[Vi<0;;"8'$> (A4"O4j.& %MJQ1?C(RABHNRb 18uhZƍ:Sl߶ :0i^u KQ ?ꀪ"Zf xwܱy?uFh疡~^~| 7_Wu<Nhpα !/:  8PZ^Y؃y6@?1l#Dլ:27枟?>~X\~&F֬ G|ʾsD),aVfG6X72PNYU]Ȳ^PB=7_* ){ `]/Y3P_awClYYmIQŠ6ĢQ#9Rίmfff6 3B)Kr-Rt@NUfDU͐ /X?Rc("FbNU|߫F ;יaf$Iҝ,tml޵033GJ:ifK,bCFQiR[ F+SA\6#J `vv+4jZs'0=:AxbL8c1Ӡ k|ňڑ =!"vQ=jm̌gƏo~ MS ~{#:c'NEnYk97_^wúl3zV4X?2̦sŖKlq?2>щ<ã㌎$i~x1PyL4Ml6gggOEQ@R=]FZ5!939UeÆ.2;T׷*ggӍ%;lň]X/?̗Q*ݲ{y9/弜t0 ?IENDB`thawab-4.1/thawab-data/themes/neo/static/manual/images/tip.png000066400000000000000000000055351305262755200244010ustar00rootroot00000000000000PNG  IHDR00WbKGD IDATx͚yluμGR4)ы6,9*R-P*l,+i9AiQw Wiq Fn\^buE[ɒBUU17q7s9c#1e;lwhXaŠ ܆+\g-EX ( j:p5`fVX!Y mnݷ{w6omͺu;{mŤi >}C^W@@"'r`>~ "(*x8q_?s@qՒ2Сy__uNr/lWyUz"zy柿mRR*EJ(XkQD29``ͷnػNMMN w ؐ]ۻwߟ=~juQ[8΀fsUIhEv/j]~lo YJ*!sOlpSdž8QUS㜠\xQ+> ru߇ &! {ol۶ZxK~4rQ5(wr<Qjb^W=ΝQd(EU}edd]H=4jSBZ-Q[9^&50|^~."KI$E"f>go60f& I ϟ:y),)T{s-i;O/IB;uIf}o2c3;.ZRrQhx'UO*)w39~Ki\ {RiFhf ?^ eN@|駞{XeRMIq)mh;G4ɋlE,`EkG2F SXxӏ?4<2DI% #!E}T"Aw3f>0 C tN|hr!h#ǧ2QISng/`Ȫ`K7N ><4-GL/p_/g$S't{O ^p"Icyw3NUH6B*u6md]ЈwM ^-` hY"8w_|oZ&#gH}BwjD\2KJdR)”'#^s:"M`0XvE%^|޽ѯg7]`Q%(gM6ej<_k_3\@k_"q `HS%2-"ڈՍO9vy> _ ;r#uh\viō poB{&DYE}Xk R28CN%h"P kQ+bmȷSP~dL c $fvMl9&jtc_nyꗸ1f*$C\fc6h5W\m[zHq_*5Oyܰi ׾HC\B-?uxz_D+625z Q-|{V}VB$TkmZm*ӃLk j4fh* ı~ܹ]9z_jSu?VoE5Aõh FM'jKrʭL7mb'5  I;9O2:Qlʡ]/ PȵzWjҬѨMnHRG;qiAz$zM7_S`g] B2K8%*L a|L/i4?:rzSUQ٪#M*xTKP[W.~]~螮U+{pITk;VkmɊ<+@gM+`wq?l*^3::::ah-Q LB<7l_z8ccc9|8Yhf|<WadIit9\9䖿7Zyt9r7ˉ_mrYx/ڥ>1WIENDB`thawab-4.1/thawab-data/themes/neo/static/manual/images/warning.png000066400000000000000000000062611305262755200252470ustar00rootroot00000000000000PNG  IHDR00WbKGD fIDATx՚{pUU$ Q4t0m(Z!)-i3ִ `V%GA@`(MmHDBHBnrs9??Vdٲedeem~Wꍄ7xwΝ MMMލx5߉Xi"dgge˖ɋ/V ˀPXf&ػw;v|cc#~;6t wwIZˉScRPP09''YTTt.xGcǎQ]]EY>aU0 !=3ܹsٺuO.]_|QD4eݿ,..޶m۶`+WPy ڵ Up)3ӧ1dƬ\n:e…=F@9r/BYQQ!['?e|{JXsQ^AN9r@;f4MScϗo?EKlgm߼ysܴGanOE1љ's}ئ0Qr+[_-c|<ΰYb3ev5%: > R[[KաChaɋNw׽ :I\ZI?J77FY7-=i2l,^y)-rPx[l|ee%}d8[x",Pl !LlKG nUub-=y2:ƍ|vmZTM6qرnRT1cD7 ;|_~~ N$E }W1>H B* i}|S~=yWzi T|fML;tPRRRF^5:,ˢ{iOJ F )BHawKaH!@8=)ᄿf=?<2AoWN''|wؖmY Hm[ٹg~8qzO@ O0*~\.W<*Ѻ]!c*^Im l!m+/^(Eahۚi5$x'g%@2|8'vՉ-T,D5A&{OԞז a@X&O*#n!ԁp8U]6u\(((JCIE іՙ@tLY;%#4 iCF1b*t FiiBSs3D R {vIo`!oȵfʏtPZ;KÅPUÃoq!vO4=a`SsӉLf,ZBkvD'%JlFPܨ@\.z:-Q{=DZ jO*& !_ #|?T+7|M `EAA)QvHKTkVhl|f9u5! i(/H `zN:BH) Iz(Sד P%FFۇMi[ \ 9gf8[ov_3v?RJ2e0t ] {!vQU5DwnzNkl+б4JGW'ЙgȜ934` TA( *t==p8T&h1zO|F"1 =[ zVSL+( sƌGg1}X6:z);TGJl+DH4l3w1wvԸc]]k;yN3f ř˹2\ o ]}JZ nwEPm0е zOcc-Ql};a\qYF;IK0RbFsEA%K._VV… cփ%u4Zϟ:ba~84 0jHmrk &ښv}ޡjѝ~c1g߾૪7oNZ uIfggjnMM %>Kmqqܚz¾jТFx@ƌqFJdggSRR*pT+B*pO@\3gl4Dee媬, @ 1i)-7|Y /]3gάOt:AիN'--JJh,g?cAXי6 DLx'e(2]׻.ߥAY\\,'M$`BO[ʰ:?䓉RS\FtK8јg/R^^I.uu}mu]}ÇOiiiƙD*uEb].yɜ9sn xEQ(,,ߦ4iȿt~k4{2ǃ<}Z} $1 H< دليل استخدام ثواب

    دليل استخدام مكتبة ثواب

    البحث

    لمحة

    تستخدم مكتبة ثواب فهرسًا لتسريع عملية البحث. الكتب غير المضافة للفهرس لا تظهر في نتائج البحث.

    عندما تفتح كتابًا ما فإن صندوق البحث الصغير في صفحة الكتاب يبحث داخل ذاك الكتاب فقط، أما الصفحة الأولى فتبحث في كل الكتب ما لم تحدّد لها مجال البحث.

    في الغالب لا تحتاج تحديد مجال البحث حيث يتم البحث في كل الكتب خلال ثانية أو أقل ولا يزيد الوقت اللازم للبحث كلما زادت حجم المكتبة كماً، والنتائج تكون مرتبة بحسب ارتباط النتيجة بالشيء الذي تبحث عنه (مثلا يعطى ورود الكلمة في العنوان وزنا أعلى من ورودها في المتن)

    يتأخر البحث عند البحث عن أمور عامة تُخرِج الكثير من النتائج (في الزمن اللازم لتقييمها وترتيبها). حاليا المكتبة تظهر أوّل 500 نتيجة فقط (يمكن إعادة ضبط هذا الرقم إن احتجنا لذلك).

    لتسريع البحث ابحث عن شيء مميز. مثلا: لا تبحث عن كلمات مثل حرف “في” بل ابحث عن كلمة مميزة.

    البحث الموجود على الصفحة الرئيسة يقوم بالبحث في كل الكتب المفهرسة ما لم تقم بحصر مجال البحث بشكل صريح.

    لا يتم البحث في الكتب غير المفهرسة.

    عندما تفتح كتابًا فإنك تجد صندوق بحث مصغر في رأس الصفحة وبعكس صندوق البحث الرئيس لا يبحث هذا الصندوق إلا في الكتاب الحالي. الكلمات التي تكتبها فيه يتم تظليلها في الصفحة الحالية فور كتابتها ويتم النزول إليها لإظهارها عند النقر على زر الإدخال أي أن الإدخال له فائدتان: البحث في الصفحة الحالية والبحث في كل الكتاب الحالي.

    الملخص

    الرمز اسم العملية شرح مثال معنى المثال
    & و تشترط تحقق ما قبلها وما بعدها معا كتاب & قلم أن يحتوي النص كلمتي “كتاب” و “قلم”
    &~ وربما تشترط تحقق ما قبلها ويفضل أن يتحقق ما بعدها دون اشتراط ذلك كتاب &~ قلم أن يحتوي النص كلمة “كتاب” ويفضل أن يحتوي كلمة “قلم”
    | أو تطابق سواء تحقق ما قبلها أو تحقق ما بعدها عيسى | المسيح أن يحتوي النص كلمة “عيسى” أو كلمة “المسيح”
    ! النفي تطابق إذا لم يتحقق ما بعدها عيسى | المسيح ! الدجال أن يحتوي النص كلمة “عيسى” أو كلمة “المسيح” لكن بشرط أن لا يحتوي كلمة “الدجال”
    * أي شيء صفر أو أكثر من الحروف استع* يمكن أن تطابق ما يبدأ ب استع مثل استعان أو استعمل …إلخ
    ? حرف واحد حرف واحد تماما ش؟ب يمكن أن تطابق شرب أو شنب …إلخ
    ^ قوة زيادة وزن الكلمة عند ترتيب النتائج أحمد ابن تيمية^3 إعطاء كلمة تيمية 3 أضعاف وزن الأخرى عند الترتيب
    كتاب: تحديد المجال البحث في الكتاب المذكور تاليا التيمم كتاب:(“صحيح البخاري” | “صحيح مسلم”) البحث عن التيمم في صحيح البخاري أو صحيح مسلم
    عنوان: البحث في العناوين حصر البحث في عناوين الأبواب عنوان:(بدء الوحي) البحث عن كلمة بدء وكلمة وحي في عناوين الأبواب

    يمكنك طباعة رمز ~ الذي يأتي بعد علامة & في استعلام “وربما” عبر الضغط على زر SHIFT+Z في لوحة المفاتيح العربية QWERTY

    الاستعلامات المتقدمة

    عند كتابة أكثر من كلمة فإنه يتم ربطها معا ضمنيا عبر علاقة “و” (ورمزها “&”) أي أنه سيعطيك الوثائق التي تحتوي كل تلك الكلمات معا (دون اشتراط تتابع معين) مثلا عند كتابة كلمة خمر ثم مسافة ثم تحريم فإنه لن يعطيك الوثائق التي تحتوي واحدة من الكلمتين بل يجب أن ترد الكلمتين معا (أي كأنك كتبت خمر & تحريم) لكنه قد يعطيك وثائق تحتوي على عبارة “تحريم الخمر” أي بترتيب معكوس.

    يمكنك استعمال العلاقة “أو” (ورمزها “|”) حتى تشترط ورود واحدة من تلك الكلمات مثلا لو كتبت كلمة عيسى ثم | ثم المسيح فإنه لن يشترط ورود الكلمتين معا فواحدة منهما تكفي.

    يمكن نفي العمليات عبر علامة التعجب “!” وذلك لاستثناء شيء من النتائج مثلا المسيح ! الدجال تحضر الصفحات التي تحتوي كلمة المسيح لكنها في نفس الوقت لا تحتوي كلمة الدجال.

    يمكن استعمال الأقواس كما في العمليات الحسابية مثلا (المسيح | عيسى) ! الدجال.

    لاشتراط الترتيب عليك وضع علامة اقتباس مزدوجة مثلا “تحريم الخمر” وهذا يسمى البحث عن عبارة.

    يمكنك اشتراط ورود الكلمة في العنوان عبر كتابة عنوان ثم علامة : مثلا عنوان:“بدء الوحي” للبحث عن عبارة “بدء الوحي” في العنوان أما البحث عن كلمات في العنوان دون اشتراط أن تكون عبارة يمكنك كتابة عنوان:(بدء الوحي)

    البحث عن عنوان:بدء الوحي دون اقتباس أو أقواس تعني البحث عن عنوان به كلمة بدء أما كلمة وحي فلا يشترط أن تكون في العنوان

    يمكنك تحديد مجال البحث عبر ذكر اسم الكتاب بعد كلمة كتاب ثم : مثلا

    • بدء الوحي كتاب:صحيح_البخاري
    • بدء الوحي كتاب:“صحيح البخاري”

    البحث عن بدء الوحي كتاب:صحيح_البخاري دون اقتباس ولا علامة تحتية _ تعني أن اسم الكتاب هو صحيح أما البخاري فهي كلمة تريد البحث عنها

    يمكنك البحث في أكثر من كتاب عبر استعمال أو مثلا بدء الوحي (كتاب:صحيح_البخاري | كتاب:صحيح_مسلم) كذلك يمكنك استثناء كتاب عبر ! مثلا بدء الوحي ! كتاب:صحيح_البخاري تبحث عن بدء الوحي في أي كتاب إلا صحيح البخاري

    الحصول على الكتب

    قد تأتي بعض الكتب مع النظام (مثلا في نظام أعجوبة لينكس هناك عينة من الكتب المميزة تأتي مع التوزيعة) لكن الكتب ليست جزء من مكتبة ثواب بل يقوم باختيارها المستخدم حيث يمكنه الحصول عليها من أكثر من مصدر.

    يمكن وضع الكتب في مجلد db داخل المجلدات الخاصة بمكتبة ثواب.

    المجلدات الخاصة بثواب هي

    1. مجلد .thawab داخل المجلد البيت الخاص بالمستخدم.
    2. مجلد thawab-data المجاور للملف التنفيذي الذي يشغل ثواب
    3. مجلد thawab-data في كل من أقراص ويندوز من C إلى Z

    الاستيراد من الشاملة

    يمكن استيراد كتب الشاملة ذات الهيئة .bok والتي يمكن الحصول عليها بتصديرها من برنامج الشاملة أو بتنزيلها من الإنترنت 1). ويكون ذلك من خلال زر الاستيراد بالأعلى أو من خلال سحب ملفات bok وإفلاتها في برنامج ثواب، كما يمكنك إضافة الكتب عبر زر الإضافة Add وبعد الانتهاء من اختيار الكتب، اضغط الزر التحويل Convert.

    من موقع أعجوبة

    يمكن جلب الكتب من هذا الرابط:

    إنشاء فهرس البحث

    بعد إضافة المزيد من الكتب إلى ثواب لا تكون مفهرسة مما يعني أنه لا يمكن البحث فيها. لإضافتها إلى فهرس البحث في تطبيق ثواب انقر على زر فهرس البحث Index ثم تضغط على زر دفع الكتب الجديدة queue new books كي يتم التعرف على الكتب غير الموجودة في الفهرس وإضافتها إليه.

    التخلص من فهرس لا يعمل

    إن توقف الفهرس عن العمل لأي سبب (مثل تلف الملف بسبب انقطاع التيار الكهربائي أو ترقية إصدار محرك البحث whoosh إلى إصدار أحدث غير متوافق مع الذي قبله) عليك حذف مجلد index ثم إعادة فهرسة الملفات.

    1) يمكن تنزيلها من موقع الشاملة الجديد أو القديم أو من موقع islamport
    thawab-4.1/thawab-data/themes/neo/static/print.css000066400000000000000000000001071305262755200222110ustar00rootroot00000000000000.noprint { display: none; } #wrapper, #container { background:#fff; } thawab-4.1/thawab-data/themes/neo/static/th-main.js000066400000000000000000000072421305262755200222450ustar00rootroot00000000000000/** * * copyright © 2010 ojuba.org, Muayyad Saleh Alsadi * **/ var resultsPerPage=50; var async_tips_div, mouse_x, mouse_y; function main_search_row_factory(u, bu, r) { return "

    "+ html_escape(r.t)+"

    "+html_escape(r.k).replace(new RegExp('_', 'g'), ' ')+" - "+html_escape(r.a)+" ("+(r.y || "-")+")
    \n" } var search_row_factory=main_search_row_factory; var search_done=function() {}; function showSearchPage(hash, pg){ var j,i=(pg-1)*resultsPerPage,o,h,l; var u=script+'/ajax/searchExcerpt/'+hash+'/',bu=script+'/view/'; /*l=document.getElementById("loading"); l.style.display="block";*/ window.scroll(100,0); getJson("/json/searchResults", {h:hash,i:i,c:resultsPerPage}, function (d) { var c=d.c,a=d.a; o=document.getElementById("SearchResults"); h="" o.innerHTML=h; for (j=0;j 10) { pages = 10; } document.getElementById("SearchPagesCount").innerHTML=pages; o=document.getElementById("SearchPages"); h=''; o.innerHTML=h; if (d.c>0) { for (i=1;i<=pages;++i) h+=''+(i)+''; o.innerHTML=h; showSearchPage(d.h,1); if (main) {se_t.style.display="block";} } search_done(); document.location="#searchResults"; }, function () { search_done(); } ); return false; } function kutubFilter(q) { var o=document.getElementById("kutubList"); var old=o.innerHTML; var l=document.getElementById("loading"); /*l.style.display="block";*/ getAjax(script+"/ajax/kutub", {q:q}, function (d) { o.innerHTML=d; }, function () { o.innerHTML=old; } ); return false; } function moveMouse(E) { var e=window.event || E; mouse_x=window.pageXOffset+e.clientX; mouse_y=window.pageYOffset+e.clientY; } function asynctip(e) { async_tips_div.style.top=(mouse_y+e.offsetHeight+5)+"px"; async_tips_div.innerHTML="..."; async_tips_div.style.display="block"; u=e.getAttribute('rel'); getAjax(u, { }, function (d) { async_tips_div.innerHTML=d; }, function () { async_tips_div.style.display="none"; } ); } function asynctip_hide(e) { async_tips_div.style.display="none"; } function async_tips_init() { var d=document.createElement("div"); d.id="async_tips_div"; d.style.width="60%"; d.style.display="none"; document.body.appendChild(d); async_tips_div=d; if (document.addEventListener) { document.addEventListener('mousemove',moveMouse,false); } else { document.attachEvent('onmousemove',moveMouse); } } init_ls.push(async_tips_init); thawab-4.1/thawab-data/themes/neo/static/th-view.js000066400000000000000000000115171305262755200222730ustar00rootroot00000000000000/** * * copyright © 2010 ojuba.org, Muayyad Saleh Alsadi * **/ var last_highlighted=""; var th_hash; function mini_search_row_factory(u, bu, r) { return ""+ html_escape(r.t)+""+html_escape(r.r)+"\n"; } function mini_search_row_factory_st(u, bu, r) { return ""+ html_escape(r.t)+""+html_escape(r.r)+"\n"; } resultsPerPage=10; // defined in main.js search_row_factory=(is_static)?mini_search_row_factory_st:mini_search_row_factory; function doMiniSearch(q) { doSearch(q+" كتاب:"+kitabId, false); } function view_cb(h) { var n,b; var spacer = ""; window.scroll(0,0); if (! h) h="_i0"; th_hash=h; getJson(script+"/json/view/"+kitabUid+"/"+h, {}, function (d) { document.getElementById("maincontent").innerHTML=arabic_numbers(d.content); document.getElementById("subtoc").innerHTML=arabic_numbers(d.childrenLinks); b=spacer+ d.breadcrumbs.replace(/>/g,spacer); if (d.breadcrumbs){ document.getElementById("breadcrumbs").innerHTML=arabic_numbers(b);} else {document.getElementById("breadcrumbs").innerHTML="";} n=document.getElementById("prevLink"); n.setAttribute('title', d.prevTitle); if (d.prevTitle) {n.setAttribute('class', 'button'); n.setAttribute('href', d.prevUrl);} else { n.setAttribute('class', 'button inactive'); n.setAttribute('href','javascript:void(0)'); } n=document.getElementById("upLink"); n.setAttribute('title', d.upTitle); if (d.upUrl != window.location.hash) {n.setAttribute('class', 'button'); n.setAttribute('href', d.upUrl);} else { n.setAttribute('class', 'button inactive'); n.setAttribute('href','javascript:void(0)'); } n=document.getElementById("nextLink"); n.setAttribute('title', d.nextTitle); if (d.nextTitle) {n.setAttribute('class', 'button'); n.setAttribute('href', d.nextUrl);} else { n.setAttribute('class', 'button inactive'); n.setAttribute('href','javascript:void(0)'); } highlight_words(document.getElementById("maincontent"), highlighted, true); }, function () { /* should show error */ } ); return false; } function arabic_numbers(string) { /* FIX this code string = string.replace(/0/g,"٠"); string = string.replace(/1/g,"١"); string = string.replace(/2/g,"٢"); string = string.replace(/3/g,"٣"); string = string.replace(/4/g,"٤"); string = string.replace(/5/g,"٥"); string = string.replace(/6/g,"٦"); string = string.replace(/7/g,"٧"); string = string.replace(/8/g,"٨"); string = string.replace(/9/g,"٩"); */ return string; } function ajax_check_hash() { var h=window.location.hash; if (h==("#"+th_hash)) return true; view_cb(h.slice(1)); return true; } var harakat="ًٌٍَُِّْـ"; function highlight_word(o, w, i) { w=w.trim(); if (w=="") return; w=re_escape(w).replace(/(\\?.)/g, "$1[\-_"+harakat+"]*"); w="("+w+")"; var re = new RegExp( w, "gi"); a=o.innerHTML.split(/(<\/?[^>]*>)/); for (j in a) { s=a[j]; if (s && s[0]!="<") { a[j]=s.replace(re, "$1"); } } o.innerHTML=a.join(""); } function highlight_words(o, w, scroll) { var i,a=w.split(" "); highlight_words_off(o); for (i in a) { highlight_word(o,a[i],i); } if (scroll) scroll_to_first_highlighted(); } function scroll_to_first_highlighted() { a=document.getElementsByClassName("highlight"); for (j=0;j([^<>]*)<\/span>/gi, "$1"); } var highlighting=false; function highlight_cb() { if (highlighting) return true; var q=document.getElementById('q').value; if (q=="نص البحث") return true; highlighting=true; highlighted=q; if (last_highlighted!=highlighted) { last_highlighted=highlighted; highlight_words(document.getElementById("maincontent"), highlighted, false); } highlighting=false; return true; } function th_view_init() { var l; if (!is_static) { l=document.location.toString(); loc=window.location.hash.slice(1); if (loc=="") document.location=l+"#_i0"; else view_cb(loc); } /* hide mini-search if not indexed */ if (!is_indexed) { document.getElementById("minisearch").style.display="none"; document.getElementById("nominisearch").style.display="block"; } highlighted=get_url_vars()["highlight"] || ""; highlight_words(document.getElementById("maincontent"), highlighted, true); last_highlighted=highlighted; } search_done=scroll_to_first_highlighted; animations["_highlight"]=[highlight_cb]; if (!is_static) animations["_ajax_check_hash"]=[ajax_check_hash]; init_ls.push(th_view_init); thawab-4.1/thawab-data/themes/neo/templates/000077500000000000000000000000001305262755200210545ustar00rootroot00000000000000thawab-4.1/thawab-data/themes/neo/templates/footer.html000066400000000000000000000014511305262755200232410ustar00rootroot00000000000000%if _r.rq.webapp._typ=='web': %end thawab-4.1/thawab-data/themes/neo/templates/layout.html000066400000000000000000000023621305262755200232620ustar00rootroot00000000000000 {{_r.title or '::'}} :: ثواب :: {{!_r.render_css_links()}} %if x_js_script: x_js_script() {{!_r.render_js_links('head')}} {{!_r.render_js_links('begin')}} %topnav() %if typ!="main": %include minisearch %end
    %include
    %include footer {{!_r.render_js_links('end')}} thawab-4.1/thawab-data/themes/neo/templates/main.html000066400000000000000000000075711305262755200227000ustar00rootroot00000000000000%_r.add_js_link('th-main.js') %def x_js_script_f(): %end %def topnav_f(): about get package contact us forum code %end

    مكتبة ثواب - بحث ذكي وسريع في أمهات الكتب



    دليل الاستخدام ( البحث المتقدم )
    في صندوق البحث أعلاه اكتب الكلمات التي تريد البحث عنها. يمكنك وضع الكلمات بين علامتي اقتباس " " للبحث عن عبارة (كلمات متتالية). عملية البحث ذكية تستوي فيها الحركات وغالبا ما تستوي التغييرات التي تطرأ على الكلمة كالإفراد والتثنية والجمع والإسناد للضمائر وغير ذلك. يمكنك استخدام استعلامات متقدمة عبر الأقواس والرموز "&" ، "|" ، "!" من أجل عمليات "و" ، "أو"، "النفي" على الترتيب. كما يمكنك تحديد مجال البحث عبر ذكر اسم الكتاب بعد "كتاب:". يمكنك البحث في عناوين الأبواب أيضا وذلك بكتابة "عنوان:". للمزيد انظر دليل الاستخدام

    الكتب المتوفرة

      {{!kutublinks}}


    %rebase layout typ="main", topnav=topnav_f, x_js_script=x_js_script_f thawab-4.1/thawab-data/themes/neo/templates/minisearch.html000066400000000000000000000025361305262755200240720ustar00rootroot00000000000000
    فضلا أضف هذا الكتاب للفهرس.
    -
    -
    thawab-4.1/thawab-data/themes/neo/templates/view.html000066400000000000000000000027461305262755200227250ustar00rootroot00000000000000%_r.title=title %_r.add_js_link('th-main.js') %_r.add_js_link('th-view.js') %def x_js_script_f(): %end %def topnav_f(): %end
    عودة للصفحة الرئيسية{{title}}
    السابق لأعلى التالي طباعة
    {{!content}}
    {{!childrenLinks}}
    %rebase layout typ="view", topnav=topnav_f, x_js_script=x_js_script_f thawab-4.1/thawab-gtk000077500000000000000000000001201305262755200146030ustar00rootroot00000000000000#! /usr/bin/python # -*- coding: UTF-8 -*- from Thawab.gtkUi import main main() thawab-4.1/thawab-server000077500000000000000000000140311305262755200153320ustar00rootroot00000000000000#! /usr/bin/python # -*- coding: UTF-8 -*- import sys, os, time, atexit, signal, shutil, tempfile, sqlite3 from Thawab.gtkUi import launchServer from Thawab.shamelaUtils import ShamelaSqlite, shamelaImport class ThawabServer: def __init__(self, pidfile): self.pidfile = pidfile def tprint(self, message, noend=False): if not noend: sys.stderr.write(message+"\n") else: sys.stderr.write(message+"\r") def daemonize(self): try: pid = os.fork() if pid > 0: # exit first parent sys.exit(0) except OSError as err: self.tprint('fork #1 failed: {0}'.format(err)) sys.exit(1) # decouple from parent environment os.chdir('/') os.setsid() os.umask(0) # do second fork try: pid = os.fork() if pid > 0: # exit from second parent sys.exit(0) except OSError as err: self.tprint('fork #2 failed: {0}'.format(err)) sys.exit(1) # redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() si = open(os.devnull, 'r') so = open(os.devnull, 'a+') se = open(os.devnull, 'a+') os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # write pidfile atexit.register(self.delpid) pid = str(os.getpid()) with open(self.pidfile,'w+') as f: f.write(pid + '\n') def delpid(self): os.remove(self.pidfile) def check_running(self): # Check for a pidfile to see if the daemon already runs try: with open(self.pidfile,'r') as pf: return int(pf.read().strip()) except IOError: return None def start(self): """Start the daemon.""" if self.check_running(): message = "pidfile {0} already exist. " + \ "Server is already running?\n" self.tprint(message.format(self.pidfile)) sys.exit(1) # Start the daemon self.daemonize() self.run() def stop(self): """Stop the daemon.""" # Get the pid from the pidfile pid = self.check_running() if not pid: message = "pidfile {0} does not exist. " + \ "Server is not running?\n" self.tprint(message.format(self.pidfile)) return # not an error in a restart # Try killing the daemon process try: while 1: os.kill(pid, signal.SIGTERM) time.sleep(0.1) except OSError as err: e = str(err.args) if e.find("No such process") > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print (str(err.args)) sys.exit(1) def restart(self): """Restart the daemon.""" self.stop() self.start() def run(self, silent=False): self.th, self.port, self.server = launchServer() if not silent: self.server.serve_forever() def clean_run(self): if self.check_running(): self.tprint("Stopping the running server") self.stop() self.run(True) def test(self): self.tprint("server started successfully") def reindex(self): self.clean_run() self.th.asyncIndexer.queueIndexNew() if not self.th.asyncIndexer.started: self.th.asyncIndexer.start() jj = j = self.th.asyncIndexer.jobs() while (j > 0 ): self.tprint("Indexing ... (%d left)" % j,True) j = self.th.asyncIndexer.jobs() self.tprint("No indexing jobs left") if j <= 0 and jj > 0: self.tprint("Indexing %d jobs, Done" % jj) self.server.server_close() def remove_index(self): self.clean_run() self.tprint("You will need to recreate search index in-order to search again.") p = os.path.join(self.th.prefixes[0], 'index') try: shutil.rmtree(p) except OSError: self.tprint("unable to remove folder [%s]" % p) else: self.tprint("Done") self.server.server_close() def remove_meta(self): self.clean_run() p = os.path.join(self.th.prefixes[0], 'cache', 'meta.db') try: os.unlink(p) except OSError: self.tprint("unable to remove file [%s]" % p) else: self.th.reconstructMetaIndexedFlags() self.tprint("Done") def progress_cb(self, msg, p, *d, **kw): self.tprint(" ** progress: [%g%% completed] %s" % (p, msg)) def importbok(self, bok): self.clean_run() fh, db_fn = tempfile.mkstemp(suffix = '.sqlite', prefix = 'th_shamela_tmp') f = open(db_fn, "w") f.truncate(0) f.close() cn = sqlite3.connect(db_fn, isolation_level = None) try: sh = ShamelaSqlite(bok,cn,0,0, self.progress_cb) except TypeError: self.tprint("not a shamela file") self.server.server_close() return except OSError: self.tprint("mdbtools is not installed") self.server.server_close() return if not sh.toSqlite(): self.server.server_close() return ids = sh.getBookIds() for j, bkid in enumerate(ids): ki = self.th.mktemp() c = ki.seek(-1,-1) m = shamelaImport(c, sh, bkid) c.flush() t_fn = os.path.join(self.th.prefixes[0], 'db', u"".join((m['kitab'] + \ u"-" + \ m['version'] + \ u'.ki',))) try: shutil.move(ki.uri, t_fn) except OSError: self.tprint("unable to move converted file.") # windows can't move an opened file if db_fn and os.path.exists(db_fn): try: os.unlink(db_fn) except OSError: pass self.th.loadMeta() self.tprint("Done") self.server.server_close() if __name__ == "__main__": daemon = ThawabServer('/tmp/thawab-server.pid') if len(sys.argv) >= 2: if 'start' == sys.argv[1]: daemon.start() elif 'stop' == sys.argv[1]: daemon.stop() elif 'restart' == sys.argv[1]: daemon.restart() elif 'reindex' == sys.argv[1]: daemon.reindex() elif 'fix' == sys.argv[1]: if sys.argv[2] == 'index': daemon.remove_index() elif sys.argv[2] == 'meta': daemon.remove_meta() elif 'importbok' == sys.argv[1] and len(sys.argv) >= 3: daemon.importbok(sys.argv[2]) else: print "Unknown command" sys.exit(2) sys.exit(0) else: print '''Thawab Server\nusage: thawab-server [command] [file(s)] \nCommands: start starts the server stop stops the server restart restarts the server reindex queues new books fix index removes search index fix meta removes meta data cache to generate a fresh one importbok [file path] imports Shamela .bok file''' sys.exit(2) thawab-4.1/thawab.desktop.in000066400000000000000000000003271305262755200161030ustar00rootroot00000000000000[Desktop Entry] Type=Application _Name=Thawab _GenericName=Electronic Arabic/Islamic Encyclopedia Terminal=false TryExec=thawab-gtk Exec=thawab-gtk Icon=thawab Categories=X-Islamic-Software;GTK; StartupNotify=false thawab-4.1/thawab.spec000066400000000000000000000116511305262755200147610ustar00rootroot00000000000000%global owner ojuba-org Name: thawab Summary: Arabic/Islamic encyclopedia system Summary(ar): نظام موسوعي عربي/إسلامي URL: http://ojuba.org/ Version: 4.1 Release: 1%{?dist} Source0: https://github.com/%{owner}/%{name}/archive/%{version}/%{name}-%{version}.tar.gz License: WAQFv2 BuildArch: noarch Requires: python-whoosh >= 1.7.2 Requires: python-okasha >= 0.2.3 Requires: pygobject3 >= 3.0.2 Requires: python Requires: mdbtools Requires: python-paste Requires: islamic-menus Requires: python-othman Requires: webkitgtk3 BuildRequires: gettext BuildRequires: python2-devel BuildRequires: perl BuildRequires: ImageMagick BuildRequires: intltool %description Thawab Arabic/Islamic encyclopedia system %description -l ar نظام موسوعي عربي/إسلامي %prep %autosetup -n %{name}-%{version} %build bash update-manual-from-site.sh make %{?_smp_mflags} %install %make_install # Register as an application to be visible in the software center # # NOTE: It would be *awesome* if this file was maintained by the upstream # project, translated and installed into the right place during `make install`. # # See http://www.freedesktop.org/software/appstream/docs/ for more details. # mkdir -p $RPM_BUILD_ROOT%{_datadir}/appdata cat > $RPM_BUILD_ROOT%{_datadir}/appdata/%{name}.appdata.xml < %{name}.desktop CC0-1.0 Arabic/Islamic encyclopedia system نظام موسوعي عربي/إسلامي

    Arabic/Islamic encyclopedia system.

    نظام موسوعي عربي/إسلامي.

    https://github.com/ojuba-org/%{name} http://ojuba.org/screenshots/%{name}.png moceap@hotmail.com
    EOF %post touch --no-create %{_datadir}/icons/hicolor || : if [ -x %{_bindir}/gtk-update-icon-cache ] ; then %{_bindir}/gtk-update-icon-cache --quiet %{_datadir}/icons/hicolor || : fi %postun touch --no-create %{_datadir}/icons/hicolor || : if [ -x %{_bindir}/gtk-update-icon-cache ] ; then %{_bindir}/gtk-update-icon-cache --quiet %{_datadir}/icons/hicolor || : fi %files %license waqf2-ar.pdf %doc waqf2-ar.pdf readme %{_bindir}/thawab-gtk %{_bindir}/thawab-server %{python2_sitelib}/Thawab/* %{python2_sitelib}/*.egg-info %{_datadir}/thawab/ %{_datadir}/icons/hicolor/*/apps/*.png %{_datadir}/icons/hicolor/*/apps/*.svg %{_datadir}/applications/*.desktop %{_datadir}/locale/*/*/*.mo %{_datadir}/appdata/%{name}.appdata.xml %changelog * Mon Feb 20 2017 Mosaab Alzoubi - 4.1-1 - Update to 4.1 - Fixes for warnings * Sun Feb 19 2017 Mosaab Alzoubi - 4.0-1 - Update to 4.0 - New generation of Thqwab Server - New enhanced look - New way to Github - Add Appdata * Sun Nov 13 2016 Ehab El-Gedawy - 3.2.1-1 - add webkitgtk3 dependancy * Tue Jul 14 2015 Mosaab Alzoubi - 3.2.0-3 - Enhance summary - Remove Group tag - Add Arabic summary and description - Improve %%install section - Remove %%clean section - Remove old attr way - Use %%license * Tue Jul 14 2015 Mosaab Alzoubi - 3.2.0-2 - Add some BRs * Sat Feb 14 2015 Mosaab Alzoubi - 3.2.0-1 - Add Thawab Server. * Sat Feb 15 2014 Mosaab Alzoubi - 3.1.1-2 - Genera Revision. * Mon Jun 1 2012 Muayyad Saleh AlSadi - 3.1.1-1 - port to gtk 3 * Mon Nov 1 2010 Muayyad Saleh AlSadi - 3.0.10-1 - update to whoosh 1.x.y * Mon Jul 26 2010 Muayyad Saleh AlSadi - 3.0.8-1 - activate cancel button in import window - only reload index after new import - css: hide overflow in minisearch * Sun Jul 4 2010 Muayyad Saleh AlSadi - 3.0.7-1 - update to latest stable release - activate footnotes links - opens external links with default browser - print button - add zoom buttons - auto reload after import - change search query syntax - add manual - add filter to book listing * Sun Jul 4 2010 Muayyad Saleh AlSadi - 3.0.5-1 - highlight minisearch text - reload meta after import - fix some importing bugs - use connection-per-thread in core.py - static-like pages * Fri Jun 18 2010 Muayyad Saleh AlSadi - 3.0.4-1 - load books from /usr/share/thawab/db/ - limit search results to 500 - notfy user for non-indexed books * Thu Jun 17 2010 Muayyad Saleh AlSadi - 3.0.3-1 - add missing Requires - hide mini search if not indexed * Sat Jun 12 2010 Muayyad Saleh AlSadi - 3.0.2-1 - initial packing thawab-4.1/thawab.svg000066400000000000000000015212301305262755200146260ustar00rootroot00000000000000 image/svg+xml thawab-4.1/update-manual-from-site.sh000077500000000000000000000023731305262755200176370ustar00rootroot00000000000000#! /bin/bash pushd thawab-data/themes/default/static/manual || { echo "can't change dir" exit 1 } baseurl="http://www.ojuba.org/wiki/_export/xhtml/thawab/" for i in manual do fn="${i:-index}.html" i="${i:-الصفحة_الأولى}" echo "getting $fn from ${baseurl}${i}" rm "$fn" 2>/dev/null || : curl -L -o "$fn" "${baseurl}${i}" perl -i -lwne 'BEGIN{$echo=1;} s:href="/wiki/thawab/([^"]+)":href="${1}.html":g; s:src="/wiki/_media/thawab/([^?"]+)(\?[^"]*)?":src="images/$1":g; s:href="/wiki/_detail/thawab/([^?"]+)(\?[^"]*)?":href="images/$1":g; s!a href="http://!a target="_blank" href="http://!g; if(/\]*\>/){$echo=0;} if(/#discussion__section|\<(link|meta|script)[^>]*\>/){next;}if (/class="tags"/) {$echo=0;} if($echo){print $_;}if (/\<\/div\>/) {$echo=1;} if(/\<\/head\>/) { print ""; print ""; print "دليل استخدام ثواب"; print ""; print ""; print ""; print ""; $echo=1; } ' "$fn" done popd thawab-4.1/waqf2-ar.pdf000066400000000000000000002072041305262755200147530ustar00rootroot00000000000000%PDF-1.4 %äüöß 2 0 obj <> stream x\K,rޟ_QkC-%$pw/5ww<$B1=C> 'i?~?=Ο1Go~hϟc~F81׬#>ͬ~UPFŧUMs?O_yԳVO5ݬ0eV;??Ͽ!>Wgc# 7[.("y rt i@--}znC''!YȾxQ%9n43dg9ܥ+?]7_O[e.[./$*3=mRY`D .7S8Rg 4/ l(̒"0Z!UO\m ʯ:X4`3$ͦ͡yU , .Ovnlx:p(HLpI6<@-Qc]q 3N4n{J>MQpg>6kws?Mj;Mq6aL&Q% ,3o6fqYpRXl9d?s?t'{s. n"r" -s'Ȕ)٭xr3i6(Gaǝiߤ a>dW=Gc&.V ٧s+^χ?ƒtk ^Z/f7,R'ĂY^y z7 e=Jw_eAj/4̋x ]9R-I_@tkS`rpy2 xR`9V2#d0Jdf5no PFpN٠{,퀬>UຑP@i:a p&D9?9SbQ|Kfnw[|FUyF$'*$jaW6v- HPY@P!*4 l* YXC ME'H=1\w FGdqH]($ASJt8U&t..q}ɱNdX|eఒ>>T;:2)%zz"_8i ,lz]p nv}%B"$[5A43M .ֺiF^|˒)WYR;4r p3b@Dq ~Jypr'? ͨBa -/($DA ag;,ÖE#yӖ_MB)^V`Seo'e^`9V*ArPB _poW_v* 5ԑf344 MEC,*#l_/rUOʵ'j09gA}!b^2@ B5J_;C[)6U)d9ǵo[4L:VAoxy3x'mBL+\pE`zOɔ }lQuqg|!ZsmphXLP^3omӓD 'vWmrlfSSyZ`:p}RK6Bb T"Hy@t%Kom6BdZV4o}] `tMęT 0C  KpK@v;vEfnv2;.s (qȢHmGDR tB.)6~V%q~irxa@ϴIX*tLx0%5=n3,i*0 {Wx=~3ϕ,3h<L4J@G$Kt(3$GgI~=B]i$1 w;0^%MtlL$C}Eǚ KeQ q(?&l̙e0}WLs]{RUu e!!5㧯 ߺp4 9КP614ԤH`foZG@4"aBNO7oq3lvpMź"^;P ɥW@/!^LF(r{t;6!,( ʼn?ѦO>}Ulhсgޏ.)l@QDՊuBwJ k}|Ց(<)>!lGą)gZsZA҈~|.77wx V&6|%eJ)\3./XP(EX12ɰvafp2}wĜ>|o)Z\ӼMS" VlO *U:H'ao (Slgڝ3LSl)FѮXW.ڹ68Yw|V @_d 'i,tI1m&,dpBYr%]ߛi@׍! ͇і@:b1+ ʬ-`]/v~F 1auR,WuIj-MNy>A_kA,(j2WWh3( ؞Wy2XQI "-IiC'tɚ6,EG{8TSҠt]Ik^fV m& #SM\{,X҃&9"\TTG>tvMr+ѶM!6I%﵄x8`x5#n%g)v) n.%4yɖY Z2ns2)ycp? |]o0.໎Yߥ("fP q&)j5gx1-&mZGv|%W\h׶7Aṋj$.1jVrlfb4hfyE>P ( <^4sC#;*:`*P@nmoۭJ^PA,xcea,?">Κ捆ؐN-\5h@ eV+TnOn$"c& caYiF5T>bD FnfM`KC\WÝ{J/^gY9۸LZ쥺XɻIE_cR3lTcOڑO(H=/gP;(kx=<&jd &Y_[9S;PiS;M`tfq].oH[C̙sB%:#@ukOI' s!*/cfW+md2ch.)֮2ӑwֹo+hkz#sK)qVu6ئbԺ\$9[~^iAay˗#sYff!-@b܎DI2$ە*GO~(03uT~H۱i-XYvpMJ>}jΕ&lAg6IR.-'F}aL䙓joo)k}O|jsԆxMM'\;ӉKw[1tdȃvoʓB&ߚR!;ReӨ] K3BMГzfXJ7" foN8|;|۵5! L edP1:Dn_z9w:itC* j-n2%xf:diOIM<mxgzR%Z]!b#kѶ~#[}]X^}= DxmA<.+5ĵkM٨Q$'{ISݿ*255{嵑Bly12À&{c1|EzΥwÜ ཅD`ogA}\OU[˺ |0(4|R|<G!r]ERRşI;\/D鿾,ԍb{s?`قkDҒE7r,޳3r߅ZM7>5=u\2i(%]xӂ\t!$nk\.T9]z60 +`RJr*~Ptv,:gnAPJZ}`s[ϵߨf%߶0h0W>4#۟>=2ˠv2#tbu3L fךN%{XWb8#0אDt MMyXLCfLOERWm22n6B-ot04X{ٸ&~sLv QK n6q҃)]Dג ]Ii8 -/Ϯ|`dL{ʂfs\CK 5h[^i8C|wD)]K]@m'Y@g!yNpWzw]a+쾕 6bMܻhP8{< ]`U4]B w]h"E7{o<(>ɸ2Si6߽4פǚMu=C+6c$?D mǥ˚XX}Бآ ڮJΣ2BV@״{^NvǷ דSԘ15݋xe"jW2di@Pw$Կ4XK+j,fD\yDHLen6γB7"&[& V7r7aheHJgnn`??~?~R endstream endobj 3 0 obj 5695 endobj 4 0 obj <> /Length 8 /Filter/FlateDecode >> stream x endstream endobj 5 0 obj <> endobj 7 0 obj <> stream x\I9rOgCUk_ Iu|(66>O,B?5=R(E(է>I|攜L?ǿo?Ӟb?vOڜ~Yhc|سI}6y1gmQ/ 9?/?4OΥ\L*Nq&.&6a s͝>?zS9ßy.0eÖe0^}Iņ0[Mڶ@ɇ(#]-Vçƣr>8 mY`AOu,' ,:./#7X~<⊎-:l6e[G+IQ f}qe^Y Ŏ6vmo㽌>}؜$/{K!=HJ>) :P hh 0*Jۡ@ɱh+OEiG ?Ѵw=>w5]PGtvC/t (4 Q=6O*a"WXj`y< <1Oku1W~ q C׾!ѹ]HJ5qqU2ݾ:o-0ƀ}i`%vs|o`yE >|1q*b0HTH`Fk~k@.݄Afۗ2wuWSq!5t4a9g[)3t|JZǰ,[VG1qMaJڐWrFGO,.n<(@ȶ^ơ3I簔9٣< ro5E( U7^au~Asq@‹:l/K>0?Bѷi};2@)rFιt#N59=Q [*)n ` fꔤ}Urn)k=XVNb0 bco|?VXCAM! Xk?;|;QpX6G䧽T >R4y+J?Ӧě8UȀn}dNUc3HLnlum 4n$*ir&NŚRtެ;rfN;HNGFtK.ocB!bҷnPVCe*|9h7X Ohx+(3*٤fsK\`sT@;6Xx,掉@o@20u"".2|d B b_'z7L\ç)(=!/-@+Ƀ_/>f<c*(]15<6:DtKU*stkXǟʍP,el&cE鹼!GZ(lHq*;)ԚnDrع ӽz㦢HFZVEح0y@af.-ؿN}G>-D4!lϾKb̡0RNo}ECxMt7UվVۛ=!mӠFDPm\A^U3˼QaYo'v%}MN(,C7eHgF3ETQ=4E?XK sAH{)ƥ#rsf: jG<@K}4}Ł1,(v$;WeSZ-]F ⡁j5{k|1j Zc,L" ohI4M;tld٧ Ћ3jJC;*3PEa{&+NG^sd>7[ $A_SN (lQhh/E5/npmlvD'qd6TpM*;Fzq~ā$?gXt۵1- 9V߰HL%J+CC0OKqQ9A={yEom$9YfjKT00, 1`6(aWr:f2E]F6TTb@DΌ6P>[f9}S[(i1Ľ֤v/ :ZFHoLW-((I)T &3Yml\رDaJP jI=5&fb9rck5e^ DۼR;h9'@&L~޹Z [,wBT7·T+ۆW^Ɇ@rJy0hkjPmrKiZcN70_1?vIZm T]+%AlRpXyWlXب˘9]; wغ&yc}S;1WYKrnB[C"z_i (N!pF.L(~老E\}X.a"!dgvxOV1mPJOwDI`fqt1a{G'&ǁK+XR3L1TAhD֌:>p vT#Y xzB(<^nN1e upNKJA9t:B3q;ά|MvnK-9P S5{v0 XCqb8:w8:Kafn~"8LJއAp=놞S'DBO7vzfJ[$T{!4b#I:ԶDߠG Gh|l+5ЁZ,Bz|? (=~9od*;̮/^WF @#w߱9^mX*𺊶k9i C\facUb4S"{?)a5?G ܕaBtNK+[a GEȄ̞QLhaldzy(=*@&$YCS[Rq'?q87(\S5ax_C|YPjie6Kxw#cRK`H0Ƅ\v_6 =4:^(}f̩L?QגV~'_<Z]p{}SGd 2vڤ=3΅!`}w0o6 !hmOg}E3LTFu F|fҨ/_/ao~􆧏*JYrK{˰O$Ks_K2(ސžg 1p+yB;a9ŗ:y 5s`1ct}wq5EukGvS$P>^4̸ ?2w9n_##eA,ac}GdDJ8)dwtȷ^vx7'8@O ^Jn6]^UviJ!V_80Og3:t]0|s HEw0{.{3{ICA1!i>u9`u 24†6u=4Fʯ}i.ō]D4DLM͎}&\*:~L6vsJ44A l]p10V{2ƷeyX ݪғ҃VPffy_@,e/uoj3ua_M.K+`1Hc$C6eE=O+͚}~_M?&Yע endstream endobj 8 0 obj 5294 endobj 9 0 obj <> /Length 8 /Filter/FlateDecode >> stream x endstream endobj 10 0 obj <> endobj 12 0 obj <> stream x[K$ϯaf|X|-#"53#v۝ųԛE]|ٿKr-]~_tw 3ň+/\ۢL=W _i߿hJ_\tm_^_3؀",GUWr@蛧E닄m,d/h?M 0PK'*#%' 'Zɚb?'AGUA w2be-HAz/]WSф/7|y?]z ,u:K۪G7gD i9,_6f|Y7.s^ܭ P2 ?b4C>D&&/zho!!pu)&2ȅ mK5 87.D8v[=07'$5}*J$pPYa2pn`״uY o.TTkSpA3Pi8_rS?gf:u%ti_,Z1d6 XY!8:HĄӼN oH@+@ۭngb< # [1Eri+B7ej U@B@UDIvI:Dvph:FNt ; -"r_ƜĆ$x} [&뇏N 5Kd|=%ơgPg vٞ7^c:= q2FS6 +}5YcA9rP%?@@^: ːVZQ2y Fc l' tͬQڨ'0q. MBhGV#GOZz~YJ{G;n3-+u9vFB^1$k "k | =oLĪE*Tb&#wG"Iz"CЀc!_ ;_i !ىX-|xaE؏څ7;gcE8j /q6=a^*h+x8OXp_&RO@"ހ*i rB1t U>4 x0iڮE LnBzGO0q|-:b\'Kz~`3D1N 8QiyS"tQ* HwZ >ՒREgW%Q4cTfGb^+3.c,/P9Xڱ]鎺hkb4J g]JS^2uB9>XkC Qu;D2,Cf xq#60P3ϑGTD)EkeZ:OJcTa0ĺ"E;guYHiVP{Yi:z:C|?ǛgľG%[8 4Tᆉ>A&5pߗ RA=LȚ4H[A}et+ք8e*0YBVE0>}O? j,c[+ wQaˆgή,KD'x{sp*Qi2D *,F)(/F OQ EVIϤVLFؐMа5N+.OQ*']IQI:8{"ѓ> dV]rol5CıVotNƪұ0ϩ6 vl.!lL% 3wF b͓6t:ewM)tL*d!ΏF1qe{wU;w$Rm@-P tU'Ii݇ ]s;G1G}A$ˇ6wG 7Fq 8Sd'q($(_GlQ}Rk< E|u@#7hAH'#/zeh8U\M)#$v|HTIr55=iת(gP3W'թh2u ýȍ5Ӧ1iʙeڳES`Oem+,%^V]65- r/Ն ژ>S6E}b׈Qu RBO4$iN&$"F:7-Hiwpk)[c,NǑO;*xTB eKPKOxJyU11ﴷ.M ^Ό3#p.L]ԌEG?$m9l;pqOGv#A8 \j##16Z_zl[;q E? Uixsz.q6!&B\9Df?S۹ +K׭h'CZӃ &'Z4(xlnykOur˥.fYâeLɧ8>[J4qm$7 Ղv5>1B_T}VsW#>oxN>r0f}[OЖ|7f#muҤzp]eaBJ,GH805 %Q&s<ůY'(28~r;uP|vu6)xvJ99UzHURtܺg{J@Uir>yQd!;k"׾jT섯+1z.cb(JCA$ D-F2Z\NK|ڤ)3N|Ek]^WƵ_#|~ a_.^> /Length 8 /Filter/FlateDecode >> stream x endstream endobj 15 0 obj <> endobj 17 0 obj <> stream xw|T?|fNݾ{vlnnMF $!BB ZE:HQPDDD 6rE\{\*9'{=#ϐ|Sޟ2gтS )s&ۻ '  S,*Dwj˦=rbG}S'uM1!>4p?!:7gѲ_}  92{I #sΤeS V'͙yrw.Kbļ M#6C˹ SmC=f_Մ4 } +!vI /#D)DAD"FD($JR D-QO4D1h&ZQDN&N!&)Tb:1E!Bb1XA$V#^#.'6M*jbq-I"v{>V6N. q8B<@%$"&AX` X. kzp96+Vp;Np=v6pCntU\KF$%7&A[Gz}p᜿>1B@{s $ qwtHdp' zYBOFO0m{L;z{r1={,OՋ?0iO3gr])g uzA ƉSe@=Wӝ=5zpzj5/ôsS%DOo4*Q  atvY@`O$?Hx{fY_.L-MČNd5j>gŪ] Bސq3SVH# xHlɦe(TVbЦ/Y]L,  ta/aN۪>A>B^Z;Ć>K)].}e@y8l" x鼢 Qh$^d$ VȰ͑ 1*3o ԑ^ƒ<>=Η$-B=0<G+KHdl0<<;>1pT/믴-=jSgoq)v l/+G$sD^ Iw..ZQc`f沾wCt~wtk7X)N>iɯr!fLr)Ӓ>cK*GVLLU4 8d3[\!?B%۱2ǰ\躬$KWoxHUþ[NH3ǾS9d{0=NL 9}/}gt XǺΟaBtVW o+bX0:+HS`( tf)8,'BlHb.ذ$6KZJV?0eq|h&b6^1qW,] =Hv`#ρ`Ț(,m&/r4E Qn2cJXƥ80n`wģѻ_@r'8I>Lމ*&6GqaiU(KN{̙IIe>Qejn%# |I˖t< $GY#1%5%x~XWy=)uuT} }=2}?r*7Cͨ!޸2n0twwFk=6gH>F˲9b!nT)o #^(- &βLew!9nǾ V  ȨZl ^񯁑v2 $, ,9kJu6ud-#n(Zar/- N\?8+zaFFPnl8غLioY<9:n*SޡojR`c452o<4+I]?#*N&Ӑ?:}s)ϓ}v>XZ=>  5_E:Ao/VLz8XCFVHI{cZ쭥̈́,k1$kňPh¥0@%,B U_EkX^"NĆ;8ʻ%FLol F-4{ef ϣ-AI& ;İh4-lХ9JASvMu}f~YތI2B@ᙑ$|;=ćϑB|H&F0+X&vq$;0|R5hei>i xiYT4Hy'׼RkPl>x*dăbOePHf)v $ȽKrqo˹=&nd-9z|jF[дf%94 ߢ9\~!^ӎ׊c4+ 7KP?e=!A+ ^ igL2 oXd6dp}IZ/TdetBmAxf0TkLvY\e 8ܦJL82&c`81nhWz \R*ת~ŋbB*BQ$p8◜dXXC-oPQA]?6fG|a،:Ղ$,D먑֡8'9MI3 ܦj:xko. _⧍г+8)80mKb-BY'}BcOJIC}Ŏ,Li "Vk* ]UM,e9UL%HN> U)6Q|FoߴkcԳ9vI Ml6љ\1B1Gf^6XQSO>#.kV.SG>So~Fts ZGmXll=!#ɣz |U$Yuk/~K/qfgFJ_t'g-JzDX'9Ҽ ~HnX" CmѻJk&P`!`q-j,ϙj\H(uYRdgl2%w^7߳|>^1N0/%8 ?6(RBVOZA&%㡤3\lA*gakz & >ȘH2tןĩ*Yȱp)dcC+'t甒_\q~Km B㓕JTC㐗&bHO`lS@5 SϾ:s쎒v82&B >oB۷ mX ׌cU3ֵV^\=ӫb&GhhQ@*ѹgFF S`׬ΒUiVUIu)SrP$.plP)Lc-Gg4J1 & aX5_Ub`zQ6."a}*mbЦϜKb+`5wdiߤܕ2?7f^18B rb;AZ"YHakm2"HF)UOo{FwQ3}U&Jq3 0H4|*|Gܱ=Zo]cK\5[FmdG{sp xwdWZBsoKֈCa~j<Pze*{ᦶ8L.[Z$trjWi̻Ux H^#yWOP `|]R!4dAV'iMgo6KiW (\BWo]8D:=\OzA6p'ae;2_#؎Pu,gCPrHȴ3FXS"PTna:W#%['e@Β$L0j9'zH6#˖І3e@(hD5$C"<kQ2f]SZ`1w,빣sy9n4 ~}]o0>]p%ȈFH>ړ}jѴ4NZǩN0hbWX~xi~OldEd&s]2<<5.i) Kr 8 ͳL)G܀Md$",c6Us wO:nBuG*k&go%дi!XjyZsR>Ic.Se'% JKQ;#`( %r T F3d gVL.а7otGf!'eat +aZ+ג4[R'?T}K,B))z2nn`3zE(>Ѥqd7Z=SCLH>NΰwsY z zIF,/4 ZխW ܋,-&#(\}0/N_pnO5Iy/ΡjFذBA5Ng789*-Ohgh,4I;63K-LZS\\Eq7g'{ͤFZ8)>@N~J\_6DIJ6-Ne9zTC޷IxtBn`sJ<(DPQk: RÑ4_jrX4eK&[<͈i{dJϗ2B"u/}?Xf|9۷d?}5j+hԞg˿SI8qM}ylƱRWٔ;m?Co-4GR+ZPTMl"^hW*]q!W׃|k"2dR0_(]PէWlRL\|qEU E p:e,?}-, |Iob},MTPkLolm/*unө/KuS!>!/7j\#Bo磔`sMf}Bt>7UIS3U˯i:2pBA`N5R>St@Ō'Jzq~}]}x/q\I r惢)RAJQ@pV<"KK-+LxŮ{ MYL~q^Y9#ʙ5)?M`--궧/~ǰnjzi'rr 6=Zu*e@/l3'AV;5:.C ٝ7J1\ u@Vk\w1}Ӟ+5U. T[͏̉sg^%^i)V B!@DxE0d1!K*m;?p Bf |*k<̴w,6L_Lfx!prUDX6:v |䆱jX3G8u'2μnD% ,W>\KNe Ns 9AKg s̽ٲ!nghnW|Y#$W١`y:iYVg_ :v3=#uZbDacD 6sL@4-SZ=ѐ e$ʰE*TW<-^7^+|䕺̠ig*oiha6+ ;j6i2F=`5#s"`J]vդٕt3?0>_Ho<0έR%Pm|H"n%bKrjǁo?woKgRo;FR7#Mjݕs6ż:A)t &`W1 :𥸪 3ŬB#ɹBc+/$`(3n_GECiGӣ1ǣ6S/ƃ&3teᄩ:MqZ},6bpj?[nKF 'xcmsznkwOcYg'm0RFħd>)40=ơ)G)mѩKylf{QJNԃsFF^(v1֠]grPh=TNH2>;b m"O@&Gk2.ĘA/2vOia߹TYpBlA Msg < Syx>E;|K;[hG$Ph_dihETSP5#:ÖF TԤxodZ\企 n(HfMFT 8pcİL9hM[y/NWoͳPVӆSM0!̗6?W (p9 -d&c)4BȖ m6!!(1+KْF{/{PLToCaVkAP2d4!@%Zg|{XS5?C6HY <`̊PY>g|'#{/ 62.OT_)}Ah;華'e^}p?u)' B…FqKjtFK|PhYNX8Y%O=3,SqKk&wbǴv'~g&Ҟkr>4߉w'&4ߋ %ZVCIaa=l0Qw|JV;,7Yke陳=,fOT8ϏO_RqAߔC G!9qJE|Hh੒CB Js_o!̃V@ uϊ^@ϵ\zygJ,IVJwچ##G6Y6iQ12P^ Nl~@Z;`ΞdJVKw>&4rNeĥ*-V]ܓ$x}Z8GfiO33ⶀB픒G+y^ XD1ϙ&_~%Ռ>C.El@N8(Y3/*2 bL0OuDeI]!.țNnp4}Zog.6<з2:ʡV}h-x-Z׮ poH&;8"!TK(9UmK醏Q+-Ykƭ'`:dSl ՞(7XF65>婫 ޵=(oE)}9&|e ;W Ǒjm]^z֓%ódJWS izn&*˫ƅ )3CfX2ngzK5Ƶ[}B1"L.ssB2֔M[y M7^-3Ȁ*iĜiI$h GE3NcI`7 LGVY=|D_ % |&Co]iO ,hdf2&LFgp) fxnQYd_֏FYkFu;@AҼ -ŏҍ02"H7Jk)@3l,pV١g.  i|4;,3z0y), 0P7#prVvs&d0NcRiϗ]E93T;%cQm1MF.yiZFWGAYADL>8 Es"Y; `u^R3>5[\={[3Wi$֦'օ$ôr, hXVv[tРQru2fb,V^)g%Fܳ@\(Dzfԃd.7&_p#1=H:`2L*Uzp-kHyV,> [&(qnk<ȥKjÿ$XB.|FQ<,EZ-ORRyI吋g+p !VS2Q>PjCr)Zߘs!#4ݟ4a䨈yBZS?,I2` 8J{_KXvXɫ|HsID<5f:3.K <٭ZGV1e=LF6g7ģu߂#L;%"ZfJLn b~JfLoW ߾*?3uM!GҢt) LcR $6Fw9PDCj8Y6AʷQ%Ì`"MvF!sA6X3ߖj f-眽nSSj8w7~1qELKR{n4[OiG?2-ݥ*1j(e'E``":SIѓ٢1UӋRAmϙ{="_/U*@߀tx$7n˨0>7~u+g{4t tq|4q*7AyV(+5MEק蝱7Rkpn2}zL? F{U|tfS( j -ufCݮ$ͬݿ+9{2y&% \4utРp^>Zُ|>q!"_cRB{&#DU2_-S/S; [EvQ]vh]^4O~"wAޔ+\;M:g/d |ɚEA -ZY!4q'ВslKHVgPa8qU:fZǙ`ja.]zԾzfUxVe:8qd_7W'RP΢Z/eo$%3>~tLM#OoOg P{4@P{) -ɪgF8-O3pVd2rZ23jN 'SwnSM%֓k#5mBΎ r#pEcWl+A-N O.tG(m&uu1>ur [bNW =[Òݐ(I/MhqX,+0.d¹h* NQ@U+ģ Up ZG #80."^7 # } Wk& ~YԵNYց}jcIWe{{'ݣ>&rd 4yx5Լ< t& %YvG:i du4d2IY&Fͱt41t1rURدx97 8²8!dgYF 0_L);83mvŅ8̤ W;U܎Mts -h?7,B}Z[Dq΁ۋ֞\4=ط'lAC,of\kb8GtNMӑ/J%q|+%\r-H$'0*ͽpGz3Z B#\meӇ.mŰo+ɽe \GV܍`u½gl$ n+)^ uC5²'bb@E>?,${Eu"$_lhBz'}/HdpITBZ~f/Ba~leqξg&CM{ZtiZ[G=0P$%hǽ7VnYsIIy|/딞Ե zJiP-@ xlZOoZ;וk;o .Gd"0|ɸgvQ|½ a(ֹ/蜚8315%D1Mq&(}r(I<94LgĆSHc&6zY}; صup7{-]s؃TaX0q]WyR5È}&nBcrPV8,2J ݺ$.PbO~Vğ^+ݑ9j5.Sh5rQ04V^S/KV=A+ %j%4n牰uYΌq(wf.}Qu3gq1޺y4ȤnWV:LJC#d̓_Bjy/ K|Qh)'{!'NW(=ɁKSZ?kTY4ХT@jۗ2zF386] Ϡ; NY< RiT~R_+,|K[$0TrBgVlc1}~ ;%hi ni3[S";P|H㳹VeA=aD:ed:Oa $$a {'x]Üw=&hzP:Ўo'ceց&_'z›O 4_RWO0ŕ&Ȱ 9OObSĿH~Wc]?̝Z#[Zues4`ǒ@U{ُ0hU dthNƷw"Hoϊ+x]Nj(%3\qZtX5 =rdVۓ i03fReA[92y\IV?(6FH5+4NB^%J'l%%jFGH±.ۮݕܳQlcz򖰹+\!yV81C͔{6$%Wh@?iE ,WԱN'9<{6]*.>if2guI9^nY ϒ^*Yz8yV;rމwCKsǐB#kW.nഒ@^?ufs-h7y`ζPh )/!{sIN{[:Sv{](8;`h:Yd^U%EvEm/(9$ZD]N,RUuwW m{z2뢛2$9_wrmFL3,I1N@}EI^ 4o9>,L{~:p){Iy~:oN'ssaԁ߂/I/ߍSą|KgXFVn6U\!6?pfl6?м&T9|1+^YU}$_Q="~ȒS]Y>iOW_t[*L-M5[^{5)Jdmz8W-V^ίsW4 hzE X֋Bvk m>{O +;WU =7|C~0uqJ']!EڟWZ7T}һտRWZĔFs[W:.&sVY4 {ےI|dqP{o{ bǢ7TG4е.j\iY-ӹOWW6ClȐ3bsW5/z̵_Y3[G;~rw=G¶fGf9w]RK/̷bYiٓYa.T ڭo wkMOS{F TW^9P~PHNJ]8lŷ+Ӥdː[v>eYw+R3O/dt< SmbNQ,oP'O3ij]kfVJ]_ 9u3xo'^W9(`l}z"Q'ѕx$~E,An(ޣu>]~K·7+.-j:&'͚WvۮXϵ0/7YxMrSQFVPC#_[E˺FS-3uˢumy⒭|Qgɖ6=~Dl\QN{xuĿ-b~kOk n}oM|Kf\qQhʵ~`e}G;krFeJ]d*^ I By!kyw8SzF p™_ӛ P}_J|v0][աOo]%7edD{KtfmwYU|wuZ^Bxpo틾~mx4'Up9 9Zkwz;CQh?yg%m@Ϻ&|~-Gߓ.Xmkˢ-I_:b;*JLվkyM΢4Yοa3f?];Qdf+:;InB`yzzfkxKnR>w^U?KWu7"څ1l`{Hv2 cooK+,^mjO/L)\Qr^OF:ʾB`2`z2WzV"KUUvYn;/}~3i!GJ*L 3t^_:a`갟铏?Ěć./5&"[0GI4O'ԗtW g S2J^ %Y؇ ] A׮n;}Tp- /tv~ Cpކ &l'8G _qVpp:m\ڣ> HOܿE~R`z/G.#}>C$8t) M ب!>N }vϳNao՟",FSgun]OX# E*..~⭻SxAH} xp-\t_GQ TwGwe~ CF?B0 tGBoRG)#~7GV^8zCZDЯS?E,87E['=aU{ "菪/ /nA#՗ G@;@`'܎@vEuC ]OPu?GQE^Us4coA2Fx dv vv HaSݎvkW8{C A@2[/~"ݴ&W \ @ܬsжD#  _mtv̨O}#a?ٸG xX=mנ_"+˿>ndQ: /,#O v#}F={_A,WoD@?nH P^s5E: >ôBnO^&X $C_}zXN}xh5l@=60>KuuAw ? a䱡W؏@|&Fn9 ;]zJsp^&o~ lP#.Jo ARz9Aw ^|nג>m\t>m߇ @Yr{YwQCrIKo]8 j m /^5s'/KuK.剔L)aZo5MGLo槖Lݕzoꃩ&GL,iciݚvWi%ln3G̟]2e dLdʘx,㉌32;3'3f'uYY˲z l}hko~2,YBb92krE˫9M9rr.ι*ٜs^5V6={sgs?='M=?}ߗ?]oWPZPUpnd[Ap_; -|𵢊"[(Ptq5E7U@#RqFq~X_(K%C%;K(yݒKJK/+=XzVW8+xe+N eue}e]Uvɕ=++}呕W}%_PrQ<||WT+2*+V\qJR<#+߭2\Uު:Zj۫WZuWyUzs%߬~DMDͮ{jV5/fڗj_eueuuM.{źKr7߭.^]ioVk dXwYX?a]C m8zû lmClڎl,lTmn 6^xƣ'MMUM@M5~ӉfԜ|-Be-7rVcu_u/kh+nsmo~מ^~Y5϶ZM::u|㾎:eC*uO 7TPB<!'EW""e,A4Ay(弜*E/biBsx9=;C rPs5/;avQ /geIe0w/SlQ(-/ :^^N x,7r?y9Gx-,2/K 2{V6Tk,K-eg{gf6N)-MM ժluz.e-(,^ltx#36pfM/L-Ǫ'SCSʺVuEB^sƑYOHʸ:t+PL}!"sJx֭>pݴ*4ۚmsg=:[Hxʄ )-&[ӅNo`٬t)6ih'BZ5!ºNe ևm r<3>6M=IĊI3LGab3]QgT8\P&"e4Q[7W{}3hvFpo: qi> 6ksb7BMOPsf*W{ e|pt }qPd 2ZI4{ds:'A{=S߽UqdNjh* CNj6ɲ'v?P{q{"Jҡ'gePw ف-netkڶJ-X ~ohVz]{=.rs|8ʔ a<ע@п=%!"l\Ǧn'z==M 8.J S0\ {!&5F<@RAke?R140BʜLĭfhB$9S L@6,` (Mi##PaVE\,]iIg2^0/آI.}{]hP9r{!H4, 1SI/"➛t\XZ .cJ4^:t ;7b"݈bLi!v~* [!dMgd~ j:cF}QtI OO$ؿXd@ C[b*BB!r1n1 KIm̍ӸF=qaԬyoЃR`?bJ  !(JŨ;8 Q0q@ /"q۪!ͧO OIsS|t Vj MnxX M`wKJBiA#0i cd@\JdNq=̃-4\o + ZitX`ZP!r{A(-usYjiEQ@,rB0NXO;bz#n2ͱO>aꦄZv9\Y(ML  #S`s0Sx^W~8 1 Ly><ƽhk _"Sѝ$?-f‹pVМÐ  [ɔ?< V؋-@50eȉv58G A1D`KpH4F̶:|mN2\IvX0( aiMs744ay1ۡSs I3n%]%[L23abqoo;ɺ\sq"dA$ R'¤8KKT|rK^FG7Uݤ<&e@u4؀c*78;=8l'l=6>?{̮ +c#}JE8\WX#&h18Jeˠ*wJk|\SG}Ve;oбI~\ N#ԕ5h~2;=CX v+cۆǔmc#pvAGb;&dwdMV4Veb>=Ŋc 50bnG t )pW A;` +I`O*}ݛq@s;l;cCVe|;`wxC)[A ;M8w Q q*;V{lpPF]!@o_ -UheF`{G4z@gzfY!,(B0%IhhV [S nW?$`EX'I7g6mP$ t2cL>89 sz#f8B\BKZl$\Gpt xz~(L3]X K-w }4"ePHvqvz"5۩WA*m!/ƈ|C<~?^mML3ˮ5 I[,ƭ~l$ay*MIIB'Q EA.7nK1rP=$iRr;L,c#,\r/Hڇbm 羋E]%Q2ԡVLEir8݄"MաD;ө0;"(8!’ wP~C'iJ{tG$9;Fb;ȵdC 5zc8sDl< v ~ltͦ,u{\g{D̒հa1AfXF{M{һIn{驓pR9 I8*@Vf#16@L? -?rG; iNAv~ A}1Zqrm!?H!1bLdڂҜJ<$#`i0isg۹Hd=u1@p8ȴO1$L8)3kQv9?6EX;cL{:!.3L94! #{M5!}4䎵7LXH}_r%\ a]Aꋶ,'x k M %H`oLęa-\"Pj栥'&d>h60N5V!^t!rC[0N Y9g(b$XtMҬϒ'yb7d\OX#ϲ%zz_hIK_熓 -ȵK ei$dt{I"^ 0aTݤKFcbeHD/"$iOxk8X>k~.27~I%!5C Rxv`ޢQ?DYZYa76l}qrFRY&ZL~)S4B3滦S|N-ӟxB߭i'wI>WSV<܂DHr<1T 0fSAӥD+EL-ԉ'>BGq>[{\%m-`1m_P}OYF4:ľ3ӣs4ܜIV.nI*JYJsgBa6f}(ON,4a|Z" -tbZ`Q8E%Xk-\?M34˄^A!cMķYL\hqczs0,l I٢Gş 5&2:l]操U}!lddX[)F 9ʌOKxYL[[oxFp'"4N'SQ<3]:{j3Eb|-]y e ^r6@y/q²Vp\Żw; KW2k4~@1re"}il#1;Uf'yCGgl.5Q?'i 1P/f)g <+w%-N 5͌ 9p8&)1Xn9.)<.M6Fb'KOs&ϓegrﲹUvZ🁥3vk}NO˱|:qu" /0qEԩ#`r[̓)[bŲ+6e p:L4D㱶2@l?!NUX5fqU#-vl,O^*I:j{r,'=Bw 稍;hcwq]0GgOVs 4$ _KO`R4I{8Zӛ$mivİZ.svO :GZ׉SEYfDZ .Eb&)Q Ov=>ة%Ⱦ,g{-]E}24W[|ڳ:815#3hRO Z!/uھ ڮz!zG*z >6Nh7FBϛF ^v߯ Ahbco=AP&NLkFB3;;SR(r|s@/00X Ҍ 3h`waA1&qqzjQhǞ8v [-vg9z)9b5AXV8ep)c9>|<2x($!u`$x?į_v⫍(yYfDT؉C<?HC1žcT?WLx&V5t'SSƼ caoL#KK,NI 2iflҷq-a_Zvg!Xr)p<ƍӏ<0GFh3:}1c8 Wpe8 56V3R*hDcB} @@=lR\>B`~k#sԸ jOpF6^Z뤺f=p^x"ai6Pj洟 T6n̬@Ef&&~N wY2̼A3µP'ߥk=RR%`zn&H^ Ԫʉ-};Pk75pZ7u}ԧ~ہNjHRԺ~ok F Qkg1j(I@MmlJ®[@~^^Zɓ+0В$\5P_ORe8j\>PtD>GJTF_f~i*kjހ}}KT> endobj 20 0 obj <> stream xeWn#7+t& `M,# Y>\&sHU^-lnc|<o?|[O~z*oj>}y]m\~{]?<~=u𴬟>.t[wa~Y{7Eax@˼eWW~:]ަOw]y~.})>,X,DXw!XU,2یMel *cW}/vƪ3Xx"T혶3 3d,N52V1 :)3vͿMOq5)>*/1̟hy P37B'P[}G*b5+B$`AOLJԊpT4ȩXrN''[ E u SgCZz78r C"_cEFAb,8i$]j;!WC!ǯzȨ{2fˉjy(:)R)jDU_*TK^K͖ћZ'-[]j?M9ք <זN{ڧNHx" o:e(CFU#kiiZv6 6LX*̴0Te&mI̴aÖJ[R[vkȎ}ImiZʃ bYvbst]ItSDN;GSY9n@lhYu <PO1x9|ˁ1yS=y<<@ECy 9W?͑nhԴo{%Ggy#?R/lR/ T} 1rPd+*P?v{ lJ8؂0tM9ɉ I ;$ RGJqё>K)X?}JLk$5Q}VL=89&j5xsR(I6d>5DOM> endobj 22 0 obj <> stream xݼ xչ7>gf4FcY.'xx8+Id[ؖdgc%@@ -e)RH)[)V (B{RJ^r½=̙3}LEfbt(B٘`R%z ~['v5UƃюoUST'p?SQT]㓱p(NGEMvNE50 dp U5=ƨ_PԞt$8}!n J ~-t9|rjh2s.waqIiYEeUM˚Z;VtuXf݆6m>>;ٱsמs; /CG~}w{C?>?gUah F*VTKUV8~]ڍnx2*)E=qQ05VڕzqJk~qrJ{Am4b e ͢;`q) {mjDrטcjN޼k:(Hv݀87lЮjTs\^*6kA֣c1o9JAa)'\ͰcN Aɱ\kc22䧟)xesh-{]#3g5[d(3xEn] bo#%4UTV pc<HK7O*)m7o&BQm1= PpJtQp,Yc =K7 u 1}q-}VOG%ܖ^mTmY*jvQ/~= (fTkk};vW[tj mW_rÆ\o;F}o19&\- yκ8mUVqu;}|ŏU-Xng~İ>Po"̀SKd-_Kfvqc@;3CW5?|DCٻ3vY:gRXZyv~NF#}ٴ6o8΍: ^|3 ]my뉼 ,Ȉ3֌$CEfM6*K[ oKֆ }N0d7T +I1* beȇ8G?Ə7Mu U<ün^14fgT*F#kE~|1 e wrkdJ.iVeWXW.uv2*:{JAc]&a%¼Kq:x; ^KNW=nh-48V^O?S+3萉6׋_NXMkF]ZMiwU()2DF6-3VB| l Pd3}СLEJk]n6쒪:CCVWeov-28tbkm%O^ /kïtER  @qݻWVUih XCI^qzЗL?J XRI+S}lciHiX]DMFK!%}bO|=AR Q0v[BCuV.׎5J]ކ]<-PVYscm[Fތ- ]P1vlR>?ۉh _LpJt!`,*0џ~1[x|ݑCs,C`usz5]uVś~,gT(ĊgUY]OPQg<6дOe{p yЃZ(ϻ3;,'ѿWѷ 6`c8/`ӳK٦%m^ؐ_wWf Tݠqהּh_{-gɑmaW)v>V[aA;Jsg5q|CAztfrMC '9r|qvl]$CTpfRiՉc \~7Ñ :o>2|&Kut4Oo,QlŞÒ弍?&*Y߱t2]]բ;lpl^bx]+2nv&Win=qjjՇV݉ASpƧlM^9:8i9x=1΢v+at)"p1^@KKSwo[ZȌsY>8a}GH=0!~[b~^62U-1m ;W.-j5v8=[-aozjzѐN&z _H<:>k**(3 rJ$>Dʱ{ݠ ѲKohm,YmKoTe݈J2#fFY监2L[?`ؕ|ֶ]CN8'ҭnG,#,S0-p h9mګ[W\LX&~2Ѵ}XsLziUO{zJpUhUjwV06+kۻLBM7[13羾}}~Xstx1TzV+֎TG_yA}Uߝvȴu%=r1}ɜ;'rxD1'O\ ?Rxzc/;Z,Q>] d6 42@߃A,z~QrnP Jf֥5]ݵ7-JyJ{p΢,֎<@ۧp&Ԓ2\vJXDµjǥl<8M9Jd=:kSsFNף0 OH c <$?_a P}E6,^ ?=nSd6nk$1"(=Aǩ I " b.@6`Ui;b&z7 vztM͉T-820Rg0-%wbEOv = `Gt?Vt hsgݳM]Y RG*#$ǒm >YaH!Z(c%.&!@w]+fV kfyj1P{5lu84Hx0[m.=sᄋcbP*iڍ.Wsnؑ[ijhK}H)\Ռ7m^X_2/Sρkat#I"_LNkA^܊P-5e( ?2.GO#'2zS).j@׸nr(\v;m,z1 1"~zi!CS-
    Z`dN2N'G\R]Gۊ +.RȅHZa6mͲFlnؿ_޿3٘ t2IŠ^J&-+jGgz1g4)}w4`ǒN{Q+w[HӦ4p}iRqmEuoW(A8d~]zW|ֈRRU&s@%WuwiyW[⭶lsMyeYhdѫv/~.P|_qk/|䱙Î5vUKCE4-_qfsǿRH 4 ɵ_xǿsq -k'x7k=DJ"?sM!rg=ZҪ&|" ]Ҹ\K0|{ɹE9c_^~SWU^soW(@ەI&g)UDIX bdwUS:4tEK4:K,/7WDͣ>eUٵc Me̝Ρ-bx-2&y_WSx3q!eU͵ҩ q2v UmԹBNg6m4eU0\&cT!6:qp1Kqi5sHȜɼ2(k#ԅh>ӄ\U>9!څ]{[+.U)e 7dᙟl%<|Jd1s۶ 9r7de@~C6}b,* 59"JO3* >ʨƨ;X-,omwg쎕,堂\5Tu{8#7Päq?WLۺKϪBnHcsЧ1,ՋOAhKJ֥kx[Э;WExpFt (hx.`&2h/d8%qtu.ߵkuǪzt39t6*=z:*kW|C˦F{k5gmls5h◘Z)*|L٢VGбCTJlss=/ײ7JM5t#*|zU =<`I"ޠ~ŕ352eVy }h樊N;K7 {t_?͢-W;?Q53M }D7F,ğd R9IpN!Kn+ Z7=J %"P yVO~9;~yy([}+sOȰƜq]~}y/c Yb+WPv{=-a?v;+YMhB7Ztm,O2Ze>qZ(TNpK'uY2CJCZ+m73&K 7%VO۫ʳ#ױ,h6YdKˎ=۲ܶIWZt~Lw2R<JعkY_l}<4~@˕x*ARBH.D2?Eru\U?hU 57/)͌f6K'K4rowoB3\XT1]TrWO=9r---[_~~?5K|㯕LFHļKEWJϸѦ3ՋؠՕ|qVOOf4ZM✋ddu'9mLGMGH[w62rJB4a% sɠ6ף+6;cbNCe}*˕l(G{J'OtH #(gX)YӔrq>*] EA yu\ϝcvgH,9CJZNԃRޠ(. kF"v" N鮩97r|k֞U A" qKVֻ|2!q 5"q"6GǙ1I}̑E 2H,W='ʊ{l~憟>dnmTYkQ<>&~+DqH5>.{H:wY HѻFްt%97> #z%uu܃.;R^,M|kѠ NoG^9KG8wKy7 #~;KrHO.ʾկ JEx@B,4K[Uh3 YVѫ MsH5dՄS>:qqc s2ΔD r5`g7ć}Ŋ]Ey=Mdm-;sK?軯{ifwGlmHwm=ͭh fpy%%_0ŠOsO+~et.2N8b&.ɐp6yU' q*.3KLnKU|Kܰ-q,~΋.CF=RkP?hl݆Uh6N^ѳWki҆W8vڳ~1}3o*{.ƱRi03Jw|Jxyb.?\z{jnI"RX`\P f- 3,G(aߏdm0~1)]س|Mު%fz5(-f(uEZՏޫͪn59ҙ%} ,[[gBY貹oFUxW?qxFY+ξ{( 8$8k|vp MMQ٬ ]UX%t2a1/Av;XX9k)+>Tăэ1tF{"v} Rk'Tme#Wҩ)@^8r=/魨c[.0شe*luwg H{KP*Fd~ƌ\)ܪ/2\^4+0g{zp^#۝d΋=p"}A^ Tas3\iV1Ԭ>wk֖>ٻ6hMjmwv2oXP1yeB5PL;xȌ..(/{IcTP^}0_+- o!LU!l^!-,#KSM)꽡հ!*qOAxp>}KӕJ!RJ+v(7c,NTY39ݖAE)/6fmL/Bs;zޖA3whW!5'ߣ}{JX=ؾ=B3Yp1ڇ5rC& 5V s%q+EC&6*qE822k"{=B]?Rr,0kVx9. ;l*ƊQjwusSթP_Hlwal@Ż`+u0~\*S?c#NW/.b(hFyE2nhe{~E}tlj-I_ %*1Z-}KwSJ"o=Gggz@%);W.Z,vRFIʷWe3t=]p+V:ʷvͭ~}v`u}vޜcELVTVVo^BST㯫?E۱H62_̕wSH3N$yUځ\+ k4*2.-ڂf^լ̈́,ϭ^faɾT k=&zDc)F<Ӎ kd枛{6ɼKM&ݲIP. DO9׽+.'_Vl>jLk=T >o@c#w|N |. /C~n(窬l}Vf7kNJevaԉ7}^BQ轛؍|ɱLI#K;6"H ^<#=(&y&'}lR1Uinft0tפ겯d-7Cq=4ǜ j,ڹI! 3}YH0仨rɅğn(/3 [sj˺.ΔeOY\{88cSB6w,2Xg:Wz*WO/R8S!+TAHkH%^ÛdOZWoKņRۈ5M\Ԉ.jtd~ӼŒޛMoBg}ƴB{唏i*1_~J޹E5#Nʉy5J= 7y{X[Qe5/_d5\ A4*F'1O0Bzuٝ`xMbMn@W"thou-G~LhuW *j=$ dx{̐.-,*x9} *^ymQ J}J欆G{T Ζߩ[YR /_sh|DF.{v'\I݊J>uzZ2'mrNUxb޷\o+m6\\3wE$g+r9}F֓}Y"u% ߅\0|Pd:a-H_ϟ4gSzͧlZ9(8‘z%,?pvYZc =V\7ѝᛕTS7T-ۿ0d$7QB|KP]ӟ)_/w:~O(|ؓ_=ҡq.ӘM_Ȃ~+S^ aII^߈Ǚ'LFbWZDr&w~=yomirZ7gG-'+i_\zj6SРR3]`xkQg_?0Xyi2.R0^ rx2W`'-Og,F~L~r \UPdA˕vqC<^uV6"hslzU,&|ۍ0ǘAvkrRB[f :ܲUTabewٱŦsQcY$3rU3rp++EUZɤ_6A`}ҿtg7?/S?ߟg4skN|"R%m <)Q je!+ (üA A2vƟ ܌ߌˣ?ON 7tQ hiJ3u7jz6{<Z/K׸Lv+?>c@?qqc 5*7 ѕщgN5nj?nu n[ܨckHP Иwqc/3wsqc^Í6ŏ6Ƈ>;.Dڶ=a~iT-KղP7Q(JleP夎Qn0ڸ,gGRa^ao\ṵOpG}a]. 7)gځ@ / "_ "'J8xn?K]2\w@Ut _č:q_č:qC7F[B041̣p@Qڟz͉Mw 1h#bsԇ(9E5X} }%2gsCKg,͖N{}D*i9{)R|85s=yskJJUV+*oQ>LE*Ym]TWwתR߯~WV^71vmCMf1o5hW;ݢޮ]]XB7];{MC}~~HLi6 y 6|hH}z6c&cw.Z֘:뷭Y߲zf]`;h{kzNE+ٟr~ǝ{xYO[61ҞLgӍu}ү$^T~h4wМH~Q 4^ UA)YJr_AIs}5ExDur_CMA:}=a+-rXHAM>x[ܧ)W3԰,œ}%؞a{O4;#yZoϔזHhxL()*+KWWX9 .~;3[}Bᩨ0r:^a 8B)ae8:2.GљD00FPThOńXlG ``"4`D4K(`*|P$:3 ቨP+oLL*:̅+@br_E_U*/q@F&hh$0!4Eá!k:8"@t c bp8@\DLMXpg't`4uJ& &1Š``&n] ظDv ûfl/F@( hX>1%i!!w s=Ŧv ],ԔwA2U #-&?E"/ =fO`6 wi2ёHh:ECiYo{?<# ]-m=mR/#3uyֆ|_[SwsW%(l  m;B!X$` ߡIЬ0' Y$(^e$< GЬEӑH /13= &;c#`{>86Fl F`bX2`+3!z B$5XV H0G0b, 0c&`"A!:&Dd?i:MC<bQO5( h`fFC3 x(gb) 8 "̞ DŽhxb3 NFO< `G&` XX tL4 pR&0pptNe$ -pǾ!pOFB@ܞ?/#A&w&vX;dK̈Ϟ EDMJ τH0ƛ'v000<2CV! '&&I%Ԣsgw.WN0e `c ?I$`!/C0R-:v O"۱`T#!ݼax&lEe%IB&%+o* KȣY9B&Z0&] M ]?X4D=CQ>U{O某޳;a}$W A!} ͐^ZpX/ C̀ac2AI2 xÑV(d@4n2 SBpj1&i&6J"N.6A qc);"!,E^2iX%LHRŵl90 ޞV(P3OX7S1싈q֠7Z:<#.|&8`o xdAd7˻I1\/ v$B4u[~~[  ȀE +`H`r*IhL(sxАyA ÍNVj0DPCT'Oj߉ޓF0 *”"'z1x8);I,F In|f20U |$sKm"$4d1L8X` z1)B(!?7%I|AGg ^>z61@[# *P)B9L1rg江;{9-/d$x>d/*җ]NY rb58I4R&G"LɰejO+H~-e;ęX=,DJ 'ăS ( Sw9Fp5(sIv0o)·$^:"=,~^<7';杓t!OD 'RMIԔBr! 9X] gʽ{ gȽ(3LO- xL (&i6~,5 'N;ܕhq*9vI@o6s@lmkMp k:;zW 0gpBS:aegOo[ /tvuuzΞխ=˅fxW|ݝ`/yT^ kKK\65wvu 흃=xvXIklY/±i]ںzy@۷syǠA0Կҋ6Ԇ9%]> }T@OZ ̯I,oio }m-ok$v]!mV-@md `=@!^g0 eM@Wh|{/"'W zdX,xTYi^" 0N;0GPajEEb@R#T]ISJ^ѕ0+@MPAj:(5+T==xN#\o=#W1; 3Q ðW<12Iܞ:A? w92.KjtO&Ƨi27FbW_iF<YbJqBMod#E"cR*珓qJ58IGiK8EjTN |ɵJxn6%5X)Ib_?%AYrS2./i' );JSRUcr$tϿ$>YgLN3:?iM) fûE)KCu:s.-ŻSΟ(Z=&OK#2&0@%+tkTS:/$"l#5j5{ 6rهx-H;|[X-2$\XVcT{~} =Ubo|OVwV 9F8-Es3G1dHtJrхd )˴LG49)qHdytƬ&\ML텲z*WX~3ę=̟N9d7Jy]FSOŸaUu{Cm.S?i㫵+T>3# IWYZfHBu)q UD uLQV|ƱzNZx/%o9^VQ܋?W2Z47QwuR@>#{fnǨ3U':'ΉsW>)'NpBuN;[cZ=@7_${0khRud-i~^GIorV.hȪ>ήՄj9ߣKSw|tw](!e &X{ p']FDxW$oWNw̓86%=v/FyW--:2c9$(I3BLO+yﺒJze)*>aww=ҳipD]d~"6M;TH?l"tobTՁ m]Hԕ!d?L+I%wE>hVm47 㟧Bs%52lIJҩ\YC,j"MꕹBi7l!֒u9V~rV!WBVLFV@f.;iy?Y ϭs>n9yN#00VK*`_ X.E)_pZBSR@έ'_@U/5u E2 W }:. JnN*#̫4(\ #e»5nک|@\ApuyYyO~9_Զqwavn0> endobj 25 0 obj <> stream x]UɎ0+r9]B"a 34(l7UгS{UvyU8,;% c?}\~voØICUﮧ)[y㾸aܶl/#Ͻ-]>MՍK..zn^<,WBUXH>:77m۶en?}BDsªf籊E =b0&lE\8k7^#qq_\u_#nb6e_#ő٘[|r IP,}EeMdgn]:TXR,t K%AE ujb@!ް\Q%jj% )(@q@S|0R?0f;GPy_CCÒjE4D-ik#@zOuIékƺE9 FW4KwTw&Nf)."=:o)J:OV%~gĦs\fnPeI *MMBvI-6 DQCc6* H!Ȼy@xrǙ?*M ҴK endstream endobj 26 0 obj <> endobj 27 0 obj <> endobj 28 0 obj <> /ExtGState<> /ProcSet[/PDF/Text/ImageC/ImageI/ImageB] >> endobj 1 0 obj <>/Contents 2 0 R>> endobj 6 0 obj <>/Contents 7 0 R>> endobj 11 0 obj <>/Contents 12 0 R>> endobj 16 0 obj <> endobj 29 0 obj <> endobj 30 0 obj < /Producer /CreationDate(D:20131107230107+02'00')>> endobj xref 0 31 0000000000 65535 f 0000067608 00000 n 0000000019 00000 n 0000005785 00000 n 0000005806 00000 n 0000005983 00000 n 0000067752 00000 n 0000006023 00000 n 0000011388 00000 n 0000011409 00000 n 0000011586 00000 n 0000067896 00000 n 0000011627 00000 n 0000016121 00000 n 0000016143 00000 n 0000016321 00000 n 0000068042 00000 n 0000016362 00000 n 0000045740 00000 n 0000045763 00000 n 0000045962 00000 n 0000047141 00000 n 0000048305 00000 n 0000065858 00000 n 0000065881 00000 n 0000066080 00000 n 0000066790 00000 n 0000067392 00000 n 0000067435 00000 n 0000068155 00000 n 0000068253 00000 n trailer < <0EC24F05E6BD3F2819DE2C22DF5EDDC1> ] /DocChecksum /20C17C7440DED784BA5D3387BCBA4933 >> startxref 68428 %%EOF thawab-4.1/webAppTest.py000077500000000000000000000016671305262755200153000ustar00rootroot00000000000000#! /usr/bin/python # -*- coding: UTF-8 -*- import sys, os, os.path, logging import Thawab.core from Thawab.webApp import webApp, get_theme_dirs prefix=os.path.dirname(sys.argv[0]) th=Thawab.core.ThawabMan() myLogger=logging.getLogger('ThawabWebAppTest') h=logging.StreamHandler() # in production use WatchedFileHandler or RotatingFileHandler h.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")) myLogger.addHandler(h) myLogger.setLevel(logging.INFO) from paste import httpserver lookup=[os.path.join(prefix,'thawab-themes')] lookup.extend(map(lambda i: os.path.join(i, 'themes'), th.prefixes)) app=webApp( th,'web', lookup, th.conf.get('theme','default'), '/_theme/', logger=myLogger ); # for options see http://pythonpaste.org/modules/httpserver.html httpserver.serve(app, host='0.0.0.0', port='8080') # to serve publically #httpserver.serve(app, host='127.0.0.1', port='8080') # to serve localhost thawab-4.1/wiki2th.py000077500000000000000000000014051305262755200145710ustar00rootroot00000000000000#! /usr/bin/python # -*- coding: UTF-8 -*- """ The is wiki importing tool for testing and demonestration of thawab Copyright © 2008, Muayyad Alsadi Released under terms of Waqf Public License. This program is free software; you can redistribute it and/or modify it under the terms of the latest version Waqf Public License as published by Ojuba.org. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The Latest version of the license can be found on "http://waqf.ojuba.org/license" """ import sys, os, os.path from Thawab.wiki import wiki2th for f in sys.argv[1:]: wiki2th(f,'.')