#!/usr/bin/env python # coding: utf-8 # # A subtle bug in A.pretty() # # 2019-04-12 # # Christiaan Erwich, Gyusang Jin and Cody Kingham spotted weird behaviour in displaying certain verses. # # Look here in Psalm 18:49 : # # ![ps18:49](images/Ps18-49.png) # # The dashed circles enclose the words that are in this verse, the other words belong to the previous part. # # When displaying the sentence atom, which started in the previous part, Text-Fabric *forgot* to remember to # cutoff this sentence atom at the verse boundary. # # And why was that? # # The root cause is in a few lines in the generic TF display library, not in the BHSA TF-app. # # ``` # getBoundary(api, n) if d.condenseType is None else # ``` # # Here TF computes the boundary slots of the node in question. # The thing is, if there is a condense type active, we should not take the boundary of the node itself, but its container of the # condense type. # # However, in our case we have the situation that `d.condenseType` appeared to be `verse`. # Yet, there is no condensing, because `d.condensed` is `False`. # # So this part of the condition was wrong and should be: # # ``` # getBoundary(api, n) if not d.condensed or not d.condenseType else # ``` # # [See the bug on GitHub](https://github.com/annotation/text-fabric/blob/421e7b577f00cbc782ca6ac9f3a132688071f59f/tf/applib/display.py#L435) # # This indeed fixed the bug, as you see below. # In[1]: get_ipython().run_line_magic('load_ext', 'autoreload') get_ipython().run_line_magic('autoreload', '2') # In[2]: from tf.applib.helpers import dm from tf.app import use # Note that we are developing/debugging code. We do not want to download code/data from github, # and we want to look in our own github clone of the TF app. # # Hence `'bhsa:clone'` and `checkout='local'`. # In[3]: A = use('bhsa:clone', checkout='local', hoist=globals()) # # Inspection # # Before we knew what was the matter we made a lens to have a detailed view of our data: # # We gathered all word nodes of Ps 18:47 - 49 and made a table of the objects they are contained in. # In[4]: v0 = T.nodeFromSection(('Psalms', 18, 47)) v1 = T.nodeFromSection(('Psalms', 18, 49)) w0 = L.d(v0, otype='word')[0] w1 = L.d(v1, otype='word')[-1] allWords = range(w0, w1 + 1) # We are only interested in certain kind of objects. # In[5]: skipTypes = {'book', 'chapter', 'half_verse', 'lex', 'subphrase', 'word'} nodeTypes = [x[0] for x in C.levels.data if x[0] not in skipTypes] nodeTypes # We represent the nodes of different types in slightly different ways. # In[6]: repNode = dict( verse=lambda n: '{} {}:{}'.format(*T.sectionFromNode(n)), sentence=lambda n: F.number.v(n), sentence_atom=lambda n: F.number.v(n), clause=lambda n: F.number.v(n), clause_atom=lambda n: F.number.v(n), phrase=lambda n: F.number.v(n), phrase_atom=lambda n: F.number.v(n), ) # And we make a markdown table. # In[7]: rows = [] rows.append(' | '.join(nodeTypes)) rows.append(' | '.join(['---'] * len(nodeTypes))) for w in allWords: parents = {F.otype.v(p): repNode[F.otype.v(p)](p) for p in L.u(w) if F.otype.v(p) in nodeTypes} rows.append(' | '.join(str(parents.get(nType, 'x')) for nType in nodeTypes)) dm('\n'.join(rows)) # This gave us the clue that we had a bunch of objects that crossed the verse boundary. # # After the fix # # We show the verse after the fix. # In[8]: n = T.nodeFromSection(('Psalms', 18, 49)) # In[9]: A.plain(n) # In[10]: A.pretty(n) # Other example: # In[11]: s1 = 1222671 A.show(((s1,),)) # In[ ]: