excel-list-transform 0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ #! /usr/local/bin/python3
2
+ """Functions for doing transform of lists excel files."""
3
+
4
+ # Copyright (c) 2024 Tom Björkholm
5
+ # MIT License
6
+
7
+
8
+ import sys
9
+ from copy import deepcopy
10
+ from excel_list_transform.config_enums import SplitWhere
11
+ from excel_list_transform.config_excel_list_transform import Column, \
12
+ Rule, RuleMerge, RuleRewrite, RuleSplit, SingleRuleSplit, \
13
+ ConfigExcelListTransform
14
+ from excel_list_transform.rewrite_value import rewrite_value
15
+ from excel_list_transform.commontypes import \
16
+ NumRow, NameRow, NumRowSeq, NameRowMap, Data, Value
17
+ # NumData, NumDataSeq, NameData, NameDataMap,
18
+
19
+
20
+ def col_must_exist_num(col: int, row: NumRowSeq, param: str) -> None:
21
+ """Stop with error report if column does not exist (num case)."""
22
+ if col < 0 or col >= len(row):
23
+ msg = f'{param}: column index {col} out of range [0, {len(row)-1}].'
24
+ print(msg, file=sys.stderr)
25
+ sys.exit(1)
26
+
27
+
28
+ def col_must_exist_name(col: str, row: NameRowMap, param: str) -> None:
29
+ """Stop with error report if column does not exist (name case)."""
30
+ if col not in row:
31
+ msg: str = f'{param}: no column named "{col}" in data row.'
32
+ print(msg, file=sys.stderr)
33
+ sys.exit(1)
34
+
35
+
36
+ def col_must_exist(col: Column, row: NumRowSeq | NameRowMap,
37
+ param: str) -> None:
38
+ """Stop with error report if column does not exist."""
39
+ if isinstance(col, str):
40
+ assert isinstance(col, str)
41
+ assert isinstance(row, dict)
42
+ col_must_exist_name(col=col, row=row, param=param)
43
+ else:
44
+ assert isinstance(col, int)
45
+ assert isinstance(row, list)
46
+ col_must_exist_num(col=col, row=row, param=param)
47
+
48
+
49
+ def cols_must_exist_lst(cols: list[Column], row: NumRowSeq | NameRowMap,
50
+ param: str, tinfo: Column) -> None:
51
+ """Stop with error report all if columns does not exist."""
52
+ for col in cols:
53
+ assert isinstance(col, type(tinfo))
54
+ col_must_exist(col=col, row=row, param=param)
55
+
56
+
57
+ def cols_must_exist_dict(rule: Rule[Column] | RuleSplit[Column] |
58
+ RuleRewrite[Column],
59
+ row: NumRowSeq | NameRowMap,
60
+ param: str, tinfo: Column) -> None:
61
+ """Stop with error report if columns does not exist."""
62
+ cols = ConfigExcelListTransform.get_cols_single(rule=rule, tinfo=tinfo)
63
+ cols_must_exist_lst(cols=cols, row=row, param=param, tinfo=tinfo)
64
+
65
+
66
+ def cols_must_exist_dictlst(rule: RuleMerge[Column],
67
+ row: NumRowSeq | NameRowMap,
68
+ param: str, tinfo: Column) -> None:
69
+ """Stop with error report if columns does not exist."""
70
+ cols = ConfigExcelListTransform.get_cols_multi(rule=rule, tinfo=tinfo)
71
+ cols_must_exist_lst(cols=cols, row=row, param=param, tinfo=tinfo)
72
+
73
+
74
+ def store_col_split_num(row: NumRow, colref: int, val: list[str],
75
+ singlerule: SingleRuleSplit[int]) -> None:
76
+ """Store result of column split for number refs."""
77
+ if len(val) == 0:
78
+ for _ in range(2):
79
+ row.insert(colref, None)
80
+ return
81
+ if len(val) == 1:
82
+ if singlerule['store_single'] != SplitWhere.RIGHTMOST:
83
+ row.insert(colref, None)
84
+ row.insert(colref, val[0])
85
+ else:
86
+ row.insert(colref, val[0])
87
+ row.insert(colref, None)
88
+ return
89
+ for j in reversed(val):
90
+ row.insert(colref, j)
91
+
92
+
93
+ def store_col_split_name(row: NameRow, colref: str, val: list[str],
94
+ singlerule: SingleRuleSplit[str]) -> None:
95
+ """Store result of column split for number refs."""
96
+ rightname = singlerule['right_name']
97
+ assert isinstance(rightname, str)
98
+ row[rightname] = None
99
+ if len(val) == 0:
100
+ row[colref] = None
101
+ elif len(val) == 1:
102
+ row[colref] = val[0]
103
+ else:
104
+ row[colref] = val[0]
105
+ row[rightname] = val[1]
106
+
107
+
108
+ def pop_from_row(row: NumRow | NameRow, colref: Column) -> Value:
109
+ """Keep mypy happy with row.pop."""
110
+ if isinstance(colref, int):
111
+ assert isinstance(colref, int)
112
+ assert isinstance(row, list)
113
+ return row.pop(colref)
114
+ assert isinstance(colref, str)
115
+ assert isinstance(row, dict)
116
+ return row.pop(colref)
117
+
118
+
119
+ def split_columns(indata: Data, cfg: ConfigExcelListTransform[Column],
120
+ tinfo: Column) -> Data:
121
+ """Split columns in the in indata."""
122
+ if len(cfg.s1_split_columns) == 0:
123
+ return indata
124
+ ret = deepcopy(indata)
125
+ for row in ret:
126
+ cols_must_exist_dict(rule=cfg.s1_split_columns, row=row,
127
+ param='s1_split_columns', tinfo=tinfo)
128
+ for i in reversed(cfg.s1_split_columns):
129
+ colref = i['column']
130
+ assert isinstance(colref, type(tinfo))
131
+ sep = i['separator']
132
+ assert isinstance(sep, str)
133
+ inval = pop_from_row(row=row, colref=colref)
134
+ val: list[str] = []
135
+ if inval is not None:
136
+ if not isinstance(inval, str):
137
+ msg = 's1_split_columns: '
138
+ msg += 'Can only split columns with string values.\n'
139
+ msg += f'Column "{colref}" has value of '
140
+ msg += f'type {type(inval).__name__}'
141
+ print(msg, file=sys.stderr)
142
+ sys.exit(1)
143
+ assert isinstance(inval, str)
144
+ if i['where'] == SplitWhere.RIGHTMOST:
145
+ val = inval.rsplit(sep=sep, maxsplit=1)
146
+ else:
147
+ val = inval.split(sep=sep, maxsplit=1)
148
+ if isinstance(tinfo, str):
149
+ assert isinstance(row, dict)
150
+ assert isinstance(colref, str)
151
+ store_col_split_name(row=row, colref=colref, val=val,
152
+ singlerule=i)
153
+ else:
154
+ assert isinstance(row, list)
155
+ assert isinstance(colref, int)
156
+ store_col_split_num(row=row, colref=colref, val=val,
157
+ singlerule=i)
158
+ return ret
159
+
160
+
161
+ def insert_into_row(row: NumRow | NameRow, colref: Column, val: Value) -> None:
162
+ """Keep mypy happy insert into row."""
163
+ if isinstance(colref, int):
164
+ assert isinstance(colref, int)
165
+ assert isinstance(row, list)
166
+ row.insert(colref, val)
167
+ return
168
+ assert isinstance(colref, str)
169
+ assert isinstance(row, dict)
170
+ row[colref] = val
171
+
172
+
173
+ def merge_columns(indata: Data, cfg: ConfigExcelListTransform[Column],
174
+ tinfo: Column) -> Data:
175
+ """Merge columns in the list in indata with column number refs."""
176
+ if len(cfg.s3_merge_columns) == 0:
177
+ return indata
178
+ ret = deepcopy(indata)
179
+ for row in ret:
180
+ cols_must_exist_dictlst(rule=cfg.s3_merge_columns, row=row,
181
+ param='s3_merge_columns', tinfo=tinfo)
182
+ for i in reversed(cfg.s3_merge_columns):
183
+ colspec = i['columns']
184
+ assert isinstance(colspec, list)
185
+ if len(colspec) < 2:
186
+ continue
187
+ colref = colspec[0]
188
+ assert isinstance(colref, type(tinfo))
189
+ vals: list[str] = []
190
+ for j in reversed(colspec):
191
+ assert isinstance(j, type(tinfo))
192
+ popval = pop_from_row(row=row, colref=j)
193
+ if popval is not None:
194
+ vals.insert(0, str(popval))
195
+ if len(vals) > 0:
196
+ sep = i['separator']
197
+ assert isinstance(sep, str)
198
+ insert_into_row(row=row, colref=colref,
199
+ val=sep.join(vals))
200
+ else:
201
+ insert_into_row(row=row, colref=colref, val=None)
202
+ return ret
203
+
204
+
205
+ def row_element(row: NumRow | NameRow, idx: Column, tinfo: Column) -> Value:
206
+ """Keep mypy happy with accessing element in dict or list."""
207
+ assert isinstance(idx, type(tinfo))
208
+ if isinstance(idx, int):
209
+ assert isinstance(idx, int)
210
+ assert isinstance(row, list)
211
+ return row[idx]
212
+ assert isinstance(idx, str)
213
+ assert isinstance(row, dict)
214
+ return row[idx]
215
+
216
+
217
+ def set_row_element(row: NumRow | NameRow, idx: Column, tinfo: Column,
218
+ val: Value) -> None:
219
+ """Keep mypy happy with accessing element in dict or list."""
220
+ assert isinstance(idx, type(tinfo))
221
+ if isinstance(idx, int):
222
+ assert isinstance(idx, int)
223
+ assert isinstance(row, list)
224
+ row[idx] = val
225
+ return
226
+ assert isinstance(idx, str)
227
+ assert isinstance(row, dict)
228
+ row[idx] = val
229
+
230
+
231
+ def rewrite_columns(indata: Data, cfg: ConfigExcelListTransform[Column],
232
+ tinfo: Column) -> Data:
233
+ """Rewrite columns in the list in indata with column number refs."""
234
+ if len(cfg.s7_rewrite_columns) == 0:
235
+ return indata
236
+ ret = deepcopy(indata)
237
+ for row in ret:
238
+ cols_must_exist_dict(rule=cfg.s7_rewrite_columns, row=row,
239
+ param='s7_rewrite_columns', tinfo=tinfo)
240
+ for i in cfg.s7_rewrite_columns:
241
+ colref = i['column']
242
+ assert isinstance(colref, type(tinfo))
243
+ oldvalue = row_element(row=row, idx=colref, tinfo=tinfo)
244
+ if oldvalue is None:
245
+ continue
246
+ assert oldvalue is not None
247
+ newvalue = rewrite_value(value=str(oldvalue), spec=i, tinfo=tinfo)
248
+ set_row_element(row=row, idx=colref, tinfo=tinfo, val=newvalue)
249
+ return ret
@@ -0,0 +1,136 @@
1
+ #! /usr/local/bin/python3
2
+ """Functions for doing transform of lists excel files."""
3
+
4
+ # Copyright (c) 2024 Tom Björkholm
5
+ # MIT License
6
+
7
+
8
+ import sys
9
+ from copy import deepcopy
10
+ from excel_list_transform.handle_csv import read_csv_named, write_csv_named
11
+ from excel_list_transform.handle_excel import \
12
+ read_excel_named, write_excel_named
13
+ from excel_list_transform.config_enums import \
14
+ FileType
15
+ from excel_list_transform.config import Config
16
+ from excel_list_transform.config_xls_list_refmt_name import \
17
+ ConfigXlsListRefmtName
18
+ from excel_list_transform.check_indata_common import check_indata_common
19
+ from excel_list_transform.commontypes import NameData, get_checked_type
20
+ from excel_list_transform.transform_func_common import \
21
+ cols_must_exist_dict, split_columns, merge_columns, rewrite_columns
22
+
23
+
24
+ def rename_columns_name(indata: NameData,
25
+ cfg: ConfigXlsListRefmtName) -> NameData:
26
+ """Rename columns in the list in indata with column name refs."""
27
+ if len(cfg.s5_rename_columns) == 0:
28
+ return indata
29
+ ret = deepcopy(indata)
30
+ for row in ret:
31
+ cols_must_exist_dict(rule=cfg.s5_rename_columns, row=row,
32
+ param='s5_rename_columns', tinfo='a')
33
+ for i in cfg.s5_rename_columns:
34
+ from_name: str = get_checked_type(value=i['column'], istype=str)
35
+ to_name: str = get_checked_type(value=i['name'], istype=str)
36
+ val = row.pop(from_name)
37
+ row[to_name] = val
38
+ return ret
39
+
40
+
41
+ def insert_columns_name(indata: NameData,
42
+ cfg: ConfigXlsListRefmtName) -> NameData:
43
+ """Insert columns in the list in indata with column name refs."""
44
+ if len(cfg.s6_insert_columns) == 0:
45
+ return indata
46
+ ret = deepcopy(indata)
47
+ for row in ret:
48
+ for i in cfg.s6_insert_columns:
49
+ col: str = get_checked_type(value=i['column'], istype=str)
50
+ val = i['value']
51
+ if col in row:
52
+ msg = f's6_insert_columns: column "{col}" '
53
+ msg += 'already exists.'
54
+ print(msg, file=sys.stderr)
55
+ sys.exit(1)
56
+ row[col] = val
57
+ return ret
58
+
59
+
60
+ def fix_indata_empty_rows_name(indata: NameData) -> None: # pylint: disable=duplicate-code # noqa: E501
61
+ """Check rows and remove empty rows with column name refs."""
62
+ for i, row in reversed(list(enumerate(indata))):
63
+ if not isinstance(row, dict):
64
+ msg = 'Internal error. Expected dict of columns but got '
65
+ msg += type(row).__name__ # pylint: disable=duplicate-code # noqa: E501
66
+ print(msg, file=sys.stderr)
67
+ raise TypeError(msg)
68
+ if len(row) == 0:
69
+ del indata[i]
70
+ continue
71
+ for val in row.values():
72
+ if val is not None and not isinstance(val, str):
73
+ return # last row not empty
74
+ if val is not None and isinstance(val, str):
75
+ if len(val) > 0:
76
+ return # last rown not empty
77
+ # last row is empty, remove it
78
+ keys = deepcopy(list(row.keys()))
79
+ for key in keys:
80
+ del row[key]
81
+ del indata[i]
82
+ return
83
+
84
+
85
+ def check_indata_name(indata: NameData) -> None:
86
+ """Check that the indata is well formed with column name refs."""
87
+ check_indata_common(indata=indata,
88
+ fix_indata_empty_rows=fix_indata_empty_rows_name)
89
+ cols = None
90
+ for row in indata:
91
+ rowcols = sorted(row.keys())
92
+ if cols is None:
93
+ cols = rowcols
94
+ else:
95
+ if cols != rowcols:
96
+ msg = 'Columns names different between lines. '
97
+ msg += f'Found {cols} and {rowcols}. Aborting.'
98
+ print(msg, file=sys.stderr)
99
+ raise RuntimeError(msg)
100
+
101
+
102
+ def transform_data_name(indata: NameData,
103
+ cfg: ConfigXlsListRefmtName) -> NameData:
104
+ """Transform list in the data with column number refs."""
105
+ check_indata_name(indata=indata)
106
+ ret = split_columns(indata=indata, cfg=cfg, tinfo='a')
107
+ ret = merge_columns(indata=ret, cfg=cfg, tinfo='a')
108
+ ret = rename_columns_name(indata=ret, cfg=cfg)
109
+ ret = insert_columns_name(indata=ret, cfg=cfg)
110
+ ret = rewrite_columns(indata=ret, cfg=cfg, tinfo='a')
111
+ return ret
112
+
113
+
114
+ def transform_named_files_name(infilename: str, outfilename: str,
115
+ cfg: Config) -> None:
116
+ """Transform list in the named excel file to named file."""
117
+ cfgn: ConfigXlsListRefmtName = \
118
+ get_checked_type(value=cfg, istype=ConfigXlsListRefmtName)
119
+ indata = None
120
+ if cfgn.in_type == FileType.CSV:
121
+ indata = read_csv_named(infilename, cfgn.get_in_csv_dialect(),
122
+ max_column_read=cfgn.max_column_read)
123
+ else:
124
+ indata = read_excel_named(infilename,
125
+ max_column_read=cfgn.max_column_read,
126
+ excel_lib=cfgn.in_excel_library)
127
+ outdata = transform_data_name(indata=indata, cfg=cfgn)
128
+ if cfgn.out_type == FileType.CSV:
129
+ write_csv_named(data=outdata, filename=outfilename,
130
+ dialect=cfgn.get_out_csv_dialect(),
131
+ column_order=cfgn.s8_column_order)
132
+ else:
133
+ write_excel_named(data=outdata, filename=outfilename,
134
+ column_order=cfgn.s8_column_order,
135
+ excel_lib=cfgn.out_excel_library)
136
+ print(f'Wrote {outfilename}')
@@ -0,0 +1,157 @@
1
+ #! /usr/local/bin/python3
2
+ """Functions for doing transform of lists excel files."""
3
+
4
+ # Copyright (c) 2024 Tom Björkholm
5
+ # MIT License
6
+
7
+
8
+ import sys
9
+ from copy import deepcopy
10
+ from excel_list_transform.handle_csv import read_csv_num, write_csv_num
11
+ from excel_list_transform.handle_excel import read_excel_num, write_excel_num
12
+ from excel_list_transform.config_enums import FileType
13
+ from excel_list_transform.commontypes import NumData, get_checked_type
14
+ from excel_list_transform.config import Config
15
+ from excel_list_transform.config_xls_list_refmt_num import \
16
+ ConfigXlsListRefmtNum
17
+ from excel_list_transform.check_indata_common import check_indata_common
18
+ from excel_list_transform.transform_func_common import \
19
+ cols_must_exist_dict, cols_must_exist_lst, split_columns, merge_columns, \
20
+ rewrite_columns
21
+
22
+
23
+ def remove_columns_num(indata: NumData,
24
+ cfg: ConfigXlsListRefmtNum) -> NumData:
25
+ """Remove columns in the list in indata with column number refs."""
26
+ if len(cfg.s2_remove_columns) == 0:
27
+ return indata
28
+ ret = deepcopy(indata)
29
+ for row in ret:
30
+ rowlen = len(row)
31
+ cols_must_exist_lst(cols=cfg.s2_remove_columns, row=row,
32
+ param='s2_remove_columns', tinfo=2)
33
+ for i in reversed(cfg.s2_remove_columns):
34
+ if 0 <= i < rowlen:
35
+ row.pop(i)
36
+ return ret
37
+
38
+
39
+ def place_columns_first(indata: NumData,
40
+ cfg: ConfigXlsListRefmtNum) -> NumData:
41
+ """Place columns first in the list in indata."""
42
+ if len(cfg.s4_place_columns_first) == 0:
43
+ return indata
44
+ ret = deepcopy(indata)
45
+ for row_i, row in enumerate(ret):
46
+ rowlen = len(row)
47
+ cols_must_exist_lst(cols=cfg.s4_place_columns_first, row=row,
48
+ param='s4_place_columns_first', tinfo=2)
49
+ drow = dict(enumerate(row))
50
+ order = deepcopy(cfg.s4_place_columns_first)
51
+ for i in range(rowlen):
52
+ if i not in order:
53
+ order.append(i)
54
+ nrow = []
55
+ for i in order:
56
+ nrow.append(drow[i])
57
+ ret[row_i] = nrow
58
+ return ret
59
+
60
+
61
+ def rename_columns_num(indata: NumData,
62
+ cfg: ConfigXlsListRefmtNum) -> NumData:
63
+ """Rename columns in the list in indata with column number refs."""
64
+ assert isinstance(cfg.s5_rename_columns, list)
65
+ if len(cfg.s5_rename_columns) == 0:
66
+ return indata
67
+ ret = deepcopy(indata)
68
+ cols_must_exist_dict(rule=cfg.s5_rename_columns, row=ret[0],
69
+ param='s5_rename_columns', tinfo=2)
70
+ for i in cfg.s5_rename_columns:
71
+ colref: int = get_checked_type(i['column'], int)
72
+ assert isinstance(colref, int)
73
+ ret[0][colref] = get_checked_type(i['name'], str)
74
+ return ret
75
+
76
+
77
+ def insert_columns_num(indata: NumData,
78
+ cfg: ConfigXlsListRefmtNum) -> NumData:
79
+ """Insert columns in the list in indata with column number refs."""
80
+ assert isinstance(cfg.s6_insert_columns, list)
81
+ if len(cfg.s6_insert_columns) == 0:
82
+ return indata
83
+ ret = deepcopy(indata)
84
+ for row_i, row in enumerate(ret):
85
+ for i in cfg.s6_insert_columns:
86
+ colref: int = get_checked_type(value=i['column'], istype=int)
87
+ if not 0 <= colref <= len(row):
88
+ msg = f's6_insert_columns: column index {colref} out of '
89
+ msg += f'range [0, {len(row)}].'
90
+ print(msg, file=sys.stderr)
91
+ sys.exit(1)
92
+ name: str = get_checked_type(i['name'], str)
93
+ to_insert = name if row_i == 0 else i['value']
94
+ if colref == len(row):
95
+ row.append(to_insert)
96
+ else:
97
+ row.insert(colref, to_insert)
98
+ return ret
99
+
100
+
101
+ def fix_indata_empty_rows_num(indata: NumData) -> None: # pylint: disable=duplicate-code # noqa: E501
102
+ """Check rows and remove empty rows with column number refs."""
103
+ for i, row in reversed(list(enumerate(indata))):
104
+ assert isinstance(row, list), 'Internal error. Expected list of ' + \
105
+ f'columns but got {type(row).__name__}' # pylint: disable=duplicate-code # noqa: E501
106
+ if len(row) == 0:
107
+ del indata[i]
108
+ elif len(row) == 1:
109
+ if row[0] is None:
110
+ del indata[i][0]
111
+ del indata[i]
112
+ elif isinstance(row[0], str) and len(row[0]) == 0:
113
+ del indata[i][0]
114
+ del indata[i]
115
+
116
+
117
+ def check_indata_num(indata: NumData) -> None:
118
+ """Check that the indata is well formed with column number refs."""
119
+ check_indata_common(indata=indata,
120
+ fix_indata_empty_rows=fix_indata_empty_rows_num)
121
+
122
+
123
+ def transform_data_num(indata: NumData,
124
+ cfg: ConfigXlsListRefmtNum) -> NumData:
125
+ """Transform list in the data with column number refs."""
126
+ check_indata_num(indata=indata)
127
+ ret = split_columns(indata=indata, cfg=cfg, tinfo=2)
128
+ ret = remove_columns_num(indata=ret, cfg=cfg)
129
+ ret = merge_columns(indata=ret, cfg=cfg, tinfo=2)
130
+ ret = place_columns_first(indata=ret, cfg=cfg)
131
+ ret = rename_columns_num(indata=ret, cfg=cfg)
132
+ ret = insert_columns_num(indata=ret, cfg=cfg)
133
+ ret = rewrite_columns(indata=ret, cfg=cfg, tinfo=2)
134
+ return ret
135
+
136
+
137
+ def transform_named_files_num(infilename: str, outfilename: str,
138
+ cfg: Config) -> None:
139
+ """Transform list in the named excel file to named file."""
140
+ cfgn: ConfigXlsListRefmtNum = \
141
+ get_checked_type(value=cfg, istype=ConfigXlsListRefmtNum)
142
+ indata = None
143
+ if cfgn.in_type == FileType.CSV:
144
+ indata = read_csv_num(infilename, cfgn.get_in_csv_dialect(),
145
+ max_column_read=cfgn.max_column_read)
146
+ else:
147
+ indata = read_excel_num(infilename,
148
+ max_column_read=cfgn.max_column_read,
149
+ excel_lib=cfgn.in_excel_library)
150
+ outdata = transform_data_num(indata=indata, cfg=cfgn)
151
+ if cfgn.out_type == FileType.CSV:
152
+ write_csv_num(data=outdata, filename=outfilename,
153
+ dialect=cfgn.get_out_csv_dialect())
154
+ else:
155
+ write_excel_num(data=outdata, filename=outfilename,
156
+ excel_lib=cfgn.out_excel_library)
157
+ print(f'Wrote {outfilename}')
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Tom Björkholm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.1
2
+ Name: excel-list-transform
3
+ Version: 0.5
4
+ Summary: Transform a list in excel or CSV
5
+ Author: Tom Björkholm
6
+ Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
7
+ Project-URL: Source code, https://bitbucket.org/tom-bjorkholm/excel-list-transform
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10.5
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE.txt
14
+ Requires-Dist: openpyxl >=3.1.5
15
+ Requires-Dist: types-openpyxl >=3.1.5.20240822
16
+ Requires-Dist: pylightxl >=1.61
17
+ Requires-Dist: XlsxWriter >=3.2.0
18
+ Requires-Dist: pip >=23.2
19
+ Requires-Dist: Pillow >=10.0.1
20
+ Requires-Dist: setuptools >=74.0.0
21
+ Requires-Dist: build >=1.2.1
22
+ Requires-Dist: wheel >=0.44.0
23
+
24
+ # excel-list-transform
25
+
26
+ ## Background
27
+
28
+ This python application was born out of an experience at sail racing events. At the start of the events we received an excel list with participants (from a registration web) to enter into the scoring software and into tool for online notice board. The information was present in the excel file, but the columns were all wrong. To avoid the stressful manual rework of the information in the excel list this application was created.
29
+
30
+ ## What it does
31
+
32
+ This small python application:
33
+ * reads data (that is a list with columns) from an excel file or from a comma separate values (CSV) file.
34
+ * splits columns in the list (like creating "first name" and "last name" columns from "name" column)
35
+ * merges columns in the list (like creating "name" column from "first name" and "last name" columns)
36
+ * removes columns in the list
37
+ * reorders columns in the list
38
+ * renames columns in the list
39
+ * inserts columns in the list
40
+ * rewrites columns in the list (like transforming telephone numbers from local/national format to international format)
41
+ * writest the resulting data (that is a list with columns) to an excel file or to a comma separate values (CSV) file.
42
+
43
+ How this is done is governed by a configuration file. The application can create a number of example configuration files with accompanying description text files.
44
+
45
+ ## Installing it
46
+
47
+ If you want to use it install it using pip. A precondition is that you have Python 3.10.5 or newer installed on you computer. Python can be downloaded from [https://www.python.org/downloads/](https://www.python.org/downloads/).
48
+
49
+ ### Installing on mac and Linux
50
+
51
+ ````
52
+ pip3 install excel-list-transform
53
+ ````
54
+
55
+ ### Installing on Microsoft Windows
56
+
57
+ ````
58
+ pip install excel-list-transform
59
+ ````
60
+
61
+ ## Running the application
62
+
63
+ ### Running the application on mac and Linux
64
+
65
+ ````
66
+ python3 -m excel_list_transform --help
67
+ python3 -m excel_list_transform example --help
68
+ python3 -m excel_list_transform transform --help
69
+ python3 -m excel_list_transform example -k forms_to_rrs -r by_name -o example.cfg
70
+ python3 -m excel_list_transform transform -c example.cfg -i input.xlsx -o output.xlsx
71
+ ````
72
+
73
+ ### Running the application on Microsoft Windows
74
+
75
+ ````
76
+ python -m excel_list_transform --help
77
+ python -m excel_list_transform example --help
78
+ python -m excel_list_transform transform --help
79
+ python -m excel_list_transform example -k forms_to_rrs -r by_name -o example.cfg
80
+ python -m excel_list_transform transform -c example.cfg -i input.xlsx -o output.xlsx
81
+ ````
82
+
83
+ ## Suggested way to get started
84
+
85
+ 1. Use the "example" sub-command to generate a few example configuration (.cfg) files with description (.txt) files.
86
+ 2. Read the example configuration (.cfg) files and the accompanying description (.txt) files.
87
+ 3. Find an example that is close to what you want to achieve.
88
+ 4. Modify that configuration file to achieve what you want to achieve.
89
+ 5. Use the "transform" sub-command to read your data and output it transformed or reorganized according to your modified configuration file.
90
+ 6. Read the produced output. If necessary go back to step 4 and adjust how the data is transformed.
91
+
92
+ ### Description of configuration file
93
+
94
+ When using the "example" sub-command to generate an example configuration file (say example.cfg) a text file describing the configuration and the syntax of the configuration file is also generated. If the example configuration file is named example.cfg, then the text file descriging the configuration is named example.txt.
95
+
96
+ You can generate several example configuration files each with an accompanying text file descriping it.
97
+
98
+ Read the text file describing the configuration file while looking at the configuration file to understand the syntax and the possible options.
99
+
100
+ ## Performance
101
+
102
+ This application is written for the moderate amounts of data when registering participants for the majority of sports events. If you have millions of rows this application is not for you.
103
+
104
+ With an input file consisting of 20 columns producing an output file of 11 columns I have measured the following performance on a MacBook Air (laptop) from 2020:
105
+ * 1 000 rows processed in less than 0.5 seconds
106
+ * 10 000 rows processed in less than 4 seconds
107
+ * 80 000 rows processed in less than 30 seconds
108
+ * 120 000 rows processed in 40 seconds
109
+
110
+ Naturally your performance will be different based on computer hardware, operating system and Python version. Generally it should be reasonably fast for less than 10 000 rows, painfully slow but somewhat usable up to 100 000 rows and probably so slow that it is unusable for more than 100 000 rows.
111
+
112
+ ## Source code
113
+
114
+ Source code and tests are available at [https://bitbucket.org/tom-bjorkholm/excel-list-transform](https://bitbucket.org/tom-bjorkholm/excel-list-transform).