@@ -51,13 +51,21 @@ def __init__(
51
51
self .multiline_strategy = "rows"
52
52
self .multiline_delimiter = " "
53
53
self .quote = True
54
+ self .skip_data_validation = skip_data_validation
54
55
55
- if not skip_data_validation :
56
+ self .__validate_parameters ()
57
+
58
+ if not self .skip_data_validation :
56
59
self .__validate_data (data )
57
60
58
- self .__validate_parameters ()
59
61
self .__update_meta_params ()
62
+
63
+ # we need to first update the meta_params for cell width, padding etc
64
+ # prior to checking whether the data will fit for multiline rendering
65
+ if self .multiline :
66
+ self .__validate_multiline (self .data )
60
67
68
+
61
69
def set_params (
62
70
self ,
63
71
row_sep : str = "always" ,
@@ -128,8 +136,17 @@ def set_params(
128
136
if isinstance (padding_weight , str ):
129
137
self .padding_weight = {key : padding_weight for key in self .data [0 ].keys ()}
130
138
139
+
131
140
self .__validate_parameters ()
141
+
142
+ if not self .skip_data_validation :
143
+ self .__validate_data (self .data )
144
+
132
145
self .__update_meta_params ()
146
+
147
+ if self .multiline :
148
+ self .__validate_multiline (self .data )
149
+
133
150
return self
134
151
135
152
def __update_meta_params (self ):
@@ -142,7 +159,8 @@ def __update_meta_params(self):
142
159
else :
143
160
self .var_padding = self .__get_padding ()
144
161
self .var_row_sep = self .__get_row_sep_str ()
145
- self .var_row_sep_last = self .__get_row_sep_last ()
162
+ # self.var_row_sep_last = self.__get_row_sep_last()
163
+ self .var_row_sep_last = self .__get_row_sep_str ()
146
164
147
165
def __validate_parameters (self ): # noqa: C901
148
166
valid_values = {
@@ -196,30 +214,32 @@ def __validate_parameters(self): # noqa: C901
196
214
if not isinstance (self .quote , bool ):
197
215
raise ValueError (f"quote value of '{ self .quote } ' is not valid. Please use a boolean." )
198
216
199
-
200
-
201
217
def __validate_data (self , data ):
202
218
# Check if all dictionaries in self.data have uniform keys
203
- keys = set (data [0 ].keys ()) # Use set for fast lookup
219
+ keys = set (data [0 ].keys ())
204
220
for item in data :
205
221
if not isinstance (item , dict ):
206
222
raise TypeError ("Each element in data must be a dictionary." )
207
223
if set (item .keys ()) != keys :
208
224
raise ValueError ("Dictionary keys are not uniform across data variable." )
209
225
210
- if self .multiline :
211
- for row in data :
212
- for key in row .keys ():
213
- if key in self .var_padding :
214
- multiline_data = row [key ].split (self .multiline_delimiter )
215
- multiline_max_width = max (multiline_data , key = len )
216
- if len (multiline_max_width ) + self .padding_width [key ] > self .var_padding [key ]:
217
- raise ValueError (
218
- f"Contiguous string exists longer than the allocated column width "
219
- f"for column '{ key } ' and padding_width '{ self .padding_width [key ]} '."
220
- )
221
- else :
222
- raise KeyError (f"Key '{ key } ' not found in var_padding." )
226
+ def __validate_multiline (self , data ):
227
+ for i , row in enumerate (data ):
228
+ for key in row .keys ():
229
+ if key in self .var_padding :
230
+ multiline_data = row [key ].split (self .multiline_delimiter )
231
+ multiline_max_string = max (multiline_data , key = len )
232
+ multiline_max_width = len (multiline_max_string )
233
+ if multiline_max_width + self .padding_width [key ] > self .var_padding [key ]:
234
+ raise ValueError (
235
+ f"There is a contiguous string:\n "
236
+ f"'{ multiline_max_string } '\n "
237
+ f"in the element [{ i } ] "
238
+ f"which is longer than the allocated column width "
239
+ f"for column '{ key } ' and padding_width '{ self .padding_width [key ]} '."
240
+ )
241
+ else :
242
+ raise KeyError (f"Key '{ key } ' not found in var_padding." )
223
243
224
244
def __get_padding (self ):
225
245
"""Calculate table-wide padding."""
@@ -249,13 +269,6 @@ def __get_row_sep_str(self):
249
269
row_sep_str += "+"
250
270
return row_sep_str
251
271
252
- def __get_row_sep_last (self ):
253
- row_sep_str_last = "+"
254
- for value in self .var_padding .values ():
255
- row_sep_str_last += "-" * (value + 1 )
256
- row_sep_str_last = row_sep_str_last [:- 1 ] + "+"
257
- return row_sep_str_last
258
-
259
272
def __get_margin (self , margin , key ):
260
273
# get column-specific alignment based on the column key (header)
261
274
if self .padding_weight [key ] == "left" :
@@ -278,6 +291,9 @@ def __get_row(self, item):
278
291
for key in self .data [0 ].keys ():
279
292
if len (item [key ]) > self .var_padding [key ]:
280
293
multiline = True
294
+ if "\n " in item [key ]:
295
+ multiline = True
296
+
281
297
if multiline :
282
298
return self .__get_multiline_row (item )
283
299
return self .__get_normal_row (item )
@@ -302,7 +318,7 @@ def __get_normal_row(self, item):
302
318
row += "|"
303
319
return row
304
320
305
- def __get_multiline_row (self , item ):
321
+ def __get_multiline_row (self , item ): # noqa: C901
306
322
multiline_items = {}
307
323
308
324
# Helper function to process each element and split by emojis if present
@@ -315,31 +331,35 @@ def split_and_process_element(element):
315
331
316
332
# Process each column in the row
317
333
for key in self .data [0 ].keys ():
318
- fully_split_cell = []
319
- # Split cell content by the delimiter and process each part
320
- for element in item [key ].split (self .multiline_delimiter ):
321
- fully_split_cell .extend (split_and_process_element (element ))
322
-
323
- multiline_row , single_row = [], []
324
- item_prev_length , spacing_between_items = 0 , 0
325
-
326
- # Create multiline rows from the split elements
327
- while fully_split_cell :
328
- current_element = fully_split_cell [0 ]
329
- item_length = len (current_element ) + len (count_emojis (current_element ))
330
-
331
- # Check if the current element fits in the row
332
- if item_length + item_prev_length + spacing_between_items + self .padding_width [key ] <= self .var_padding [key ]:
333
- item_prev_length += item_length
334
- single_row .append (fully_split_cell .pop (0 ))
335
- spacing_between_items = len (single_row )
336
- else :
337
- # Start a new line if the current element doesn't fit
338
- multiline_row .append (" " .join (single_row ))
339
- single_row , item_prev_length , spacing_between_items = [], 0 , 0
334
+ multiline_row = []
335
+ # First we split by embedded line breaks in order to correctly
336
+ # render lists and othe markdown elements which depend on newline offset
337
+ for line in item [key ].split ("\n " ):
338
+ fully_split_cell = []
339
+ # Split cell content by the delimiter and process each part
340
+ for element in line .split (self .multiline_delimiter ):
341
+ fully_split_cell .extend (split_and_process_element (element ))
342
+
343
+ single_row = []
344
+ item_prev_length , spacing_between_items = 0 , 0
345
+
346
+ # Create multiline rows from the split elements
347
+ while fully_split_cell :
348
+ current_element = fully_split_cell [0 ]
349
+ item_length = len (current_element ) + len (count_emojis (current_element ))
350
+
351
+ # Check if the current element fits in the row
352
+ if item_length + item_prev_length + spacing_between_items + self .padding_width [key ] <= self .var_padding [key ]:
353
+ item_prev_length += item_length
354
+ single_row .append (fully_split_cell .pop (0 ))
355
+ spacing_between_items = len (single_row )
356
+ else :
357
+ # Start a new line if the current element doesn't fit
358
+ multiline_row .append (" " .join (single_row ))
359
+ single_row , item_prev_length , spacing_between_items = [], 0 , 0
340
360
341
- # Add the remaining elements in single_row to multiline_row
342
- multiline_row .append (" " .join (single_row ))
361
+ # Add the remaining elements in single_row to multiline_row
362
+ multiline_row .append (" " .join (single_row ))
343
363
multiline_items [key ] = multiline_row
344
364
345
365
# Find the maximum number of rows in any column
0 commit comments