Exploring the collation of Calpurnius Flaccus

In this notebook, I present an interactive method to manipulate the collation of the Declamations of Calpurnius Flaccus obtained with CollateX.

Understanding the data

First it is necessary to understand the structure of the collation data. Here you can find the generic description of json structures. An object (dictionary) is made of one or more pairs of name/value. The relation name/value is similar to the relation between a word and its definition in a dictionary. An array (list) is an ordered list of values. A json object corresponds to a python dictionary, and a json array to a python list. Lists can be combined: a table is a list of lists.

There are two objects in the json collation, which are converted into their equivalent in python:

  • a list of witnesses ("witnesses")
  • a table with the aligned text versions ("table")

The list of witnesses works as a header for the table, which can be itself described as:

  • a table is a list of rows
  • a row is a list of cells
  • a cell is a list of tokens
  • a token is a dictionary with the following entries:
    • t : exact word as it appears in the witness
    • n : optional normalized version of the word
    • locus : optional exact location of the word in the manuscript/edition. Folio/page number, followed by line number
    • note : optional comment
    • decl: optional declamation number
    • link : optional link to the page of a digital facsimile, where the token appears

The order of the rows is important, because it follows the order of the text. Row 0 has the first word(s) of the text, whereas the last row has also the last word(s) of the text. The row's number is called ID number, and will be used later for variant location identification.

Contents

  1. Import Data. The first step is to import the collation results into python.

  2. Functions. Then we will create functions to filter the data according to our need. For instance, we may want to find all the unique variants of one witness. Or we would like to see the places where one group of witnesses agrees with each other against another group of witnesses. Some functions will allow us to diplay only a section of the collation, or information specific to one variant location. Including:

    1. find agreements
    2. table to html
    3. move tokens
    4. add/delete note
    5. search
    6. save
  3. Exploration of collation. Finally, we will use our functions to explore the collation of Calpurnius Flaccus, with interactive widgets:

    1. Modify collation (move tokens, add/del rows, add/del notes, save the new json)
    2. Find agreements between witnesses, and save the result into a more complex html file
    3. Search the collation
    4. Clarify a reading
  4. Summary. Summary of all interactions gathered in one widget for ease of use.

Users may go straight to the summary to start exploring the collation of Calpurnius Flaccus. For those who would prefer to see the code behind the widgets and the various interactions, you can read through the functions or the interactive widgets.

Notebook re-use

Any JSON output from collateX, with tokens "t". The other elements are optional.

Some transformations may be needed. For instance:

  • In collation import data : change the base base text. If you choose to have no base text, change also the description of the "save_to_html" function.
  • In the HTML template, title and credits should be updated. The transformation into complex HTML could also be adapted, according to the json model of the new user.

Possible Improvements

  • large amounts of modifications (moving many tokens at the same time)
  • find and delete empty rows automatically
  • prevent deletion of non empty rows
  • avoid moving first/last tokens
  • edit notes directly
  • save file with popup window
  • etc.

IMPORT DATA

Python modules import


In [1]:
#python modules
import json
import sys
import re
from IPython.display import display,HTML
from datetime import datetime

#ipywidgets modules
from __future__ import print_function
from ipywidgets import *
import ipywidgets as widgets

Collation import


In [2]:
#path to the file with json results of the collation
path = 'json-collations/calpurnius-collation-joint-BCMNPH.json'
#path = 'json-collations/calpurnius-collation-joint-BCMNPH-corr.json'

#open the file
with open (path, encoding='utf-8') as jsonfile:
    #transform the json structure (arrays, objects) into python structure (lists, dictionaries)
    data = json.load(jsonfile)

#list of witnesses
witnesses = data["witnesses"]
print(witnesses)

#table of the aligned text versions
collation = data["table"]

#base text: choose a witness which variants are considered true readings (in green)
#for Calpurnius, the most recent edition of Hakanson is used as the base text
#if you do not want a base text, set it as an empty string ''
base_text = 'LH'

#the index of a witness is its position in the witness list:
#for instance B1 has position 0, and P1594 has position 9.


['B1', 'B2', 'C1', 'C2', 'LH', 'M1', 'M2', 'N1', 'N2', 'P1594']

FUNCTIONS

Transform a cell c (list of tokens) into a string of text


In [3]:
#original text
def cell_to_string(c):    
    #tokens t are joined together, separated by a space
    string = ' '.join(token['t'] for token in c)
    return string

#text with normalized tokens
def cell_to_string_norm(c):    
    string = ''
    #word division is not taken into account when comparing the normalized text
    #for this reason we do not add a space in between tokens
    for token in c:
        if 'n' in token:
            string += token['n']
        elif 't' in token:
            string += token['t']
    return string

Compare cells


In [4]:
#compare two cells, original text
def compare_cell(c1,c2):
    return cell_to_string(c1) == cell_to_string(c2)

#compare two cells, normalized text
def compare_cell_norm(c1,c2):
    return cell_to_string_norm(c1) == cell_to_string_norm(c2)    

#compare a list of cells, original text
#return true if all the cells are equivalent (they contain the same string of tokens)    
def compare_multiple_cell(cell_list):
    #compare each cell to the next
    for c1,c2 in zip(cell_list, cell_list[1:]):
        if compare_cell(c1,c2) is False:
            comparison = False
            break
    else:
        comparison = True
    return comparison

#compare a list of cells, normalized text
#return true if all the cells are equivalent (they contain the same string of tokens)
def compare_multiple_cell_norm(cell_list):
    #compare each cell to the next
    for c1,c2 in zip(cell_list, cell_list[1:]):
        if compare_cell_norm(c1,c2) is False:
            comparison = False
            break
    else:
        comparison = True
    return comparison

Find agreements 1


In [5]:
#this function returns rows of the collation table (table) where a list of x witnesses (witlist) agree together.
#we display only variant locations, and not places where all witnesses agree.

def find_agreements(table, witlist):
    result_table = []
    
    #transform widget tuple into actual list
    witlist = list(witlist)
    
    #transform the witnesses names (sigla) into indexes
    witindex = [witnesses.index(wit) for wit in witlist]
    nonwitindex = [witnesses.index(wit) for wit in witnesses if wit not in witlist]

    for row in table:
        #get list of cell for the x witnesses
        cell_list = [row[i] for i in witindex]
        #there must be agreement of the x witnesses (normalized tokens)
        if compare_multiple_cell_norm(cell_list) is True:
            for i in nonwitindex:
                #if they disagree with at least one of the others
                if compare_cell_norm(row[witindex[0]],row[i]) is False:
                    #add row to the result
                    result_table.append(row)
                    #and go to next row
                    break

    return result_table

Find agreements 2


In [6]:
#This function is similar to the previous one:
#it returns rows of the collation table (table) where a list of x witnesses (witlist) agree together, but
#do not agree with the witnesses in a second list (nonwitlist).

#By default, the function will return the agreement of the x witnesses, against all the other witnesses.

def compare_witnesses(table, witlist, nonwitlist=[]):
    result_table = []
    
    #first list of x witnesses which agree together
    witindex = [witnesses.index(wit) for wit in witlist]
    #against all the other witnesses
    if not nonwitlist:
        nonwitindex = [witnesses.index(wit) for wit in witnesses if wit not in witlist]
    #except if a second list of y witnesses is specified
    else:
        nonwitindex = [witnesses.index(wit) for wit in nonwitlist]
    
    #go through the collation table, row by row
    #to find places where the x witnesses agree together against others
    for row in table:
        #get list of cell for the x witnesses
        cell_list = [row[i] for i in witindex]
        #there must be agreement of the x witnesses (normalised tokens)
        if compare_multiple_cell_norm(cell_list) is True:
            for i in nonwitindex:
                #if they agree with one of the other y witnesses
                if compare_cell_norm(row[witindex[0]],row[i]) is True:
                    #go to next row
                    break
            #but if they do not agree with any of the y witnesses        
            else:
                #add row to the result
                result_table.append(row)
    return result_table

Find all variants in the collation table


In [7]:
def view_variants(table):
    result_table = []
    #go through the collation table, row by row
    for row in table:
        #if there is a variant in the row (i.e. at least one cell is different from another cell, normalized form)
        if compare_multiple_cell_norm(row) is False:
            #add row to the result
            result_table.append(row)       
    return result_table

Transform the result table into an html table


In [8]:
#this function returns a minimal HTML table, to display in the notebook.

def table_to_html(collation,table):
    
    #table in an HTML format
    html_table = ''
    #div is for a better slides view. For notebook use, comment it out
    #html_table += '<div style="overflow: scroll; width:960; height:417px; word-break: break-all;">'
    html_table += '<table border="1" style="width: 100%; border: 1px solid #000000; border-collapse: collapse;" cellpadding="4">'
    
    #add a header to the table with columns, one for each witnesses and one for the row ID
    html_table += '<tr>'
    #a column for each witness
    for wit in witnesses:
        html_table += '<th>'+wit+'</th>'
    #optional: column for the declamation number
    #html_table += '<th>Decl</th>'
    #column for the row id
    html_table += '<th>ID</th>'
    html_table += '</tr>'
    
    for row in table:
        #add a row to the html table
        html_table += '<tr>'
        #optional : a variable to store the declamation number (will not be defined in empty rows)
        #declamation = 0
        #fill row with cell for each witness
        for cell in row:
            #transform the tokens t into a string.
            #we display the original tokens, not the normalized form
            token = cell_to_string(cell)
            
            #some cells are empty. Thus the declamation number is only available in cell with at least 1 token
            #if len(cell)>0:
            #    declamation = str(cell[0]['decl'])
            
            #if no base text is selected, background colour will be white
            if not base_text:
                bg = "white"
            #if the tokens are the same as the base text tokens (normalized form)
            #it is displayed as a "true reading" in a green cell
            elif compare_cell_norm(cell,row[witnesses.index(base_text)]):
                bg = "d9ead3"
            #otherwise it is diplayed as an "error" in a red cell
            else:
                bg = "ffb1b1"
            html_table += '<td bgcolor="'+bg+'">'+token+'</td>'
        
        #optional: add declamation number  
        #html_table += '<td>'+str(location)+'</td>'
        
        #add row ID
        html_table += '<td>'+str(collation.index(row))+'</td>' 
        
        #close the row
        html_table += '</tr>'
    
    #close the table
    html_table += '</table>'
    #html_table += '</div>'

    return html_table

In [9]:
#this function returns a fancier HTML, but can't be displayed in the notebook (yet)

def table_to_html_fancy(collation,table):
    #table in an HTML format
    html_table = '<table>'
    
    #add a header to the table with columns
    html_table += '<thead><tr>'
    #a column for each witness
    for wit in witnesses:
        html_table += '<th>'+'<p>'+wit+'</p>'+'</th>'
    #a column for the row id
    html_table += '<th><p>ID</p></th>'
    #close header
    html_table += '</tr></thead><tbody>'
    
    for row in table:
        #add a row to the html table
        html_table += '<tr>'
        for cell in row:
            #transform the tokens t into a string (original token) 
            token = cell_to_string(cell)
            
            #if there is no base text
            if not base_text:
                #arbitrary class for the HTML cells. It will have no effect on the result.
                cl = "foo"
            #if the normalized token is the same as the base text
            #it is diplayed as a "true reading" in a cell with green left border
            elif compare_cell_norm(cell,row[witnesses.index(base_text)]):
                cl = "green"
            #otherwise as an "error" in a cell with an orange left border
            else:
                cl = "orange"
            #add token to the table, in a text paragraph    
            html_table += '<td class="'+cl+'">'+'<p>'+token
            
            #if there is a note to display, add a little 'i' to indicate there is more hidden information
            for t in cell:
                #in the cell, if we find a token with a note
                if 'note' in t:
                    #add info indicator
                    html_table += ' <a href="#" class="expander right"><i class="fa fa-info-circle"></i></a>'
                    #then stop (even if there are several notes, we display only one indicator)
                    break

            #close the text paragraph in the cell
            html_table += '</p>'
            
            #add paragraphs for hidden content (notes. Not limited to notes only: normalized form could be added, etc.)
            for t in cell:
                if 'note' in t:
                    html_table += '<p class="expandable hidden more-info">Note: '+t['note']+'</p>'
            
            #when the cell is not empty, add hidden info of page/line numbers. Adapted to make 'locus' optional
            if len(cell)>0 and 'locus' in cell[0]:
            #if len(cell)>0 :
                #add link to images when possible
                if 'link' in cell[0]:
                    url = cell[0]['link']
                    html_table += '<p class="expandable-row hidden more-info"><a target="blank" href='+url+'>'+cell[0]['locus']+'</a></p>'
                else:
                    html_table += '<p class="expandable-row hidden more-info">'+cell[0]['locus']+'</p>'
            
            #close cell
            html_table += '</td>'
        
        #add row ID with indicator of hidden content
        html_table += '<td>'+'<p>'+str(collation.index(row))+' <a href="#" class="expander-row right"><i class="fa fa-ellipsis-v"></i></a></p>'+'</td>' 
        #close the row
        html_table += '</tr>'
    #close the table
    html_table += '</tbody></table>'
    
    return html_table

To read a short passage quickly. The collation table is reversed so that each witness is displayed on a row instead of a column.


In [10]:
def print_witnesses_text(table):
    reverse_table = [[row[i] for row in table] for i in range(len(witnesses))]
    for index,row in enumerate(reverse_table):
        text = ''
        for cell in row:
            #the row starts and ends with a token, not a space
            if row.index(cell) == 0 or text == '' or not cell:
                text += cell_to_string(cell)
            #if it is not the start of the string or an empty cell, add a space to separate tokens
            else:
                text += ' '+cell_to_string(cell)
        text += ', '+str(witnesses[index])
        print(text)
    #return reverse_table

In [11]:
def print_info(rowID, wit):
    #select cell
    cell = collation[rowID][witnesses.index(wit)]
    #if cell is empty, there is no token
    if len(cell) == 0:
            print('-')
    else:
        for token in cell:
            #position of token in cell + content
            print(cell.index(token), ':', ', '.join(token[feature] for feature in token))

Get the ID of a row


In [12]:
def get_pos(row):
    return collation.index(row)

Move a token to the previous (up) or next (down) row


In [13]:
def move_token_up(rowID, wit):
    try:
        #the token cannot be in the first row 
        rowID > 0
        #select the first token
        token = collation[rowID][witnesses.index(wit)].pop(0)
        #append it at the end of the cell in the previous row
        collation[rowID-1][witnesses.index(wit)].append(token)
        print("Token '"+token['t']+"' moved up!")
    except:
        print("There is no token to move.")

def move_token_down(rowID, wit):
    try:
        #the token cannot be in the last row
        rowID < len(collation)-1
        #select the last token
        token = collation[rowID][witnesses.index(wit)].pop()
        #add it at the beginning of the cell in the next row
        collation[rowID+1][witnesses.index(wit)].insert(0, token)
        print("Token '"+token['t']+"' moved down!")
    except:
        print("There is no token to move.")

Add / delete a row


In [14]:
def add_row_after(rowID):
    #rowID must be within collation table
    if rowID < 0 or rowID > len(collation)-1:
        print('Row '+str(rowID)+' does not exist.')
    else:
        #create an empty row
        new_row = []
        #for each witness in the collation
        for wit in witnesses:
            #add an empty list of tokens to the row
            new_row.append([])
        #insert new row in the collation, after the row passed in argument (+1)
        collation.insert(rowID+1, new_row)
        print('Row added!')
    
def delete_row(rowID):
    #rowID must be within collation table
    if rowID < 0 or rowID > len(collation)-1:
        print('Row '+str(rowID)+' does not exist.')
    else:
        collation.pop(rowID)
        print('Row deleted!')

Add / Delete a note


In [15]:
#add or modify a note
def add_note(wit, rowID, token, note):
    try:
        #select token
        t = collation[rowID][witnesses.index(wit)][token]     
        if note is '':
            print('Your note is empty.')
        elif 'note' in t:
            #add comment to an already existing note
            t['note'] += ' '+note
        else:
            #or create a new note
            t['note'] = note
    except:
        print('This token is not valid.')

#delete completely a token's note            
def del_note(wit, rowID, token):
    try:
        #select token
        t = collation[rowID][witnesses.index(wit)][token]
        if 'note' in t:
            #delete note    
            t.pop('note')
        else:
            #or print error message
            print('There is no note to delete')
    except:
        print('This token is not valid.')

Search


In [16]:
def search(table,text):
    #result table to build
    result_table = []
    
    #go through each row of the collation table
    for row in table:
        #go through each cell
        for cell in row:
            #if the search text matches the cell text (original or normalized form)
            if text in cell_to_string_norm(cell) or text in cell_to_string(cell):
                #add row to the result table
                result_table.append(row)
                #go to next row
                break
    
    #if the result table is empty, the text was not found in the collation
    if result_table == []:
        print(text+" was not found!")
    
    return result_table

Save results


In [17]:
#save the json file with update in the collation table
def save_json(path, table):
    #combine new collation table with witnesses, so as to have one data variable
    data = {'witnesses':witnesses, 'table':table}
    #open a file according to path
    with open(path, 'w') as outfile:
        #write the data in json format
        json.dump(data, outfile)

In [18]:
#save a subset of the collation table into fancy HTML version, with a small text description
def save_table(descr, table, path):
    #path to template
    template_path = 'alignment-tables/template.html'
    
    #load the text of the template into a variable html
    with open(template_path, 'r', encoding='utf-8') as infile:
        html = infile.read()
    
    #add base text to description
    if base_text:
        descr += '<br>Agreement with the base text '+base_text+' is marked in green.'
        descr += ' Variation from '+base_text+' is marked in red.' 
        
    #modify template: replace the comment with descr paragraph
    html = re.sub(r'<!--descr-->',descr,html)
    
    #modify template: replace the comment with table
    html = re.sub(r'<!--table-->',table,html)
    
    #save
    with open(path, 'w', encoding='utf-8') as outfile:
        outfile.write(html)

INTERACTIVE EXPLORATION OF THE COLLATION TABLE

Update the collation results

In this section, we will see how to view a selection of the collation table and update it.

Possible updates:

  1. Add/delete a row
  2. Move one token up/down
  3. Add notes
  4. Save

Note : it is only possible to move the last token (or word) in a cell down to the first place in the next cell; or vice-versa to move the first token of a cell up to the last place in the previous cell. This is to prevent any change in the word order of a witness, and to keep the correct text sequence.


In [19]:
#select an extract of the collation table with interactive widgets

#widget for HTML display
w1_html = widgets.HTML(value="")

#define the beginning of extract
w_from = widgets.BoundedIntText(
    value=6,
    min=0,
    max=len(collation)-1,
    description='From:',
    continuous_update=True,
)

#define the end of extract
#because of python list slicing, the last number is not included in the result.
#to make it more intuitive, the "to" number is added +1 in collation_extract function
w_to = widgets.BoundedIntText(
    value=11,
    min=0,
    max=len(collation)-1,
    description='To:',
    continuous_update=True,
)

#binding widgets with table_to_html function
def collation_extract(a, b):
    x = a
    y = b+1
    if y <= x:
        print("The table you have requested does not exist.")
    w1_html.value = table_to_html(collation,collation[x:y])

#uncomment the next lines to see the widgets
##interactive selection of a collation table extract
#interact(collation_extract, a=w_from, b=w_to)
##display HTML widget (rows 6-11)
#display(w1_html)

In [20]:
#Widgets for:
#move tokens up/down
#add/delete rows
#add/delete notes to a specific token

#widget to select a witness        
w_wit = widgets.Dropdown(
    options = witnesses,
    description = 'Witness:',
)
#widget to select a row
w_rowID = widgets.BoundedIntText(
    min=0,
    max=len(collation)-1,
    description='ID:',
)
#widget to select a specific token
w_token = widgets.Text(
    min=0,
    description = 'Token position:',
)
#widget to enter text note
w_note = widgets.Text(
    description = 'Note:',
)

out = widgets.Output()

#link buttons and functions
@out.capture(clear_output=True)#wait=True, clear_output=True
def modif_on_click(b):
    if b.description == 'add row after':
        #add row
        add_row_after(rowID=w_rowID.value)
    if b.description == 'delete row':
        #delete
        delete_row(rowID=w_rowID.value)
    if b.description == 'move token down':
        move_token_down(rowID=w_rowID.value, wit=w_wit.value)
    if b.description == 'move token up':
        move_token_up(rowID=w_rowID.value, wit=w_wit.value)

In [21]:
#add row after
b1 = widgets.Button(description="add row after", 
                    style=ButtonStyle(button_color='#fae58b'))
b1.on_click(modif_on_click)

#uncomment the next line to see the widget
#interact_manual(add_row_after, rowID=w_rowID, {'manual': True, 'manual_name': 'add row after'})

In [22]:
#delete row
b2 = widgets.Button(description="delete row", 
                    style=b1.style)
b2.on_click(modif_on_click)

#uncomment the next line to see the widget
#interact_manual(delete_row, rowID=w_rowID, {'manual': True, 'manual_name': 'delete row'})

In [23]:
#move token down
b3 = widgets.Button(description="move token down", 
                    style=b1.style)
b3.on_click(modif_on_click)

#uncomment the next line to see the widget
#interact_manual(move_token_down, rowID=w_rowID, wit=w_wit, {'manual': True, 'manual_name': 'move token down'})

In [24]:
#move token up
b4 = widgets.Button(description="move token up", 
                    style=b1.style)
b4.on_click(modif_on_click)

#uncomment the next line to see the widget
#interact_manual(move_token_up, rowID=w_rowID, wit=w_wit, {'manual': True, 'manual_name': 'move token up'})

In [25]:
#add/delete notes
#link add button and function
@out.capture(clear_output=True)
def add_on_click(b):
    add_note(wit=w_wit.value, rowID=w_rowID.value, token=w_token.value, note=w_note.value)
    #check result
    print('Result:')
    print_info(w_rowID.value, w_wit.value)
    print('\n')

#add a note button
w_add_note = widgets.Button(description='Add note', button_style='success')   
w_add_note.on_click(add_on_click)

#link del button and function
@out.capture(clear_output=True)
def del_on_click(b):
    del_note(wit=w_wit.value, rowID=w_rowID.value, token=w_token.value)
    #check result
    print('Result:')
    print_info(w_rowID.value, w_wit.value)

#delete a note button
w_del_note = widgets.Button(description='Delete note', button_style='danger')
w_del_note.on_click(del_on_click)

#dislpay widgets
#uncomment the next line to see the widgets
#display(w_wit, w_rowID, w_token, w_note)
#display(w_add_note, w_del_note)

In [26]:
#save new json

#path to the new file
path_new_json = 'json-collations/calpurnius-collation-joint-BCMNPH-corr.json'

#alternative path: take the original collation file name, and add a date/time identifier
#file_name = os.path.split(path)[1]
#file_id = datetime.now().strftime('%Y-%m-%d-%H%M%S')
#path_new_json = 'json-collations/'+file_id+'-'+file_name

#save button to click
w_button = widgets.Button(description="Save JSON", button_style='info')

#on click
def on_button_clicked(b):
    #save json of the whole collation
    save_json(path_new_json, collation)

#link btw button and onclick function 
w_button.on_click(on_button_clicked)

#save json
#uncomment the next line to see the widget
#display(w_button)

Find agreements of witnesses against others

In the first drop-down menu, you will be able to choose one or more witness(es) which agree together in places where all the other witnesses have a different reading. It means that, if you pick only one witness, you will see it's unique variants.

On the other hand, you may want to see the agreements of witnesses against another group of witnesses selected in the second drop-down menu. This allows you for instance to ignore modern editors from the comparison. This option also allows to compare for groups of witnesses to see if they share erroneous readings (or innovations), for instance.

Finally, if you chose only one witness in each dropdown menu, you will be able to see the differences between the two witnesses.


In [27]:
#widget for HTML display
w2_html = widgets.HTML(value="")

#selection of a group of witnesses which share the same readings
w1 = widgets.SelectMultiple(
    description="Agreements:",
    options=witnesses
)

#selection of a secong group of witnesses
w2 = widgets.SelectMultiple(
    description="Against:",
    options=witnesses
)

def collation_compare(table, a, b):
    #transform widget tuple into actual list
    if isinstance(a, (tuple)):
        witlist = list(a)
        nonwitlist = list(b)
    else:
        witlist = [a]
        nonwitlist = [b]
    if not a:
        print("No witness selected.")
    else:
        #create the result table
        result = compare_witnesses(table, witlist, nonwitlist)
        #transform table to HTML
        html_table = table_to_html(table,result)
        #add an indication of the number of rows in the result table
        html_table += '<span>Total: '+str(len(result))+' rows in the table.</span>'
        #set HTML display value
        w2_html.value = html_table

#-----------
#save button
w_save = widgets.Button(description="Save Table", button_style='info')

#description of the table
w_descr = widgets.Text(value="Table description")

