Skip to content

Commit d81833b

Browse files
skelsecSkelSec
andauthored
Generate tables from 2D string arrays (#114)
* Add new_table_array method to MdUtils and create_table_array method to Table: - Implemented new_table_array in MdUtils for creating tables from lists of lists. - Added create_table_array in Table to normalize data and generate markdown tables. - Enhanced documentation for the new methods with examples. * Add tests for create_table_array method in Table - Implemented multiple test cases to validate the functionality of create_table_array, including basic functionality, various text alignments, handling of uneven rows, mixed data types, and special characters. - Ensured coverage for edge cases such as empty data and rows with empty lists. --------- Co-authored-by: SkelSec <[email protected]>
1 parent 2a89e48 commit d81833b

File tree

3 files changed

+260
-0
lines changed

3 files changed

+260
-0
lines changed

mdutils/mdutils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,19 @@ def new_table(
254254
self.___update_file_data(text_table)
255255

256256
return text_table
257+
258+
def new_table_array(self, data: List[List[str]], text_align: Optional[Union[str, list]] = None, marker: str = ""):
259+
"""
260+
Create a table from a list of lists.
261+
"""
262+
new_table = mdutils.tools.Table.Table()
263+
text_table = new_table.create_table_array(data, text_align)
264+
if marker:
265+
self.file_data_text = self.place_text_using_marker(text_table, marker)
266+
else:
267+
self.___update_file_data(text_table)
268+
269+
return text_table
257270

258271
def new_paragraph(
259272
self,

mdutils/tools/Table.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,60 @@ def create_table(
123123
else:
124124
raise ValueError("columns * rows is not equal to text length")
125125

126+
def create_table_array(self, data: List[List[str]], text_align: Optional[Union[str, list]] = None):
127+
"""
128+
Create a table from a list of lists.
129+
130+
This method takes a 2D array (list of lists) and creates a markdown table.
131+
It normalizes the data by converting all elements to strings and ensuring
132+
all rows have the same number of columns by padding with empty strings.
133+
134+
:param data: A 2D list where each inner list represents a row
135+
:type data: List[List[str]]
136+
:param text_align: text align argument, same as create_table.
137+
Values available are: 'right', 'center', 'left' and None (default).
138+
If text_align is a list then individual alignment can be set for each column.
139+
:type text_align: Optional[Union[str, list]]
140+
:return: a markdown table string
141+
:rtype: str
142+
143+
:Example:
144+
>>> from mdutils.tools.Table import Table
145+
>>> data = [['Header 1', 'Header 2'], ['Row 1 Col 1', 'Row 1 Col 2'], ['Row 2 Col 1']]
146+
>>> table = Table().create_table_array(data=data, text_align='center')
147+
>>> print(repr(table))
148+
'\\n|Header 1|Header 2|\\n| :---: | :---: |\\n|Row 1 Col 1|Row 1 Col 2|\\n|Row 2 Col 1||\\n'
149+
"""
150+
if not data:
151+
return ""
152+
153+
# Find the maximum column count across all rows
154+
max_columns = max(len(row) for row in data) if data else 0
155+
156+
if max_columns == 0:
157+
return ""
158+
159+
# Normalize the data: convert all elements to strings and pad rows
160+
normalized_data = []
161+
for row in data:
162+
# Convert all elements to strings
163+
str_row = [str(cell) for cell in row]
164+
# Pad with empty strings if the row is shorter than max_columns
165+
while len(str_row) < max_columns:
166+
str_row.append("")
167+
normalized_data.append(str_row)
168+
169+
# Flatten the 2D array into a 1D list
170+
flattened_text = []
171+
for row in normalized_data:
172+
flattened_text.extend(row)
173+
174+
# Calculate dimensions
175+
rows = len(normalized_data)
176+
columns = max_columns
177+
178+
# Call the existing create_table method
179+
return self.create_table(columns=columns, rows=rows, text=flattened_text, text_align=text_align)
126180

127181
if __name__ == "__main__":
128182
import doctest

tests/test_tools/test_table.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,196 @@ def test_invalid_text_align(self):
213213
self.assertRaises(
214214
ValueError, table.create_table, 3, 14, text_array, "invalid_align"
215215
)
216+
217+
def test_create_table_array_basic(self):
218+
"""Test basic functionality of create_table_array method."""
219+
table = Table()
220+
data = [
221+
["Header 1", "Header 2", "Header 3"],
222+
["Row 1 Col 1", "Row 1 Col 2", "Row 1 Col 3"],
223+
["Row 2 Col 1", "Row 2 Col 2", "Row 2 Col 3"]
224+
]
225+
expected_result = (
226+
"\n|Header 1|Header 2|Header 3|\n| --- | --- | --- |\n"
227+
"|Row 1 Col 1|Row 1 Col 2|Row 1 Col 3|\n"
228+
"|Row 2 Col 1|Row 2 Col 2|Row 2 Col 3|\n"
229+
)
230+
231+
result = table.create_table_array(data=data)
232+
self.assertEqual(result, expected_result)
233+
234+
def test_create_table_array_with_center_alignment(self):
235+
"""Test create_table_array with center text alignment."""
236+
table = Table()
237+
data = [
238+
["Header 1", "Header 2"],
239+
["Data 1", "Data 2"]
240+
]
241+
expected_result = (
242+
"\n|Header 1|Header 2|\n| :---: | :---: |\n"
243+
"|Data 1|Data 2|\n"
244+
)
245+
246+
result = table.create_table_array(data=data, text_align="center")
247+
self.assertEqual(result, expected_result)
248+
249+
def test_create_table_array_with_left_alignment(self):
250+
"""Test create_table_array with left text alignment."""
251+
table = Table()
252+
data = [
253+
["Header 1", "Header 2"],
254+
["Data 1", "Data 2"]
255+
]
256+
expected_result = (
257+
"\n|Header 1|Header 2|\n| :--- | :--- |\n"
258+
"|Data 1|Data 2|\n"
259+
)
260+
261+
result = table.create_table_array(data=data, text_align="left")
262+
self.assertEqual(result, expected_result)
263+
264+
def test_create_table_array_with_right_alignment(self):
265+
"""Test create_table_array with right text alignment."""
266+
table = Table()
267+
data = [
268+
["Header 1", "Header 2"],
269+
["Data 1", "Data 2"]
270+
]
271+
expected_result = (
272+
"\n|Header 1|Header 2|\n| ---: | ---: |\n"
273+
"|Data 1|Data 2|\n"
274+
)
275+
276+
result = table.create_table_array(data=data, text_align="right")
277+
self.assertEqual(result, expected_result)
278+
279+
def test_create_table_array_with_mixed_alignment(self):
280+
"""Test create_table_array with mixed text alignment per column."""
281+
table = Table()
282+
data = [
283+
["Left", "Center", "Right"],
284+
["L1", "C1", "R1"],
285+
["L2", "C2", "R2"]
286+
]
287+
expected_result = (
288+
"\n|Left|Center|Right|\n| :--- | :---: | ---: |\n"
289+
"|L1|C1|R1|\n|L2|C2|R2|\n"
290+
)
291+
292+
result = table.create_table_array(data=data, text_align=["left", "center", "right"])
293+
self.assertEqual(result, expected_result)
294+
295+
def test_create_table_array_uneven_rows(self):
296+
"""Test create_table_array with rows of different lengths."""
297+
table = Table()
298+
data = [
299+
["Header 1", "Header 2", "Header 3"],
300+
["Row 1 Col 1", "Row 1 Col 2"], # Missing third column
301+
["Row 2 Col 1"] # Missing second and third columns
302+
]
303+
expected_result = (
304+
"\n|Header 1|Header 2|Header 3|\n| --- | --- | --- |\n"
305+
"|Row 1 Col 1|Row 1 Col 2||\n"
306+
"|Row 2 Col 1|||\n"
307+
)
308+
309+
result = table.create_table_array(data=data)
310+
self.assertEqual(result, expected_result)
311+
312+
def test_create_table_array_mixed_data_types(self):
313+
"""Test create_table_array with mixed data types (converts to strings)."""
314+
table = Table()
315+
data = [
316+
["Name", "Age", "Score", "Active"],
317+
["Alice", 25, 95.5, True],
318+
["Bob", 30, 87.2, False],
319+
["Charlie", None, 92.0, True]
320+
]
321+
expected_result = (
322+
"\n|Name|Age|Score|Active|\n| --- | --- | --- | --- |\n"
323+
"|Alice|25|95.5|True|\n"
324+
"|Bob|30|87.2|False|\n"
325+
"|Charlie|None|92.0|True|\n"
326+
)
327+
328+
result = table.create_table_array(data=data)
329+
self.assertEqual(result, expected_result)
330+
331+
def test_create_table_array_empty_data(self):
332+
"""Test create_table_array with empty data."""
333+
table = Table()
334+
data = []
335+
expected_result = ""
336+
337+
result = table.create_table_array(data=data)
338+
self.assertEqual(result, expected_result)
339+
340+
def test_create_table_array_single_row(self):
341+
"""Test create_table_array with a single row."""
342+
table = Table()
343+
data = [["Single", "Row", "Data"]]
344+
expected_result = (
345+
"\n|Single|Row|Data|\n| --- | --- | --- |\n"
346+
)
347+
348+
result = table.create_table_array(data=data)
349+
self.assertEqual(result, expected_result)
350+
351+
def test_create_table_array_single_column(self):
352+
"""Test create_table_array with a single column."""
353+
table = Table()
354+
data = [["Header"], ["Row 1"], ["Row 2"]]
355+
expected_result = (
356+
"\n|Header|\n| --- |\n"
357+
"|Row 1|\n|Row 2|\n"
358+
)
359+
360+
result = table.create_table_array(data=data)
361+
self.assertEqual(result, expected_result)
362+
363+
def test_create_table_array_with_special_characters(self):
364+
"""Test create_table_array with special characters that need escaping."""
365+
table = Table()
366+
data = [
367+
["Header|With|Pipes", "Normal Header"],
368+
["Data|With|Pipes", "Normal Data"]
369+
]
370+
expected_result = (
371+
"\n|Header\\|With\\|Pipes|Normal Header|\n| --- | --- |\n"
372+
"|Data\\|With\\|Pipes|Normal Data|\n"
373+
)
374+
375+
result = table.create_table_array(data=data)
376+
self.assertEqual(result, expected_result)
377+
378+
def test_create_table_array_rows_with_empty_lists(self):
379+
"""Test create_table_array with some empty rows."""
380+
table = Table()
381+
data = [
382+
["Header 1", "Header 2"],
383+
[], # Empty row
384+
["Row 2 Col 1", "Row 2 Col 2"]
385+
]
386+
expected_result = (
387+
"\n|Header 1|Header 2|\n| --- | --- |\n"
388+
"|||\n"
389+
"|Row 2 Col 1|Row 2 Col 2|\n"
390+
)
391+
392+
result = table.create_table_array(data=data)
393+
self.assertEqual(result, expected_result)
394+
395+
def test_create_table_array_with_partial_alignment_list(self):
396+
"""Test create_table_array with alignment list shorter than number of columns."""
397+
table = Table()
398+
data = [
399+
["Col 1", "Col 2", "Col 3"],
400+
["Data 1", "Data 2", "Data 3"]
401+
]
402+
expected_result = (
403+
"\n|Col 1|Col 2|Col 3|\n| :--- | :---: | --- |\n"
404+
"|Data 1|Data 2|Data 3|\n"
405+
)
406+
407+
result = table.create_table_array(data=data, text_align=["left", "center"])
408+
self.assertEqual(result, expected_result)

0 commit comments

Comments
 (0)