From 3e34b1c8c183fa3946cb4fc6085f78b516f64562 Mon Sep 17 00:00:00 2001
From: Sebastian David <sebastian.david@uni-hamburg.de>
Date: Sat, 4 Dec 2021 22:16:37 +0100
Subject: [PATCH] Changed some variable names, removed some redundancies and
 expanded code documentation

---
 citation_parser_ui.py        | 147 ++++++++++++++++++++++-------------
 Info Box.txt => info_box.txt |   2 +-
 2 files changed, 95 insertions(+), 54 deletions(-)
 rename Info Box.txt => info_box.txt (95%)

diff --git a/citation_parser_ui.py b/citation_parser_ui.py
index 48de29d..fd7ee84 100644
--- a/citation_parser_ui.py
+++ b/citation_parser_ui.py
@@ -11,8 +11,14 @@ import input.publication
 
 app = dash.Dash(__name__)
 
+# List of options when inputting data and generating the graph
 additional_options = ['Update Automatically','Smart Input']
 
+# Reads the contents of info_box.txt. They can later be displayed by pressing the corresponding button.
+f = open('info_box.txt', 'r')
+boxcontent = f.read()
+f.close()
+
 app.layout = html.Div([
     # Layer 0: For the Header and Help Function(s)
     html.Div([
@@ -22,15 +28,17 @@ app.layout = html.Div([
     # Layer 1: For all mandatory Inputs
     html.Div([
         "Input: ",
+        # A simple box for inputting a string. Value is transmitted upon pressen return or clicking out of the box.
         dcc.Input(id='input-string', value='', type='text',debounce=True),
+        # Forward recursion. Values between 1 and 10 can be entered.
         dcc.Input(id='forward-depth',value='1',type='number',min='1',max='10'),
+        # Backward recursion. Values between 1 and 10 can be entered.
         dcc.Input(id='backward-depth',value='1',type='number',min='1',max='10'),
+        # Upload box. Can be used via drag-and-drop or byclicking on it to open a file viewer. 
         dcc.Upload(
             id="upload-data",
             children=html.Div(
-                ["Drag and drop or click to select a file to upload."]
-            ),
-
+                ["Drag and drop or click to select a file to upload."]),
             style={
                 "width": "30%",
                 "height": "60px",
@@ -44,15 +52,21 @@ app.layout = html.Div([
     ]),
     # Layer 2: For the checklist, Remove-/Start-Buttons and input-error-message
     html.Div([
+        # All input DOIs are collected in this checklist. It is initialized to avoid error messages.
         dcc.Checklist(id='input-checklist',options=[],labelStyle = dict(display='block'),value=[]),
+        # Displays error message if 'Smart Input' is active.
         html.Div(id='input-err',style={'color':'red'}),
+        # Clears the entire list.
         html.Button(id='clear-all-button',children='Clear All'),
+        # Clear all selected elements.
         html.Button(id='clear-selected-button',children='Clear Selected'),
+        # Starts the process that generates a graph.
         html.Button(id='start-button',children='Generate Graph')
     ]),
     # Layer 3: For additional Options (e.g. Topological Sort)
     html.Div([
         html.H4('Additional Options'),
+        # A checklist of all additional options that are listed above.
         dcc.Checklist(id='additional-options',
             options=[{'label':k,'value':k} for k in additional_options],
             value=[])
@@ -63,10 +77,6 @@ app.layout = html.Div([
     ])
 ])
 
-'''
-Most important callback function. Updates the checklist that holds all inputs.
-input-string is required as Output to clear the input box after each input
-'''
 @app.callback(
     Output('input-checklist','options'),
     Output('input-checklist','value'),
@@ -82,49 +92,78 @@ input-string is required as Output to clear the input box after each input
 )
 def update_input_checklist(input_value,btn1,btn2,filecontents,all_inputs,selected_inputs,additional_options):
     '''
-        :param input_value: given by dcc.Input
-        :type input_value: string
-        :param btn1: signals pressing of clear-all-button
-        :param btn2: signals pressing of clear-selected-button
-        :param all_inputs: all labels and values from the checklist,
-            regardless if they have been checked or not
-        :type all_inputs: list of dictionaries with 2 entries each
-        :param selected_inputs: values of all checked elements
-        :type selected_inputs: list of strings
+    Most important callback function. Updates the checklist that holds all inputs.
+    State of the checklist as input is needed so that previews entries are readded.
+    input-string is required as Output to clear the input box after each input.
+    Different actions are performed depending on which input triggered the callback.
+    The value-attribute of input-checklist must be updates so that the values
+    of deleted elements no longer appear in the list of selected elements.
+
+    :param input_value: given by dcc.Input
+    :type input_value: string
+    :param btn1: signals pressing of clear-all-button
+    :type btn1: int
+    :param btn2: signals pressing of clear-selected-button
+    :type btn2: int
+    :param filecontents: the contents of an uploaded file
+    :type filecontents: bit-string
+    :param all_inputs: all labels and values from the checklist,
+        regardless if they have been checked or not
+    :type all_inputs: list of dictionaries with 2 entries each
+    :param selected_inputs: values of all checked elements
+    :type selected_inputs: list of strings
+    :param addtitional_options: all checked additional options
+    :type additional_options: list of strings
     '''
+    # changed_id is used to determine which Input has triggered the callback
     changed_id = [p['prop_id'] for p in callback_context.triggered][0]
+
     # if clear-all-button was pressed:
     if 'clear-all-button' in changed_id:
         return list(),list(),'',''
+    
     # if clear-selected-button was pressed:
     if 'clear-selected-button' in changed_id:
         all_inputs = [i for i in all_inputs if i['value'] not in selected_inputs]
         return all_inputs,list(),'',''
+    
     # when a new element is added via dcc.Input
     if 'input-string' in changed_id:
-        options = all_inputs
-        currValues = [x['value'] for x in options]
+        # Creates a list of previously added inputs to make sure nothing is added twice
+        currValues = [x['value'] for x in all_inputs]        
         if input_value not in currValues:
+
+            # if 'Smart Input' is selected, the input will be checked for validity 
+            # and a more readable string will be returned
             if 'Smart Input' in additional_options:
                 try:
+                    # Attempts to call get_publication. If unsuccesful, 
+                    # the DOI is not added and an error message is returned
                     i = InputInterface()
                     pub = i.get_publication(input_value)
                 except Exception as err:
                     return options,selected_inputs,'','{}'.format(err)
+                # Creates a more readable string to display in the checklist
                 rep_str = pub.contributors[0] + ',' + pub.journal + ',' + pub.publication_date
-                options.append({'label':rep_str, 'value':input_value})
+                all_inputs.append({'label':rep_str, 'value':input_value})
+            
+            # if 'Smart Input' is not selected, the input value is added as is,
+            # without checking for validity.
             else:
-                options.append({'label':input_value,'value':input_value})
-        return options,selected_inputs,'',''
+                all_inputs.append({'label':input_value,'value':input_value})
+        return all_inputs,selected_inputs,'',''
+    
     # when a txt-file is uploaded
     if 'upload-data.contents' in changed_id:
         if filecontents:
-            string = filecontents
-            found = base64.b64decode(re.search(',(.+?)$', string).group(1))
-            filecontents = found.decode('utf-8')
-            list_of_inputs = (filecontents.strip().split('\n'))
-            options = all_inputs
-            CurrValues = [x['value'] for x in options]
+            # Skips the info portion that is added when a file is uploaded
+            found = base64.b64decode(re.search(',(.+?)$', filecontents).group(1))
+            # Returns the binary string into a proper text
+            text = found.decode('utf-8')
+            # Creates a list of inputs by splitting the lines
+            list_of_inputs = (text.strip().split('\n'))
+            CurrValues = [x['value'] for x in all_inputs]
+            # For every line the same actions as for a single input are performed
             for input_value in list_of_inputs:
                 if input_value not in CurrValues:
                     if 'Smart Input' in additional_options:
@@ -132,36 +171,35 @@ def update_input_checklist(input_value,btn1,btn2,filecontents,all_inputs,selecte
                             i = InputInterface()
                             pub = i.get_publication(input_value)
                         except Exception as err:
-                            return options,selected_inputs,'','{}'.format(err)
+                            return all_inputs,selected_inputs,'','{}'.format(err)
                         rep_str = pub.contributors[0] + ',' + pub.journal + ',' + pub.publication_date
-                        options.append({'label':rep_str, 'value':input_value})
+                        all_inputs.append({'label':rep_str, 'value':input_value})
                     else:
-                        options.append({'label':input_value,'value':input_value})
-        return options,selected_inputs,'',''
+                        all_inputs.append({'label':input_value,'value':input_value})
+        return all_inputs,selected_inputs,'',''
     # when the programm is first started:
+    # if this is not done, the input_checklist will be generated 
+    # with one element that contains an empty string
     if input_value == '':
         app.layout['input-checklist'].options.clear()
         return list(),list(),'',''
 
-'''
-This callback shows and hides the (first) help-box
-'''
 @app.callback(
     Output('info-box','children'),
     Input('show-info','n_clicks')
 )
 def show_hide_info_box(n_clicks):
+    '''
+    This callback shows and hides the (first) info-box by, checking how often 
+    the button has been pressed. The text was loaded at the top.
+    :param n_clicks: number of times show-info has been clicked.
+    'type n_clicks: int
+    '''
     if n_clicks % 2 == 0:
         return ''
     else:
-        f = open('Info Box.txt', 'r')
-        boxcontent = f.read()
         return html.Div(boxcontent, style={'whiteSpace': 'pre-line'})
-        f.close()
 
-'''
-Basic structure for a callback that generates an output
-'''
 @app.callback(
     Output('test-output','children'),
     Input('start-button','n_clicks'),
@@ -174,19 +212,22 @@ Basic structure for a callback that generates an output
 def generate_output(n_clicks,all_inputs,selected_inputs,
         forward_depth,backward_depth,additional_options):
     '''
-        :param n_clicks: how often has Generate Graph been clicked
-        :type n_clicks: int
-        :param all_inputs: all labels and values from the checklist,
-            regardless if they have been checked or not
-        :type all_inputs: list of dictionaries with 2 entries each
-        :param selected_inputs: values of all checked elements
-        :type selected_inputs: list of strings
-        :param forward_depth: forward recursion depth
-        :type forward_depth: unsigned int
-        :param backward_depth: backward recursion depth
-        :type backward_depth: unsigned int
-        :param additional_options: value of all selected additional options
-        :type additional_options: list of strings
+    Basic structure for a callback that generates an output. This is only a 
+    proof of concept and has noting to do with the intended output yet.
+
+    :param n_clicks: how often has Generate Graph been clicked
+    :type n_clicks: int
+    :param all_inputs: all labels and values from the checklist,
+        regardless if they have been checked or not
+    :type all_inputs: list of dictionaries with 2 entries each
+    :param selected_inputs: values of all checked elements
+    :type selected_inputs: list of strings
+    :param forward_depth: forward recursion depth
+    :type forward_depth: unsigned int
+    :param backward_depth: backward recursion depth
+    :type backward_depth: unsigned int
+    :param additional_options: value of all selected additional options
+    :type additional_options: list of strings
     '''
     changed_id = [p['prop_id'] for p in callback_context.triggered][0]
     if n_clicks is None:
diff --git a/Info Box.txt b/info_box.txt
similarity index 95%
rename from Info Box.txt
rename to info_box.txt
index b03d2f4..3cb826b 100644
--- a/Info Box.txt	
+++ b/info_box.txt
@@ -14,7 +14,7 @@ Clear Selected: clearing all selected inputs
 
 Generate Graph: generates the graph
 
-Update Automatically: updates Automatically the graph for every new input
+Update Automatically: automatically updates the graph for every new input
 
 Smart Input: checks the correctness of the entered DOI and shows a nicer depiction: Author, Journal, publication date.
 
-- 
GitLab