def yield_verses(mod): from swlib.pysw import VK, TOP vk = VK() vk.Headings(1) vk.setPosition(TOP) #vk.setText("Matthew 1:1") vk.Persist(1) vk.thisown = False mod.setKey(vk) books = ("Genesis", "Matthew") #"Exodus") while not vk.Error(): #while vk.Testament() in '\x00\x01': #while vk.Testament() == '\x00' or vk.Book() == '\x00' or \ # vk.getBookName() in books: yield vk.increment(1)
def get_key(self, module): vk = VK((self.bookname, self.bookname)) vk.Headings(0) vk.setPosition(TOP) return vk
class Book(object): is_verse_keyed = False is_dictionary = False is_genbook = False chapter_view = False type = None category = None categories_to_exclude = () def __init__(self, parent, version=""): self.parent = parent self.mod = None self.observers = observerlist.ObserverList() self.cleanup_module = observerlist.ObserverList() self.template = VerseTemplate(body="$text") self.templatelist = [self.template] self.vk = VK() self.headings = False if self.ModuleExists(version): self.SetModule(version) else: mods = self.GetModuleList() if mods: self.SetModule(mods[0]) else: dprint(WARNING, "No modules of type", self.type) self.SetModule(None) def SetModule(self, modname, notify=True): """Sets the module to modname""" oldmod = self.mod # No book at all if modname is None: self.mod = None elif isinstance(modname, SW.Module): self.mod = modname else: # look up the book new_mod = self.parent.get_module(modname) if not new_mod: return False self.mod = new_mod self.features = None if self.mod != oldmod and notify: self.observers(self.mod) return True def ModuleExists(self, modname): return modname in self.GetModuleList() @property def version(self): if self.mod: return self.mod.Name() def GetModuleList(self): return sorted([ name for name, mod in self.parent.modules.iteritems() if (mod.Type() == self.type or self.type is None) and ( not self.category or (self.category and mod.getConfigEntry( "Category") == self.category)) and mod.getConfigEntry( "Category") not in self.categories_to_exclude ], key=lambda name: name.lower()) def GetModules(self): return sorted([ mod for name, mod in self.parent.modules.iteritems() if (mod.Type() == self.type or self.type is None) and ( not self.category or (self.category and mod.getConfigEntry( "Category") == self.category)) and mod.getConfigEntry( "Category") not in self.categories_to_exclude ], key=lambda mod: mod.Name().lower()) @staticmethod def get_template_options(): items = { "$": _("A $ sign"), "range": _("The range of verses"), "version": _("The version this is taken from"), "description": _("A description of the version"), } body_items = { "text": _("The text of a verse"), "versenumber": _("The verse number"), "reference": _("The reference for each verse"), "bookname": _("The name of the current book"), "bookabbrev": _("A shorter abbreviation of the book name"), "chapternumber": _("The number of the chapter in the book") } heading_items = {"heading": _("The text of the heading")} body_items.update(items) heading_items.update(body_items) return dict(body=body_items, headings=heading_items, header=items, footer=items) def GetReference(self, ref, specialref="", specialtemplate=None, context="", max_verses=177, raw=False, stripped=False, template=None, display_tags=None, exclude_topic_tag=None, end_ref=None, headings=False, verselist=None, remove_extra_whitespace=False): """GetReference gets a reference from a Book. specialref is a ref (string) which will be specially formatted according to specialtemplate. exclude_topic_tag: If this is not None, then it is a topic that should not have a tag generated, because it is obvious from the context (for example, the topic window for that topic). """ #only for bible keyed books if not self.mod: return None raw = raw or display_options.options["raw"] if template is None and self.templatelist: template = self.templatelist[-1] if context: lastverse = context else: lastverse = "" if display_tags is None: # if we don't have tags in, don't calculate them as it can be # expensive if "$tags" not in template.body.template: display_tags = False else: display_tags = passage_list.settings.display_tags assert not (verselist and end_ref), \ "No end ref with a listkey!!!" if end_ref: ref += " - " + end_ref old_headings = self.vk.Headings(headings) if not verselist: verselist = self.vk.ParseVerseList(to_str(ref), to_str(lastverse), True) # if they pass in a verselist, they can also pass in the ref they # would like to go along with it. This can be useful if it also # includes headings that shouldn't be seen rangetext = GetBestRange(ref, context=context, userInput=False, userOutput=True, headings=headings) internal_rangetext = GetBestRange(ref, context=context, headings=headings) if rangetext == "": self.vk.Headings(old_headings) #if invalid reference, return empty string return u"" if specialref: specialref = GetVerseStr(specialref) description = to_unicode(self.mod.Description(), self.mod) d = dict(range=rangetext, internal_range=internal_rangetext, version=self.mod.Name(), description=description) text = template.header.safe_substitute(d) verses = [] for body_dict, headings in self.GetReference_yield( verselist, max_verses, raw, stripped, exclude_topic_tag=exclude_topic_tag, display_tags=display_tags, ): # if we have exceeded the verse limit, body_dict will be None if body_dict is None: verses.append(config.MAX_VERSES_EXCEEDED() % max_verses) break body_dict.update(d) t = template if specialref == body_dict["internal_reference"]: t = specialtemplate verse = t.preverse.safe_substitute(body_dict) for heading_dict in headings: verse += t.headings.safe_substitute(heading_dict) verse += t.body.safe_substitute(body_dict) verses.append(verse) self.vk.Headings(old_headings) text += template.finalize(u''.join(verses)) text += self.end_of_render text += template.footer.safe_substitute(d) return text def GetReference_yield(self, verselist, max_verses=177, raw=False, stripped=False, module=None, exclude_topic_tag=None, display_tags=True, skip_linked_verses=True): """GetReference_yield: yield the body dictionary and headings dictinoary for each reference. Preconditions: one of module or self.mod is not None verselist is valid""" #only for bible keyed books verselist.setPosition(TOP) verselist.Persist(1) u_vk = pysw.UserVK() u_vk.Headings(1) versekey = SW.VerseKey() versekey.Headings(1) mod = module or self.mod old_mod_skiplinks = mod.getSkipConsecutiveLinks() mod.setSkipConsecutiveLinks(True) mod.SetKey(verselist) verses_left = max_verses render_text, render_start, render_end = self.get_rendertext(mod) if render_start: render_start() try: incrementer = mod if skip_linked_verses else verselist while not self.has_error(incrementer): if verses_left == 0: yield None, None break if not skip_linked_verses: mod.SetKey(verselist) key = mod.getKey() #versekey = VK.castTo(key) versekey.setText(key.getText()) #if(self.headings): # versekey.Headings(1) osisRef = versekey.getOSISRef() internal_reference = versekey.getText() rawentry = mod.getRawEntryBuf() if skip_linked_verses and not rawentry.length(): # don't include empty text; typically this may be at the # start of the chapter or something... incrementer.increment(1) continue start_verse = end_verse = versekey.Verse() # look forwards and backwards to see what the linked verse # number is (e.g. 3-5). Note: currently this won't cross # chapter boundaries vk = versekey.clone() vk = versekey.castTo(vk) vk.thisown = True vk.Headings(0) while (vk.Error() == '\x00' and vk.Chapter() == versekey.Chapter() and mod.isLinked(vk, versekey)): end_verse = vk.Verse() vk.increment(1) vk.copyFrom(versekey) vk.Headings(0) # hopefully we won't see anything backwards, but it is # possible (i.e. if we start in the middle of a linked # verse while (vk.Error() == '\x00' and vk.Chapter() == versekey.Chapter() and mod.isLinked(vk, versekey)): start_verse = vk.Verse() vk.decrement(1) if start_verse == end_verse: verse = "%d" % start_verse else: verse = "%d-%d" % (start_verse, end_verse) u_vk.setText(internal_reference) if internal_reference.endswith(":0"): if start_verse != end_verse: print "WARNING: unhandled linked verse with verse 0" if versekey.Chapter() == 0: reference = u_vk.getBookName() else: reference = u_vk.get_book_chapter() else: reference = u_vk.get_book_chapter() reference += ":" + verse body_dict = dict( # text comes later versenumber=process_digits(verse, userOutput=True), chapternumber=process_digits(str(versekey.Chapter()), userOutput=True), booknumber=ord(versekey.Book()), bookabbrev=versekey.getBookAbbrev(), bookname=versekey.getBookName(), reference=reference, internal_reference=internal_reference, osisRef=osisRef, ) # usually RenderText flushes this out, but we haven't called # that yet - but we definitely don't want extraneous headings mod.getEntryAttributesMap().clear() # we want to do our pre-verse content first, but we can't # without running it through the optionFilter first. # we'll then have to run it through again after, otherwise our # entryattributes may go walkabout if raw: rawentry_str = rawentry.c_str() mod.optionFilter(rawentry, versekey) if raw: option_filtered = rawentry.c_str() headings = self.get_headings(internal_reference, mod) #versekey = VK.castTo(key) heading_dicts = [] raw_headings = [] for heading, canonical in headings: # the new-style pre-verse content lives wrapped up in # <div>'s - it will contain the <title>, but the div will # be stripped out. # the old-style pre-verse content lives in <title>'s, # which will also be stripped out. Unfortunately, it isn't # that easy to tell whether it did have a title, so we # employ a heuristic - if it starts with an <, it is a new # one... nh = heading.startswith("<") if stripped: heading = mod.StripText(heading).decode( "utf8", "replace") else: heading = render_text(heading).decode( "utf8", "replace") if not nh: cls = " canonical" if (canonical and canonical == "true") else "" heading = '<h2 class="heading%s">%s</h2>\n' % (cls, heading) if raw: raw_headings.append(heading) heading_dict = dict(heading=heading, canonical=canonical) heading_dict.update(body_dict) heading_dicts.append(heading_dict) if stripped: text = mod.StripText(rawentry.c_str(), rawentry.length()).decode( "utf-8", "replace") else: # we can't use rawentry due to a static local buffer in # swmodule.c; breaks gospel harmonies text = ( render_text( #rawentry.c_str(), rawentry.length() ).decode("utf8", "replace")) # get our actual text if raw: text = self.process_raw(rawentry_str, text, versekey, mod, raw_headings, option_filtered) user_comments = self.get_user_comments(osisRef, versekey) # XXX: This needs to be done better than this. Move into # subclass somehow. if isinstance(self, Bible) and display_tags: tags = self.insert_tags(osisRef, versekey, exclude_topic_tag) else: tags = "" body_dict.update( dict(text=text, tags=tags, usercomments=user_comments)) yield body_dict, heading_dicts incrementer.increment(1) verses_left -= 1 finally: mod.setKey(SW.Key()) mod.setSkipConsecutiveLinks(old_mod_skiplinks) self.end_of_render = "" if render_end: self.end_of_render = render_end() def get_user_comments(self, osis_ref, verse_key): if not isinstance(self, Bible): return u"" manager = passage_list.get_primary_passage_list_manager() comments = u"".join( self.get_user_comment_div(passage_entry) for passage_entry in manager.get_all_passage_entries_for_verse(verse_key) if (self.get_tag_type_to_show(passage_entry) == "usercomment")) return u'<span class="usercomment_container" osisRef="%s">%s</span>' % ( osis_ref, comments) def get_user_comment_div(self, passage): passage_id = passage.get_id() return '<a class="usercomment" href="usercomment://%(passage_id)d" passageEntryId="%(passage_id)d"><sup>†</sup></a>' % locals( ) def insert_tags(self, osis_ref, verse_key, exclude_topic_tag): """Generates and returns all the passage tags for the given verse.""" manager = passage_list.get_primary_passage_list_manager() passage_tags = "".join( self.get_passage_topic_div(passage) for passage in manager.get_all_passage_entries_for_verse(verse_key) if (self.get_tag_type_to_show(passage, exclude_topic_tag) == "passage_tag")) return u'<span class="passage_tag_container" osisRef="%s">%s</span>' % ( osis_ref, passage_tags) def get_tag_type_to_show(self, passage, exclude_topic_tag=None): topic = passage.parent if (topic is not None and topic.parent is not None and topic is not exclude_topic_tag): if topic.can_display_tag: return "passage_tag" elif topic is passage_list.get_primary_passage_list_manager( ).comments_special_topic: return "usercomment" return None def get_passage_topic_div(self, passage): from gui import passage_tag topic_text = " > ".join(passage.parent.topic_trail) look, colour = passage.parent.resolve_tag_look() topic_id = passage.parent.get_id() passage_id = passage.get_id() return """ <a href="passagetag://passage/%(topic_id)d/%(passage_id)d" class="passage_tag passage_tag_%(colour)d_%(look)d" passageEntryId="%(passage_id)d"> <div> %(topic_text)s </div> </a> """ % locals() def get_headings(self, ref, mod=None): """Gets an array of the headings for the current verse. Must have just called RenderText on the verse you want headings for""" mod = mod or self.mod heading = SW.Buf("Heading") preverse = SW.Buf("Preverse") interverse = SW.Buf("Interverse") canonical = SW.Buf("canonical") headings = [] heading_types = [preverse, interverse] attrmap = mod.getEntryAttributesMap() #[SW.Buf("Heading") if heading in attrmap: h = attrmap[heading] if preverse in h: i = 0 p = h[preverse] while True: is_canonical = "false" i_buf = SW.Buf(str(i)) # try to see if this is a canonical heading # unfortunately, if there happens to be a interverse # heading after a canonical heading, it will overwrite it # so we may not get the correct answer. This oughtn't to # matter overly much if i_buf in h: attributes = h[i_buf] if (canonical in attributes and attributes[canonical].c_str() == "true"): is_canonical = "true" if i_buf in p: headings.append((p[i_buf].c_str(), is_canonical)) else: break i += 1 if not headings: dprint(WARNING, "no heading found for", ref) return headings def GetReferences(self, ref, context="", max_verses=-1): """Gets a list of references. In: ref - list of references context: context of first in list Out: A list of verses """ # TODO: If we have a list passed in like this: # ref= ['104:6', '8, 10, 105:1', '3'], context = 'PS 104:14' # for second item, GetVerseStr will return Psalms 104:8, instead # of Psalms 105:1, so the third retrieved will be 104:3, not 105:3 # Fixes: # Make GetVerseStr take last in list, not first as optional parameter results = [] lastref = context for j in ref: #get text results.append( self.GetReference(j, context=lastref, max_verses=max_verses)) # set context for next ref lastref = GetVerseStr(j, lastref) return results def GetFootnoteData(self, mod, passage, number, field): if mod != self.mod: if not isinstance(mod, SW.Module): mod = self.parent.get_module(mod) if mod is None: return None else: mod = self.mod vk = SW.Key(passage) mod.SetKey(vk) #set passage mod.RenderText() # force entry attributes to get set data = mod.getEntryAttributesMap()[SW.Buf("Footnote")] \ [SW.Buf(number)][SW.Buf(field)].c_str() # put it through the render filter before returning it return mod.RenderText(data).decode("utf8", "replace") def GetReferenceFromMod(self, mod, ref, max_verses=-1): oldmod = self.mod if not self.SetModule(mod, notify=False): return None try: verses = self.GetReference(ref, max_verses=max_verses) finally: self.SetModule(oldmod, notify=False) return verses def GetReferencesFromMod(self, mod, ref, context="", max_verses=-1): oldmod = self.mod if not self.SetModule(mod, notify=False): return None try: verses = self.GetReferences(ref, context, max_verses=max_verses) finally: self.SetModule(oldmod, notify=False) return verses def GetChapter(self, ref, specialref="", specialtemplate=None, context="", raw=False): self.vk.setText(to_str(ref, self.mod)) #get first ref text = self.vk.getText() match = re.match("([\w\s]+) (\d+):(\d+)", text) if match: book, chapter, verse = match.group(1, 2, 3) # include introductions - book introduction if necessary ref = "%s %s" % (book, chapter) text = "%s %s:0-%s %s" % (book, chapter, book, chapter) vk = SW.VerseKey() vk.Headings(1) list = vk.ParseVerseList(text, "", True) if chapter == "1": vk.setText("%s 0:0" % book) list.add(vk) #text = "%s 0:0-%s %s" % (book, book, chapter) if book == "Genesis": vk.Testament(0) list.add(vk) vk.Testament(1) list.add(vk) elif book == "Matthew": # set it to 0 first so that we come back to the testament # heading vk.Testament(0) vk.Testament(2) list.add(vk) list.sort() else: dprint(ERROR, "Couldn't parse verse text", text) return "" return self.GetReference(ref, specialref, specialtemplate, context, raw=raw, headings=True, verselist=list) def get_rendertext(self, mod=None): """Return the text render function. This makes sure that plaintext modules render whitespace properly""" module = mod or self.mod render_text = module.RenderText start = finish = None if module.getConfigEntry("SourceType") in (None, "Plaintext"): def render_text(*args): text = module.RenderText(*args) text = cgi.escape(text) return '<span class="plaintext">%s</span>' % text else: if ord(module.Markup()) == SW.FMT_OSIS: start = osisparser.p.block_start finish = osisparser.p.block_end return render_text, start, finish def has_feature(self, feature, module=None): if module is not None: oldmod = self.mod try: self.SetModule(module, notify=False) return self.has_feature(feature) finally: self.SetModule(oldmod, notify=False) if not self.mod: return False if self.features is None: self.features = [] mod = self.mod map = mod.getConfigMap() feature_buf = SW.Buf("Feature") featureBegin = map.lower_bound(feature_buf) featureEnd = map.upper_bound(feature_buf) while featureBegin != featureEnd: v = featureBegin.value() self.features.append(v[1].c_str()) featureBegin += 1 return feature in self.features def get_cipher_code(self, mod): """Return the cipher key for the module. This will be empty if locked, non-empty if unlocked and None if not enciphered""" return mod.getConfigEntry("CipherKey") def unlock(self, mod, key): assert self.get_cipher_code(mod) != None cm = mod.getConfigMap() cm[SW.Buf("CipherKey")] = SW.Buf(key) mgr = self.get_mgr(mod) mgr.setCipherKey(mod.Name(), key) conf = self.get_config(mod) conf.set(mod.Name(), "CipherKey", key) conf.Save() # send a refresh through for this book # TODO: do this better self.observers(self.mod) conf = self.get_config(mod) if conf.get(mod.Name(), "CipherKey") != key: raise FileSaveException( _("Couldn't save cipher key. You will have to set it again when you restart" )) def has_chapter(self, ref, mod=None): assert self.is_verse_keyed, "Calling has_chapter for non-verse keyed module." module = mod or self.mod if module is None: return False vk = VK(ref) if module.hasEntry(vk): return True vk.setVerse(1) if module.hasEntry(vk): return True module.setKey(vk) try: old_mod_skiplinks = module.getSkipConsecutiveLinks() module.setSkipConsecutiveLinks(True) module.increment() next_vk = VK.castTo(module.getKey()) return (next_vk.Book() == vk.Book() and next_vk.Chapter() == vk.Chapter() and next_vk.Testament() == vk.Testament()) and not self.has_error(module) finally: module.setSkipConsecutiveLinks(old_mod_skiplinks) def get_mgr(self, mod): for path, mgr, modules in self.parent.mgrs: if mod in [m for name, m in modules]: return mgr return None def get_config(self, mod): pp = mod.getConfigEntry("PrefixPath") pp += "mods.d/%s.conf" % mod.Name().lower() # make sure it exists os.stat(pp) return SW.Config(pp) def process_raw(self, text, rendered_text, key, module, headings=(), option_filtered=""): kt = key.getOSISRefRangeText() or key.getText() kt = to_unicode(kt, module) if headings: headings = "<ul class='raw-headings'>%s</ul>" % ('\n'.join( "<li>%s</li>" % cgi.escape(heading) for heading in headings)) else: headings = "" if option_filtered: option_filtered = "<pre class='raw-option-filtered'>%s</pre>" % cgi.escape( option_filtered.decode("utf-8", "replace")) return u""" %s <div class='debug-raw-details' key='%s'> <pre class='raw-rendered'>%s</pre> %s <pre class='raw'>%s</pre> %s </div>""" % (rendered_text, cgi.escape(kt), cgi.escape(rendered_text), headings, cgi.escape(text.decode("utf-8", "replace")), option_filtered) @staticmethod def has_error(module): ERR_OK = chr(0) return (module.Error() != ERR_OK)