def on_button_clicked(x):
    #transform widget tuple into actual list
    if isinstance(w1.value, (tuple)):
        witlist = list(w1.value)
        nonwitlist = list(w2.value)
    else:
        witlist = [w1.value]
        nonwitlist = [w2.value]
    if not w1.value:
        print("No table to save.")
    else:
        #path for new result file
        file_id = datetime.now().strftime('%Y-%m-%d-%H%M%S')
        path_result = 'alignment-tables/collation-'+file_id+'.html'
        #description
        descr = str(w_descr.value)
        #html table
        table = table_to_html_fancy(collation,compare_witnesses(collation, witlist, nonwitlist))
        #save
        save_table(descr, table, path_result)

#link button with saving action
w_save.on_click(on_button_clicked)
#---------------

#find agreements between witnesses or unique readings
#uncomment the next line to see the widgets
#interact(collation_compare, table=fixed(collation), a=w1, b=w2)

#display(w2_html)
#display(w_descr)
#display(w_save)

Search the collation

Basic search. It will check for token t or normalized form n, and return rows where there is at least one match.


In [28]:
#widget for HTML display
w3_html = widgets.HTML(value="")

#do the search
def search_collation(table,text):
    w3_html.value = table_to_html(table,search(table,text))
    
#search collation with interactive text input
#uncomment the next line to see the widgets
#interact(search_collation, table=fixed(collation),text="calpurnius",__manual=True)
#display(w3_html)

Clarify one reading


In [29]:
#Examples: 459/C1, 932/M1, 9/LH
#uncomment the next line to see the widget
#interact(print_info, rowID=w_rowID, wit=w_wit)

Summary of Widgets

Here you will find all possible interactions gathered into one tab widget. It is a widget that displays several pages in tabs. each page is dedicated to a specific interaction:

  1. Extract: select a collation extract
  2. Modifications: update the collation by adding/deleting rows, moving tokens or adding/deleting notes
  3. Agreements: select groups of witnesses to see what readings they have in common, against other witnesses
  4. Search: search the collation for a single reading, for which you can see more information in the last page
  5. Clarify: see more information about one reading, i.e. all properties of the tokens in one witness, from a specific row

In [30]:
#Using the tab widget, gather all interactions in one place
tab = widgets.Tab()

#page 1 = view extract
w_extract = interactive(collation_extract, a=w_from, b=w_to)
page1 = widgets.VBox(children = [w_extract, w1_html])

#page 2 = modify the collation
w_modif1 = widgets.VBox(children=[w_rowID, b1, out])#add row
w_modif2 = widgets.VBox(children=[w_rowID, b2, out])#delete row
w_modif3 = widgets.VBox(children=[w_rowID, w_wit, b3, out])#move token down
w_modif4 = widgets.VBox(children=[w_rowID, w_wit, b4, out])#move token up
w_modif5 = widgets.VBox([w_wit, w_rowID, w_token, w_note, 
                         widgets.HBox(children=[w_add_note, w_del_note]), out])#add/del notes
                       
accordion = widgets.Accordion(children=[w_modif1, w_modif2, w_modif3, w_modif4, w_modif5])
accordion.set_title(0, 'Add Row')
accordion.set_title(1, 'Delete Row')
accordion.set_title(2, 'Move Token Down')
accordion.set_title(3, 'Move Token Up')
accordion.set_title(4, 'Notes')
accordion.selected_index = None
page2 = widgets.VBox(children = [accordion, w_button])

#page 3 = find agreements
w_agr = interactive(collation_compare, table=fixed(collation), a=w1, b=w2)
page3 = widgets.VBox(children = [w_agr, w2_html, w_descr, w_save])

#page 4 = search
w_search = interactive(search_collation, {'manual' : True, 'manual_name' : 'Search'}, table=fixed(collation),text="calpurnius")
page4 = widgets.VBox(children = [w_search, w3_html])

#page 5 = clarify
w_clar = interactive(print_info, rowID=w_rowID, wit=w_wit)
page5 = widgets.VBox(children = [w_clar])

tab.children = [page1, page2, page3, page4, page5]
tab.set_title(0, 'Extract')
tab.set_title(1, 'Modifications')
tab.set_title(2, 'Find Agreements')
tab.set_title(3, 'Search')
tab.set_title(4, 'Clarify')

display(tab)