Skip to content

Commit 14af1a4

Browse files
committed
Text2Excel 2.5.0
- The codes of the program divided to several files - One bug solved to handle PatternError. It raises when you enter invalid regex patterns.
1 parent 8b96cef commit 14af1a4

15 files changed

+1122
-864
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
build/build
2-
build/dist
2+
build/dist
3+
4+
**/__pycache__/

build/extractors.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import openpyxl, re, os, csv
2+
from openpyxl.utils import get_column_letter
3+
from utils import *
4+
5+
from typing import TypeAlias
6+
from openpyxl.worksheet.worksheet import Worksheet
7+
8+
ExtractedData : TypeAlias = tuple[tuple[str, ...], ...]
9+
Patterns : TypeAlias = list[str]
10+
11+
class CSVFileExtractor:
12+
def __init__(self, col_var):
13+
self.col_var = col_var
14+
15+
def create_csv_file(self, output_file : str, patterns : Patterns ,content : str) -> str | None:
16+
extracted_data = DataExtractor.extract_data(patterns,content)
17+
18+
if WithLogging.with_logging:
19+
extracted_data_copy = extracted_data
20+
21+
if self.col_var.get():
22+
extracted_data = DataExtractor.create_column_order(extracted_data)
23+
24+
with open(output_file,'a',newline='',encoding=ENCODING) as f:
25+
writer = csv.writer(f)
26+
writer.writerows(extracted_data)
27+
28+
if WithLogging.with_logging:
29+
return DataExtractor.log_found_data(extracted_data_copy)
30+
31+
class ExcelFileExtractor:
32+
def __init__(self, col_var, exact_var):
33+
self.col_var = col_var
34+
self.exact_var = exact_var
35+
36+
@staticmethod
37+
def find_max(index : int, sheet : Worksheet) -> int:
38+
row = 0
39+
for i in sheet.iter_rows(min_col=index,max_col=index):
40+
if i[0].value is not None:
41+
row = i[0].row
42+
return row
43+
44+
@staticmethod
45+
def put_data_in_excel_without_exact_order(extracted_data : ExtractedData, sheet : Worksheet) -> None:
46+
for data_list in extracted_data:
47+
sheet.append(data_list)
48+
49+
@staticmethod
50+
def get_cell(pattern_letter : str, row_number : int):
51+
return pattern_letter + str(row_number)
52+
53+
@staticmethod
54+
def put_data_in_excel_with_exact_order(extracted_data : ExtractedData, sheet : Worksheet) -> None:
55+
column_letters_list = [get_column_letter(i) for i in range(1,len(extracted_data)+1)]
56+
57+
find_max_index = 1
58+
columns_list_index = 0
59+
60+
for data_list in extracted_data:
61+
row_number = ExcelFileExtractor.find_max(find_max_index,sheet) + 1
62+
for item in data_list:
63+
sheet[ExcelFileExtractor.get_cell(column_letters_list[columns_list_index],row_number)] = item
64+
row_number += 1
65+
columns_list_index += 1
66+
find_max_index += 1
67+
68+
def create_excel_file(self, output_file : str, sheet_name : str, patterns : Patterns, content : str) -> str | None:
69+
if not os.path.isfile(output_file):
70+
wb = openpyxl.Workbook()
71+
wb.save(output_file)
72+
wb.close()
73+
74+
sheet_name = sheet_name.title()
75+
76+
wb = openpyxl.load_workbook(output_file)
77+
78+
if sheet_name in wb.sheetnames:
79+
sheet = wb[sheet_name]
80+
else:
81+
sheet = wb.create_sheet(sheet_name)
82+
83+
extracted_data = DataExtractor.extract_data(patterns,content)
84+
85+
if WithLogging.with_logging:
86+
extracted_data_copy = extracted_data
87+
88+
if self.col_var.get() and not self.exact_var.get():
89+
extracted_data = DataExtractor.create_column_order(extracted_data)
90+
91+
if not self.exact_var.get(): # The codes in this if statement will not be executed if 'put in rows' is enabled
92+
ExcelFileExtractor.put_data_in_excel_without_exact_order(extracted_data,sheet)
93+
else:
94+
ExcelFileExtractor.put_data_in_excel_with_exact_order(extracted_data,sheet)
95+
96+
wb.save(output_file)
97+
wb.close()
98+
99+
if WithLogging.with_logging:
100+
return DataExtractor.log_found_data(extracted_data_copy)
101+
102+
class DataExtractor:
103+
def __init__(self, excel_var, log_text, col_var, exact_var):
104+
self.log_text = log_text
105+
self.excel_var = excel_var
106+
107+
self.excel_extractor = ExcelFileExtractor(col_var, exact_var)
108+
self.csv_extractor = CSVFileExtractor(col_var)
109+
110+
@staticmethod
111+
def extract_data(patterns : Patterns, content : str) -> ExtractedData:
112+
extracted_data = []
113+
for pattern in patterns:
114+
data_list = re.findall(pattern,content)
115+
extracted_data.append(data_list)
116+
117+
return extracted_data
118+
119+
def prepare_to_extract_data(self, output_file : str, input_file : str, sheet_name : str, patterns : Patterns) -> None:
120+
try:
121+
assert patterns, 'There is no patterns to extract data'
122+
123+
with open(input_file,encoding=ENCODING) as f:
124+
try:
125+
content = f.read()
126+
except UnicodeDecodeError:
127+
raise ValueError('The input file cannot be a binary file')
128+
129+
assert output_file, 'The name of output file is required.'
130+
131+
output_file_extention = os.path.splitext(output_file)[1].lower()
132+
133+
if self.excel_var.get():
134+
if output_file_extention in ['.xlsx', '.xlsm', '.xltx', '.xltm']:
135+
log_string = self.excel_extractor.create_excel_file(output_file,sheet_name,patterns,content)
136+
else:
137+
raise ValueError('The output file format is not supported. It should be .xlsx, .xlsm, .xltx or .xltm')
138+
139+
else:
140+
log_string = self.csv_extractor.create_csv_file(output_file,patterns,content)
141+
142+
if WithLogging.with_logging:
143+
log_string += f'\n{output_file!r} saved.' + '\n'
144+
self.log_text.config(state='normal')
145+
self.log_text.delete('1.0','end')
146+
self.log_text.insert('end', log_string)
147+
self.log_text.config(state='disabled')
148+
self.log_text.see('end')
149+
150+
except (FileNotFoundError, AssertionError, PermissionError, ValueError, re.PatternError) as err:
151+
show_error(err)
152+
153+
@staticmethod
154+
def log_found_data(extracted_data_copy : ExtractedData) -> str:
155+
log_string = ''
156+
157+
for data_list in extracted_data_copy:
158+
log_string += '\n'.join(data_list) + '\n'
159+
160+
return log_string
161+
162+
@staticmethod
163+
def create_column_order(extracted_data : ExtractedData) -> tuple[tuple[str]]:
164+
max_len = max([len(data_list) for data_list in extracted_data])
165+
166+
for data_list in extracted_data:
167+
for _ in range(max_len - len(data_list)):
168+
data_list.append('')
169+
170+
return tuple(zip(*extracted_data))

build/gui/context_menu_creators.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from tkinter.ttk import Entry
2+
from tkinter import Menu
3+
from utils import *
4+
from gui.menu_commands import *
5+
6+
class CommandsObjects:
7+
log_menu_commands = None
8+
patterns_menu_commands = None
9+
csv_excel_switch_functions = None
10+
11+
def create_commands_objects(log_menu,log_text , window, patterns_list, exact_var,
12+
exact_cb, exact_cb_substitute_lbl, sheet_name_lbl,
13+
sheet_name_entry, col_var, excel_var):
14+
15+
CommandsObjects.csv_excel_switch_functions = CSVExcelSwitchFunctions(exact_var, exact_cb, exact_cb_substitute_lbl,
16+
sheet_name_lbl, sheet_name_entry, col_var, excel_var)
17+
CommandsObjects.log_menu_commands = LogMenuCommands(log_menu, window, log_text)
18+
CommandsObjects.patterns_menu_commands = PatternsMenuCommands(patterns_list, window)
19+
20+
def browse_files(widget : Entry, is_input_file_entry : bool) -> None:
21+
TITLE = 'Browse'
22+
23+
if is_input_file_entry:
24+
file_path = filedialog.askopenfilename(title=TITLE)
25+
else:
26+
file_path = filedialog.askopenfilename(title=TITLE,filetypes=FILE_TYPES)
27+
28+
if file_path:
29+
widget.delete(0,'end')
30+
widget.insert('end',file_path)
31+
32+
class MenuCreators:
33+
def create_patterns_menu() -> Menu:
34+
menu = Menu(tearoff=False,**MENU_COLOR_ARGS)
35+
menu.add_command(label='Add Pattern', command=CommandsObjects.patterns_menu_commands.add_pattern,accelerator='Ctrl+Shift+A')
36+
menu.add_command(label='Insert Pattern',command=CommandsObjects.patterns_menu_commands.insert_pattern,accelerator='Ctrl+I')
37+
menu.add_separator()
38+
menu.add_command(label='Edit selected', command=CommandsObjects.patterns_menu_commands.edit_selected,accelerator='F2')
39+
menu.add_command(label='Delete selected', command=CommandsObjects.patterns_menu_commands.delete_selected,accelerator='Delete')
40+
menu.add_command(label='Copy selected', command=CommandsObjects.patterns_menu_commands.copy_pattern,accelerator='Ctrl+C')
41+
menu.add_command(label='Delete All', command=CommandsObjects.patterns_menu_commands.delete_all,accelerator='Ctrl+Shift+D')
42+
menu.add_command(label='Copy All', command=lambda : CommandsObjects.patterns_menu_commands.copy_pattern(all=True),accelerator='Ctrl+Shift+C')
43+
menu.add_separator()
44+
menu.add_command(label='Import from file', command=CommandsObjects.patterns_menu_commands.import_from_file,accelerator='Ctrl+Shift+I')
45+
menu.add_command(label='Export to file', command=CommandsObjects.patterns_menu_commands.export_to_file,accelerator='Ctrl+E')
46+
return menu
47+
48+
def create_log_menu() -> Menu:
49+
menu = Menu(tearoff=False,**MENU_COLOR_ARGS)
50+
menu.add_command(label='Copy log',command=CommandsObjects.log_menu_commands.copy_log,accelerator='Ctrl+C')
51+
menu.add_command(label='Clear log',command=CommandsObjects.log_menu_commands.clear_log,accelerator='Ctrl+D')
52+
menu.add_separator()
53+
menu.add_command(label=LOG_MODE[0],command=CommandsObjects.log_menu_commands.toggle_log)
54+
return menu
55+
56+
def create_entry_menu(widget : Entry, excel_var, is_file_entry : bool=True,is_output_file_entry : bool=True) -> Menu:
57+
menu = Menu(tearoff=False,**MENU_COLOR_ARGS)
58+
menu.add_command(label='Select All', accelerator='Ctrl+A',command=lambda : widget.select_range(0,'end'))
59+
menu.add_command(label='Copy', accelerator='Ctrl+C',command=lambda : widget.event_generate('<<Copy>>'))
60+
menu.add_command(label='Paste', accelerator='Ctrl+V',command=lambda : widget.event_generate('<<Paste>>'))
61+
menu.add_command(label='Cut', accelerator='Ctrl+X',command=lambda : widget.event_generate('<<Cut>>'))
62+
menu.add_separator()
63+
menu.add_command(label='Clear',accelerator='Ctrl+Shift+C',command=lambda : widget.delete(0,'end'))
64+
if is_file_entry:
65+
menu.add_command(label='Browse',command=lambda : browse_files(widget, not is_output_file_entry),accelerator='Ctrl+B')
66+
if is_output_file_entry:
67+
menu.add_separator()
68+
menu.add_radiobutton(label='Excel',variable=excel_var,value=True,command=CommandsObjects.csv_excel_switch_functions.show_only_excel_required_widgets)
69+
menu.add_radiobutton(label='CSV',variable=excel_var,value=False,command=CommandsObjects.csv_excel_switch_functions.hide_only_excel_required_widgets)
70+
return menu

