@@ -51,13 +51,21 @@ def __init__(
5151 self .multiline_strategy = "rows"
5252 self .multiline_delimiter = " "
5353 self .quote = True
54+ self .skip_data_validation = skip_data_validation
5455
55- if not skip_data_validation :
56+ self .__validate_parameters ()
57+
58+ if not self .skip_data_validation :
5659 self .__validate_data (data )
5760
58- self .__validate_parameters ()
5961 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 )
6067
68+
6169 def set_params (
6270 self ,
6371 row_sep : str = "always" ,
@@ -128,8 +136,17 @@ def set_params(
128136 if isinstance (padding_weight , str ):
129137 self .padding_weight = {key : padding_weight for key in self .data [0 ].keys ()}
130138
139+
131140 self .__validate_parameters ()
141+
142+ if not self .skip_data_validation :
143+ self .__validate_data (self .data )
144+
132145 self .__update_meta_params ()
146+
147+ if self .multiline :
148+ self .__validate_multiline (self .data )
149+
133150 return self
134151
135152 def __update_meta_params (self ):
@@ -142,7 +159,8 @@ def __update_meta_params(self):
142159 else :
143160 self .var_padding = self .__get_padding ()
144161 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 ()
146164
147165 def __validate_parameters (self ): # noqa: C901
148166 valid_values = {
@@ -196,30 +214,32 @@ def __validate_parameters(self): # noqa: C901
196214 if not isinstance (self .quote , bool ):
197215 raise ValueError (f"quote value of '{ self .quote } ' is not valid. Please use a boolean." )
198216
199-
200-
201217 def __validate_data (self , data ):
202218 # 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 ())
204220 for item in data :
205221 if not isinstance (item , dict ):
206222 raise TypeError ("Each element in data must be a dictionary." )
207223 if set (item .keys ()) != keys :
208224 raise ValueError ("Dictionary keys are not uniform across data variable." )
209225
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." )
223243
224244 def __get_padding (self ):
225245 """Calculate table-wide padding."""
@@ -249,13 +269,6 @@ def __get_row_sep_str(self):
249269 row_sep_str += "+"
250270 return row_sep_str
251271
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-
259272 def __get_margin (self , margin , key ):
260273 # get column-specific alignment based on the column key (header)
261274 if self .padding_weight [key ] == "left" :
@@ -278,6 +291,9 @@ def __get_row(self, item):
278291 for key in self .data [0 ].keys ():
279292 if len (item [key ]) > self .var_padding [key ]:
280293 multiline = True
294+ if "\n " in item [key ]:
295+ multiline = True
296+
281297 if multiline :
282298 return self .__get_multiline_row (item )
283299 return self .__get_normal_row (item )
@@ -302,7 +318,7 @@ def __get_normal_row(self, item):
302318 row += "|"
303319 return row
304320
305- def __get_multiline_row (self , item ):
321+ def __get_multiline_row (self , item ): # noqa: C901
306322 multiline_items = {}
307323
308324 # Helper function to process each element and split by emojis if present
@@ -315,31 +331,35 @@ def split_and_process_element(element):
315331
316332 # Process each column in the row
317333 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
340360
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 ))
343363 multiline_items [key ] = multiline_row
344364
345365 # Find the maximum number of rows in any column
0 commit comments