diff --git a/citation_parser_ui.py b/citation_parser_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..fd7ee849ec641de7bc1b849d52721dc4cb1d910b --- /dev/null +++ b/citation_parser_ui.py @@ -0,0 +1,249 @@ +import base64 +import re +import dash +from dash import dcc +from dash import html +from dash import callback_context +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate +from input.interface import InputInterface +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([ + html.Button(id='show-info',children='Show Info',n_clicks=0), + html.Div(id='info-box') + ]), + # 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."]), + style={ + "width": "30%", + "height": "60px", + "lineHeight": "60px", + "borderWidth": "1px", + "borderStyle": "dashed", + "borderRadius": "5px", + "textAlign": "center", + "margin": "10px", + }) + ]), + # 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=[]) + ]), + # Layer 4: For the Graph + html.Div([ + html.Div(id='test-output') + ]) +]) + +@app.callback( + Output('input-checklist','options'), + Output('input-checklist','value'), + Output('input-string','value'), + Output('input-err','children'), + Input('input-string','value'), + Input('clear-all-button','n_clicks'), + Input('clear-selected-button','n_clicks'), + Input('upload-data','contents'), + State('input-checklist','options'), + State('input-checklist','value'), + State('additional-options','value') +) +def update_input_checklist(input_value,btn1,btn2,filecontents,all_inputs,selected_inputs,additional_options): + ''' + 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: + # 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 + 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: + 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: + # 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: + try: + i = InputInterface() + pub = i.get_publication(input_value) + except Exception as err: + return all_inputs,selected_inputs,'','{}'.format(err) + rep_str = pub.contributors[0] + ',' + pub.journal + ',' + pub.publication_date + all_inputs.append({'label':rep_str, 'value':input_value}) + else: + 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(),'','' + +@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: + return html.Div(boxcontent, style={'whiteSpace': 'pre-line'}) + +@app.callback( + Output('test-output','children'), + Input('start-button','n_clicks'), + Input('input-checklist','options'), + Input('input-checklist','value'), + Input('forward-depth','value'), + Input('backward-depth','value'), + State('additional-options','value') +) +def generate_output(n_clicks,all_inputs,selected_inputs, + forward_depth,backward_depth,additional_options): + ''' + 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: + raise PreventUpdate + elif 'Update Automatically' in additional_options \ + or 'start-button' in changed_id: + s = '' + for i in range(len(all_inputs)): + x = all_inputs[i]['value'] + if x in selected_inputs: + s += x*(abs(int(forward_depth)-int(backward_depth))) + else: + s += x*(int(forward_depth)+int(backward_depth)) + return s + else: + raise PreventUpdate + +if __name__ == '__main__': + app.run_server(debug=True) diff --git a/info_box.txt b/info_box.txt new file mode 100644 index 0000000000000000000000000000000000000000..3cb826b85b7336e0083f35a235a197e88f77ee5d --- /dev/null +++ b/info_box.txt @@ -0,0 +1,43 @@ +English + +Show Info: Can be activated and deactivated by clicking on the button. + +Input: input by entering a DOI ("Digital Object Identifier") + +Drag and drop or click to select a file to upload: entering multiple DOI by txt-file is only possible if every DOI has its own line. + +Recursion: + +Clear All: clearing all inputs + +Clear Selected: clearing all selected inputs + +Generate Graph: generates the graph + +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. + + + +German + +Show Info: Durch wiederholtes klicken kann das Fenster ein und aus geblendet werden. + +Input: Die Eingabe erfolgt in Form eines DOI ("Digital Object Identifier") + +Drag and drop or click to select a file to upload: Mehrere DOI in einem txt-Dokument müssen untereinander angeordnet sein. + +Recursion: + +Clear All: alle Eingaben werden gelöscht + +Clear Selected: alle markierten Eingaben werden gelöscht + +Generate Graph: generiert den zugehörigen Graphen + +Update Automatically: automatische Aktualisierung des Graphen nach neuer Eingabe + +Smart Input: direkte Überprüfung der Eingabe auf Richtigkeit zudem wird nicht mehr der DOI angezeigt sondern: Der Autor, Das Journal, Das Veröffentlichungsdatum. + + diff --git a/ui_programm_fragmente/input_to_checklist.py b/ui_programm_fragmente/input_to_checklist.py new file mode 100644 index 0000000000000000000000000000000000000000..3c00ed4c23c7acf914c02af576fec41d8ba2efc7 --- /dev/null +++ b/ui_programm_fragmente/input_to_checklist.py @@ -0,0 +1,160 @@ +import dash +from dash import dcc +from dash import html +from dash import callback_context +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate +from input.interface import InputInterface +import input.publication + +app = dash.Dash(__name__) + +additional_options = ['Update Automatically'] + +app.layout = html.Div([ + # Layer 0: For the Header and Help Function(s) + html.Div([ + html.Button(id='show-info',children='Show Info',n_clicks=0), + html.Div(id='info-box') + ]), + # Layer 1: For all mandatory Inputs + html.Div([ + "Input: ", + dcc.Input(id='input-string', value='', type='text',debounce=True), + dcc.Input(id='forward-depth',value='1',type='number',min='1',max='10'), + dcc.Input(id='backward-depth',value='1',type='number',min='1',max='10') + ]), + # Layer 2: For the checklist, Remove-/Start-Buttons and input-error-message + html.Div([ + dcc.Checklist(id='input-checklist',options=[],labelStyle = dict(display='block'),value=[]), + html.Div(id='input-err',style={'color':'red'}), + html.Button(id='clear-all-button',children='Clear All'), + html.Button(id='clear-selected-button',children='Clear Selected'), + html.Button(id='start-button',children='Generate Graph') + ]), + # Layer 3: For additional Options (e.g. Topological Sort) + html.Div([ + html.H4('Additional Options'), + dcc.Checklist(id='additional-options', + options=[{'label':k,'value':k} for k in additional_options], + value=[]) + ]), + # Layer 4: For the Graph + html.Div([ + html.Div(id='test-output') + ]) +]) + +''' +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'), + Output('input-string','value'), + Output('input-err','children'), + Input('input-string','value'), + Input('clear-all-button','n_clicks'), + Input('clear-selected-button','n_clicks'), + State('input-checklist','options'), + State('input-checklist','value') +) +def update_input_checklist(input_value,btn1,btn2,all_inputs,selected_inputs): + ''' + :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 + ''' + 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 the programm is first started: + if input_value == '': + app.layout['input-checklist'].options.clear() + return list(),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] + if input_value not in currValues: + try: + i = InputInterface() + pub = i.get_pub_light(input_value) + except Exception as err: + return options,selected_inputs,'','{}'.format(err) + rep_str = pub.contributors[0] + ',' + pub.journal + ',' + pub.publication_date + options.append({'label':rep_str, 'value':input_value}) + return options,selected_inputs,'','' + +''' +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): + if n_clicks % 2 == 0: + return '' + else: + return 'Hier koennte Ihre Werbung stehen' + +''' +Basic structure for a callback that generates an output +''' +@app.callback( + Output('test-output','children'), + Input('start-button','n_clicks'), + Input('input-checklist','options'), + Input('input-checklist','value'), + Input('forward-depth','value'), + Input('backward-depth','value'), + State('additional-options','value') +) +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 + ''' + changed_id = [p['prop_id'] for p in callback_context.triggered][0] + if n_clicks is None: + raise PreventUpdate + elif 'Update Automatically' in additional_options \ + or 'start-button' in changed_id: + s = '' + for i in range(len(all_inputs)): + x = all_inputs[i]['value'] + if x in selected_inputs: + s += x*(abs(int(forward_depth)-int(backward_depth))) + else: + s += x*(int(forward_depth)+int(backward_depth)) + return s + else: + raise PreventUpdate + +if __name__ == '__main__': + app.run_server(debug=True) diff --git a/ui_programm_fragmente/upload_to_checklist.py b/ui_programm_fragmente/upload_to_checklist.py new file mode 100644 index 0000000000000000000000000000000000000000..9a094f213901a808ad924f4b1ffa87fb87f2f75d --- /dev/null +++ b/ui_programm_fragmente/upload_to_checklist.py @@ -0,0 +1,78 @@ +import dash +from dash import dcc +from dash import html +from dash.dependencies import Input, Output, State +import base64 +import re + +app = dash.Dash(__name__) + +list_of_inputs = dict() + +app.layout = html.Div([ + html.H4("Add all lines in a file to a list"), + html.Div([ + dcc.Upload( + id="upload-data", + children=html.Div( + ["Drag and drop or click to select a file to upload."] + ), + + style={ + "width": "30%", + "height": "60px", + "lineHeight": "60px", + "borderWidth": "1px", + "borderStyle": "dashed", + "borderRadius": "5px", + "textAlign": "center", + "margin": "10px", + }), + + ]), + dcc.Checklist(id='input-checklist',options=list(),labelStyle = dict(display='block'),value=[]), + +]) + +@app.callback( + Output('input-checklist','options'), + Input('upload-data','filename'), + Input('upload-data','contents'), + State('input-checklist','options') +) +def update_input_list(uploaded_filenames,uploaded_file_contents,all_inputs): + if uploaded_file_contents is not None: + + + string = uploaded_file_contents + + #cutting the first part of the String away to decode + found = base64.b64decode(re.search(',(.+?)$', string).group(1)) + print(found.decode('utf-8')) + + uploaded_file_contents = found.decode('utf-8') + + + list_of_inputs = (uploaded_file_contents.split()) + #das hier sollte es untereinander anzeigen, bekomme ich allerdings nicht auf die Seite... + #return (*list_of_inputs, sep="\n") + + options = all_inputs + if not options: + options = list() + CurrValues = [x['value'] for x in options] + + + # würde auch funktionieren + # return (found.decode('utf-8')) + for i in list_of_inputs: + if i not in CurrValues: + options.append({'label':i, 'value':i}) + + + return options + +if __name__ == '__main__': + app.run_server(debug=True) + +