build/gui/context_menu_displayers.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from tkinter import Menu, Event
2+
3+
class ContextMenuDisplayers:
4+
5+
def __init__(self,log_text, log_menu, patterns_menu, patterns_list, window, sheet_name_entry):
6+
self.log_text = log_text
7+
self.log_menu = log_menu
8+
self.patterns_menu = patterns_menu
9+
self.patterns_list = patterns_list
10+
self.window = window
11+
self.sheet_name_entry = sheet_name_entry
12+
13+
def show_log_menu(self ,event : Event, app : bool = False) -> None:
14+
if self.log_text.tag_ranges('sel'):
15+
text='Copy selected'
16+
else:
17+
text='Copy log'
18+
self.log_menu.entryconfig(0,label=text)
19+
20+
if app:
21+
self.log_menu.tk_popup(self.log_text.winfo_rootx()+100, self.log_text.winfo_rooty()+100)
22+
else:
23+
self.log_menu.tk_popup(event.x_root,event.y_root)
24+
25+
26+
def show_patterns_menu(self ,event : Event, app : bool=False) -> None:
27+
selected = self.patterns_list.curselection()
28+
if selected:
29+
if len(selected)>1:
30+
for i in (1,3):
31+
self.patterns_menu.entryconfig(i,state='disabled')
32+
33+
for i in range(4,6):
34+
self.patterns_menu.entryconfig(i,state='active')
35+
36+
else:
37+
self.patterns_menu.entryconfig(1,state='active')
38+
for i in range(3,6):
39+
self.patterns_menu.entryconfig(i,state='active')
40+
else:
41+
self.patterns_menu.entryconfig(1,state='disabled')
42+
for i in range(3,6):
43+
self.patterns_menu.entryconfig(i,state='disabled')
44+
45+
if app:
46+
self.patterns_menu.tk_popup(self.patterns_list.winfo_rootx()+100, self.patterns_list.winfo_rooty()+100)
47+
else:
48+
self.patterns_menu.tk_popup(event.x_root, event.y_root)
49+
50+
def show_entry_menu(self, menu : Menu, event : Event, app : bool=False) -> None:
51+
if self.window.focus_get() == event.widget:
52+
for i in range(4):
53+
menu.entryconfig(i,state='active')
54+
else:
55+
for i in range(4):
56+
menu.entryconfig(i,state='disabled')
57+
if app:
58+
if event.widget == self.sheet_name_entry:
59+
menu.tk_popup(event.widget.winfo_rootx()+50,event.widget.winfo_rooty()+25)
60+
else:
61+
menu.tk_popup(event.widget.winfo_rootx()+100,event.widget.winfo_rooty()+25)
62+
else:
63+
menu.tk_popup(event.x_root,event.y_root)

0 commit comments

Comments
 (0)