From 62a4c4a4e182ff641fda5afc32eb2977fce963f9 Mon Sep 17 00:00:00 2001
From: ankraft <an.kraft@gmail.com>
Date: Fri, 21 Feb 2025 14:33:30 +0100
Subject: [PATCH] Adapted debug output. Corrections in the main script. Renamed
 wrong filename

---
 .gitignore                                    |   1 +
 toMkdocs/gridTableTools.py                    | 229 ++++++++++--------
 .../{makrdownTools.py => markdownTools.py}    |  58 +++--
 toMkdocs/toMkdocs.py                          |  59 ++++-
 4 files changed, 208 insertions(+), 139 deletions(-)
 rename toMkdocs/{makrdownTools.py => markdownTools.py} (92%)

diff --git a/.gitignore b/.gitignore
index 6d0869a..4fb7084 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
 */ts-*
 */.python-version
 .python-version
+toMkdocs/__pycache__
diff --git a/toMkdocs/gridTableTools.py b/toMkdocs/gridTableTools.py
index c690c2c..22bc895 100644
--- a/toMkdocs/gridTableTools.py
+++ b/toMkdocs/gridTableTools.py
@@ -6,10 +6,25 @@
 #
 """ Tools for working with grid tables in markdown files. """
 
-from typing import Optional
+from typing import Optional, Callable
 from regexMatches import *
 
 
+_alignLeft = 'align="left"'
+_alignRight = 'align="right"'
+_alignCenter = 'align="center"'
+
+printInfo = print
+printDebug = print
+printError = print
+
+def setLoggers(info:Callable=print, debug:Callable=print, error:Callable=print) -> None:
+	global printInfo, printDebug, printError
+
+	printInfo = info
+	printDebug = debug
+	printError = error
+
 
 class GridCell:
 	"""	Represents a grid table cell. """
@@ -27,8 +42,18 @@ class GridCell:
 		self.auxiliarIndex:int = 0
 
 
-	def calculateAndSetAlignment(self, headerDelimiterPositions:list[int], delimiterPositions:list[int], defaultAlignments:list[str], hasHeader:bool) -> None:
+	def calculateAndSetAlignment(self, 
+							  	 headerDelimiterPositions:list[int], 
+							  	 delimiterPositions:list[int], 
+								 defaultAlignments:list[str], 
+								 hasHeader:bool) -> None:
 		"""	Set the alignment of the cell based on the position of the delimiter. 
+
+			Args:
+				headerDelimiterPositions: The positions of the header delimiters.
+				delimiterPositions: The positions of the delimiters.
+				defaultAlignments: The default alignments.
+				hasHeader: True if the table has a header, False otherwise.
 		"""
 		if self.position is None:
 			raise ValueError('Cell position must be set before calculating alignment.')
@@ -46,17 +71,17 @@ class GridCell:
 			else:
 				raise ValueError('Invalid table formatting')
 		else:
-			body_delimiter_index = 0
-			while body_delimiter_index in range(len(defaultAlignments)) and self.position > delimiterPositions[body_delimiter_index]:
-				body_delimiter_index += 1
-			if body_delimiter_index in range(len(defaultAlignments)):
-				if self.position < delimiterPositions[body_delimiter_index]:
-					self.alignment = defaultAlignments[body_delimiter_index]
-				elif self.position == delimiterPositions[body_delimiter_index]:
-					self.alignment = defaultAlignments[body_delimiter_index]
-					body_delimiter_index += 1
+			bodyDelimiterIndex = 0
+			while bodyDelimiterIndex < len(defaultAlignments) and self.position > delimiterPositions[bodyDelimiterIndex]:
+				bodyDelimiterIndex += 1
+			if bodyDelimiterIndex < len(defaultAlignments):
+				if self.position < delimiterPositions[bodyDelimiterIndex]:
+					self.alignment = defaultAlignments[bodyDelimiterIndex]
+				elif self.position == delimiterPositions[bodyDelimiterIndex]:
+					self.alignment = defaultAlignments[bodyDelimiterIndex]
+					bodyDelimiterIndex += 1
 			else:
-				raise ValueError("Invalid table formatting")
+				raise ValueError('Invalid table formatting')
 
 	
 	def __str__(self):
@@ -91,6 +116,7 @@ class GridRow():
 	def __repr__(self):
 		return self.__str__()
 	
+
 class GridRowsTracker():
 	"""	Represents the document object. """
 	def __init__(self, size:int) -> None:
@@ -155,9 +181,10 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 				cell.content = _c + nextListElementMark  # Add list element end mark to know when the list element ends		
 			elif cell.listFlag and len(_c) > 0:  # any other content when handling list is concatenated to the last list element
 				_c = re.sub(r'\\\s*$', '\n', _c)
-				cell.content += _c + nextListElementMark #add the list element end mark
-			elif not _c:  # separation between list and other paragraph
-				cell.content += '\n' if not cell['content'].endswith('\n') else ""
+				cell.content = _c + nextListElementMark #add the list element end mark
+			elif not _c:  # empty line. separation between list and other paragraph
+				# cell.content = '\n' if not cell.content.endswith('\n') else ""
+				cell.content = '\n' # cell content is always empty / None here.
 			else:
 				cell.content = re.sub(r'\\\s*$', '\n', _c)
 		else: # Cell has content
@@ -173,8 +200,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 				_c = re.sub(r'\\\s*$', '\n', _c)
 				cell.content += " " + _c + nextListElementMark #add list element end mark
 			elif len(_c) == 0:  # separation between list and other paragraph
-				if cell.list_flag:
-					cell.list_flag = False
+				if cell.listFlag:
+					cell.listFlag = False
 					cell.content += '\n\n' #end list by \n
 				#content = re.sub(r'\\\s*$', "\n", content.strip())
 				cell.content += '\n' if not cell.content.endswith('\n') else ''
@@ -205,7 +232,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 
 		row[columnIndex].colspanAdjusted = True	# Mark cell as adjusted
 
-	def check_delimiter_alignment(line: str, delimiterPositions:list[int], delimiters: str = "|+") -> bool:
+
+	def checkDelimiterAlignment(line: str, delimiterPositions:list[int], delimiters: str = "|+") -> bool:
 		"""
 		Check if delimiters in a row align with expected positions.
 		
@@ -220,33 +248,29 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 		if not line or not delimiterPositions:
 			return False
 		
-		print(f"\nChecking line: '{line}'")
-		print(f"Expected delimiter positions: {delimiterPositions}")
+		printDebug(f'\nChecking line: "{line}"')
+		printDebug(f'Expected delimiter positions: {delimiterPositions}')
 		
 		# For full separator lines (only +)
 		if '+' in line and '|' not in line:
-			currentPositions = [i for i, char in enumerate(line) if (char == '+' and i != 0)]
-			print(f"Full separator line - Found + at positions: {currentPositions}")
-			return all(delimiterPositions[-1] in currentPositions and 
-					line.startswith("+") and
-					pos in delimiterPositions for pos in currentPositions)
+			currentPositions = [i for i, char in enumerate(line) if (char == '+' and i > 0)]
+			printDebug(f'Full separator line - Found + at positions: {currentPositions}')
+			return all(delimiterPositions[-1] in currentPositions and line.startswith('+') and pos in delimiterPositions 
+					   for pos in currentPositions)
 		
 		# For data lines (only |)
 		if '|' in line and '+' not in line:
-			currentPositions = [i for i, char in enumerate(line) if (char == '|' and i != 0)]
-			print(f"Data line - Found | at positions: {currentPositions}")
-			return all(delimiterPositions[-1] in currentPositions and 
-					line.startswith("|") and
-					pos in delimiterPositions for pos in currentPositions)
+			currentPositions = [i for i, char in enumerate(line) if (char == '|' and i > 0)]
+			printDebug(f'Data line - Found | at positions: {currentPositions}')
+			return all(delimiterPositions[-1] in currentPositions and line.startswith("|") and pos in delimiterPositions 
+			  		   for pos in currentPositions)
 		
 		# For partial separators (mix of + and |)
-		currentPositions = [i for i, char in enumerate(line) if (char in delimiters and i != 0)]
-		print(f"Partial separator - Found delimiters at positions: {currentPositions}")
-		print(f"Characters at those positions: {[line[pos] for pos in currentPositions]}")
-		return all(delimiterPositions[-1] in currentPositions and 
-				(line.startswith("+") or line.startswith("|")) and
-				pos in delimiterPositions for pos in currentPositions)
-
+		currentPositions = [i for i, char in enumerate(line) if (char in delimiters and i > 0)]
+		printDebug(f'Partial separator - Found delimiters at positions: {currentPositions}')
+		printDebug(f'Characters at those positions: {[line[pos] for pos in currentPositions]}')
+		return all(delimiterPositions[-1] in currentPositions and line.startswith(('+', '|')) and pos in delimiterPositions 
+			 	   for pos in currentPositions)
 
 	separatorIndices = [i for i, line in enumerate(lines) if isSeparator(line)]
 
@@ -254,8 +278,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 		raise ValueError('No valid separators found in the provided grid table.')
 
 	# Calculate max number of columns
-	delimiterPositions:list[int] = []
-	numberOfColumns = 0
+	delimiterPositions = []
+	numberOfColumns:int = 0
 
 	for separatorIndex in separatorIndices:
 		if (_cnt := lines[separatorIndex].count('+') - 1) > numberOfColumns:
@@ -263,10 +287,10 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 			delimiterPositions = []
 			for rowIndex in range(numberOfColumns):
 				delimiterPositionsStart = delimiterPositions[rowIndex - 1] if rowIndex != 0 else 0
-				delPositions = [lines[separatorIndex].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[separatorIndex][delimiterPositionsStart + 1:]]
+				delPositions = [lines[separatorIndex].find(delimiter, delimiterPositionsStart + 1) 
+								for delimiter in '+' if delimiter in lines[separatorIndex][delimiterPositionsStart + 1:]]
 				delimiterPositions.append(min(delPositions) if delPositions else -1)
 	
-
 	# Determine delimter positions and alignments
 	headerRows:GridTableRowList = []
 	dataRows:GridTableRowList = []
@@ -278,15 +302,22 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 			parts = re.split(r'\+', lines[index].strip('+'))
 			#Calculate default alignments and positions of delimiters
 			for partIndex in range(len(parts)):
-				if parts[partIndex].startswith(':') and not parts[partIndex].endswith(':'):	# Left alignment
-					defaultAlignments.append('align="left"')
-				elif not parts[partIndex].startswith(':') and parts[partIndex].endswith(':'): # Right alignment
-					defaultAlignments.append('align="right"')
+				# Left alignment
+				if parts[partIndex].startswith(':') and not parts[partIndex].endswith(':'):	
+					defaultAlignments.append(_alignLeft)
+
+				# Right alignment
+				elif not parts[partIndex].startswith(':') and parts[partIndex].endswith(':'): 
+					defaultAlignments.append(_alignRight)
+
+				# Center alignment
 				else:
-					defaultAlignments.append('align="center"')	# Center alignment
+					defaultAlignments.append(_alignCenter)	
+
 				# Delimiter position
 				delimiterPositionsStart = delimiterPositions[partIndex - 1] if partIndex != 0 else 0
-				delPositions = [lines[index].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[index][delimiterPositionsStart + 1:]]
+				delPositions = [lines[index].find(delimiter, delimiterPositionsStart + 1) 
+								for delimiter in '+' if delimiter in lines[index][delimiterPositionsStart + 1:]]
 				headerDelimiterPositions.append(min(delPositions) if delPositions else -1)
 
 	if not hasHeader:
@@ -296,11 +327,13 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 		# Calculate default alignments and positions of delimiters
 		for part_index in range(len(parts)):
 			if parts[part_index].startswith(':') and not parts[part_index].endswith(':'):
-				default_alignments.append('align="left"')
+				default_alignments.append(_alignLeft)
+
 			elif not parts[part_index].startswith(':') and parts[part_index].endswith(':'):
-				default_alignments.append('align="right"')
+				default_alignments.append(_alignRight)
+
 			else:
-				default_alignments.append('align="center"')
+				default_alignments.append(_alignCenter)
 
 	for rowNumber in range(len(separatorIndices) - 1):
 		rows:list[GridRow] = []
@@ -314,8 +347,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 				if isSeparator(line) and not inDataRow:
 					inDataRow = True
 					# Add delimiter alignment check for separator lines
-					if not check_delimiter_alignment(line, delimiterPositions):
-						raise ValueError(f"Misaligned delimiters in separator row: {line}")
+					if not checkDelimiterAlignment(line, delimiterPositions):
+						raise ValueError(f'Misaligned delimiters in separator row: {line}')
 					
 					parts = re.split(r'\s*\+\s*', line.strip('+'))
 					delimiterIndex = 0
@@ -343,17 +376,17 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 					# Regular data row or partial separator
 					if matchGridTableBodySeparator.match(line): # Partial separator
 						# Add delimiter alignment check for partial separators
-						if not check_delimiter_alignment(line, delimiterPositions):
-							raise ValueError(f"Misaligned delimiters in partial separator: {line}")
+						if not checkDelimiterAlignment(line, delimiterPositions):
+							raise ValueError(f'Misaligned delimiters in partial separator: {line}')
 
-						cellsContent = re.split(r"[\|\+]", line.strip('|').strip('+'))  # (?<!\\)[\|\+]
+						cellsContent = re.split(r'[\|\+]', line.strip('|').strip('+'))  # (?<!\\)[\|\+]
 						#Add another row, set delimiters for each cell
 						rows.append(GridRow(numberOfColumns))
 						auxDelimiterIndex = 0
 						auxiliarCellIndex = 0
 
 						for columnIndex, content in enumerate(cellsContent):
-							if auxiliarCellIndex in range(numberOfColumns):
+							if auxiliarCellIndex < numberOfColumns:
 								auxDelimiterIndex += len(content) + 1
 								cell = rows[-1][auxiliarCellIndex]
 								cell.position = auxDelimiterIndex  # Position of cell delimiter +
@@ -398,15 +431,14 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 									continue
 
 						else:
-							raise ValueError("More cells than columns found")
+							raise ValueError('More cells than columns found')
 						
 					else: # Data row
-						cellsContent = line.strip()
-						cellsContent = re.split(r"\|", line.strip('|'))
+						cellsContent = re.split(r'\|', line.strip('|'))
 						
 						# Add delimiter alignment check
-						if not check_delimiter_alignment(line, delimiterPositions):
-							raise ValueError(f"Misaligned delimiters in row: {line}")
+						if not checkDelimiterAlignment(line, delimiterPositions):
+							raise ValueError(f'Misaligned delimiters in row: {line}')
 							
 						columnCellIndex = 0
 						if len(cellsContent) < numberOfColumns: # Colspan: Positions of | with respect to + need to be determined
@@ -426,9 +458,9 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 								rowIndex = rowsTracker[columnIndex]
 								handleCellContent(rows[rowIndex][columnIndex], content)
 						else:
-							raise ValueError("More cells than columns found")
+							raise ValueError('More cells than columns found')
 				else:
-					raise ValueError("No separator line found for row starting")
+					raise ValueError('No separator line found for row starting')
 
 			if hasHeader and start >= headerSeparatorIndex: # table_row and auxiliar_row are part of data_rows
 				for row in rows:
@@ -451,7 +483,7 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 			for cell in gridRow:
 				if cell.content is not None:
 					# Replacing "<" by &lt;
-					cell.content = cell.content.replace("<", "&lt;")
+					cell.content = cell.content.replace('<', '&lt;')
 
 					# Bold replacements
 					# Regex to detect markdown bold formatting in cell content
@@ -463,7 +495,6 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR
 					if cell.content is not None:
 						cell.content = matchItalic.sub(r'<i>\g<text></i>', cell.content)
 
-
 	# Correct newlines characters
 	for headerRow in headerRows:
 		for cell in headerRow:
@@ -525,25 +556,11 @@ def generateHtmlTableWithSpans(gridTable:str) -> str:
 		Returns:
 			The HTML table in string format.
 	"""
-	debug_output = []
-	def debug_print(msg):
-		debug_output.append(str(msg))  # Convert message to string
-
 	try:
-		# Redirect print statements to our debug collector
-		global print
-		original_print = print
-		print = debug_print
-
 		gridHeader, gridBody = parseGridTableWithSpans(gridTable)
-		
-		# Restore original print
-		print = original_print
-
 	except Exception as e:
-		debug_print("Grid table could not be generated")
-		debug_text = "<br>".join(debug_output)  # Now all items are strings
-		return f'HTML TABLE COULD NOT BE GENERATED FROM MARKDOWN GRID TABLE.<br><pre>{debug_text}</pre>'
+		printDebug('Grid table could not be generated')
+		return f'HTML TABLE COULD NOT BE GENERATED FROM MARKDOWN GRID TABLE'
 		
 	# Generate table HTML...
 	html = '<table>\n'
@@ -564,47 +581,47 @@ def generateHtmlTableWithSpans(gridTable:str) -> str:
 					continue
 				else:
 					# Prepare content, in case there's a list
-					if cell.content is not None and (matches := re.findall(r"\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@", cell.content)):  # Update cell in new row
-						list = "<ul>"
+					if cell.content is not None and (matches := re.findall(r'\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@', cell.content)):  # Update cell in new row
+						list = '<ul>'
 						# Build list the matches
 						for match in matches:
-							list += "<li>" + match[1] + "</li>"
-						list += "</ul>"
-						cell.content = re.sub(r"(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+", list, cell.content)
+							list += '<li>' + match[1] + '</li>'
+						list += '</ul>'
+						cell.content = re.sub(r'(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+', list, cell.content)
 						# Enforce left alignment if cell contains a list
-						cell.alignment = "align=\"left\""
+						cell.alignment = _alignLeft
 
-					rowspan = f" rowspan=\"{cell.rowspan}\"" if cell.rowspan > 1 else ""
-					colspan = f" colspan=\"{cell.colspan}\"" if cell.colspan > 1 else ""
-					html += f"            <th{rowspan}{colspan} {cell.alignment}>{cell.content}</th>\n"
-			html += "        </tr>\n"
-		html += "    </thead>\n"
+					rowspan = f' rowspan="{cell.rowspan}"' if cell.rowspan > 1 else ''
+					colspan = f' colspan="{cell.colspan}"' if cell.colspan > 1 else ''
+					html +=   f'            <th{rowspan}{colspan} {cell.alignment}>{cell.content}</th>\n'
+			html += '        </tr>\n'
+		html += '    </thead>\n'
 
 
-	html += "    <tbody>\n"
+	html += '    <tbody>\n'
 	for row in gridBody:
-		html += "        <tr>\n"
+		html += '        <tr>\n'
 		for cell in row:
 			if cell.rowspan == 0 or cell.colspan == 0:
 				continue
 			else:
 				#Prepare content, in case there's a list
-				if cell.content is not None and (matches := re.findall(r"\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@", cell.content)):  # Update cell in new row
-					list = "<ul>"
+				if cell.content is not None and (matches := re.findall(r'\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@', cell.content)):  # Update cell in new row
+					list = '<ul>'
 					# Build list the matches
 					for match in matches:
-						list += "<li>" + match[1] + "</li>"
-					list += "</ul>"
-					cell.content = re.sub(r"(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+",list, cell.content)
+						list += f'<li>{match[1]}</li>'
+					list += '</ul>'
+					cell.content = re.sub(r'(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+', list, cell.content)
 					# Enforce left alignment if cell contains a list
-					cell.alignment = "align=\"left\""
+					cell.alignment = _alignLeft
 
-				rowspan = f" rowspan=\"{cell.rowspan}\"" if cell.rowspan > 1 else ""
-				colspan = f" colspan=\"{cell.colspan}\"" if cell.colspan > 1 else ""
-				html += f"            <td{rowspan}{colspan} {cell.alignment}>{cell.content}</td>\n"
-		html += "        </tr>\n"
+				rowspan = f' rowspan="{cell.rowspan}"' if cell.rowspan > 1 else ''
+				colspan = f' colspan="{cell.colspan}"' if cell.colspan > 1 else ''
+				html +=   f'            <td{rowspan}{colspan} {cell.alignment}>{cell.content}</td>\n'
+		html += '        </tr>\n'
 
-	html += "    </tbody>\n"
-	html += "</table>"
+	html += '    </tbody>\n'
+	html += '</table>'
 	return html
 
diff --git a/toMkdocs/makrdownTools.py b/toMkdocs/markdownTools.py
similarity index 92%
rename from toMkdocs/makrdownTools.py
rename to toMkdocs/markdownTools.py
index 169b634..56ea86e 100644
--- a/toMkdocs/makrdownTools.py
+++ b/toMkdocs/markdownTools.py
@@ -9,12 +9,13 @@
 """ Various tools for markdown processing
 """
 from __future__ import annotations
+from typing import Callable
 
 from dataclasses import dataclass
 import base64, hashlib
 from enum import Enum, auto
 
-from gridTableTools import generateHtmlTableWithSpans
+from gridTableTools import generateHtmlTableWithSpans, setLoggers as setGridTableLoggers
 from regexMatches import *
 
 # TODO use a verbosity level instead
@@ -23,12 +24,18 @@ veryVerbose = False
 
 printInfo = print
 printDebug = print
+printError = print
 
-def setScreenPrinters(info:callable = print, debug:callable = print) -> None:
-	global printInfo, printDebug
+def setLoggers(info:Callable = print, debug:Callable = print, error:Callable= print) -> None:
+	global printInfo, printDebug, printError
 
 	printInfo = info
 	printDebug = debug
+	printError = error
+
+	# Set the loggers for the grid table tools
+	setGridTableLoggers(info, debug, error)
+
 
 
 def _shortHash(value:str, length:int) -> str:
@@ -365,6 +372,28 @@ def analyseMarkdown(filename:str) -> Document:
 			The document object.
 	"""
 
+	gridTable:str = ''
+
+	def processGridTable() -> None:
+		"""	Process a grid table and convert it to an html table.
+
+			This function adds the html table to the output clauses and
+			clears the gridTable variable.
+		"""
+		nonlocal gridTable
+		
+		htmltable:str = ''
+		try:
+			htmltable = generateHtmlTableWithSpans(gridTable)
+			printDebug(htmltable)
+		except Exception as e:
+			printError(f"Error: {e}")
+		# TODO move this outside of the analyseMarkdown function !!!
+		for row in htmltable:
+			outClauses[-1].append(Line(row, LineType.TABLEROW))
+		gridTable = ''
+
+
 	printInfo(f'Analyzing "{filename}"')
 
 	# Read the file.
@@ -381,10 +410,7 @@ def analyseMarkdown(filename:str) -> Document:
 	inTable = False
 	tableHasSeparator = False
 	inGridTable = False
-	gridTableHasSeparator = False
-	gridTable = ""
 	for line in inLines:
-
 		# Detect and handle codefences
 		# For the moment we support only codefences that start and end
 		# with 3 backticks. This is the most common way to define codefences.
@@ -439,18 +465,7 @@ def analyseMarkdown(filename:str) -> Document:
 				continue
 			else:
 				inGridTable = False
-				# Mark the previous line as the last row in the table
-				#outClauses[-1].lines[-1].lineType = LineType.TABLELASTROW
-				# print(gridTable)
-				try:
-					htmltable = generateHtmlTableWithSpans(gridTable)
-					print(htmltable)
-				except Exception as e:
-					print(f"Error: {e}")
-				# TODO move this outside of the analyseMarkdown function !!!
-				for row in htmltable:
-					outClauses[-1].append(Line(row, LineType.TABLEROW))
-				gridTable = ""
+				processGridTable()
 		# continue with other matches
 
 		# Detect notes
@@ -486,9 +501,10 @@ def analyseMarkdown(filename:str) -> Document:
 		# Just add the line to the current clause as text
 		outClauses[-1].append(Line(line, _lineType))
 
-	return Document(outClauses, footnotes)
-
-
+	# Process still unfinished cases
+	if gridTable:
+		processGridTable()
 
+	return Document(outClauses, footnotes)
 
 
diff --git a/toMkdocs/toMkdocs.py b/toMkdocs/toMkdocs.py
index 69037d1..af4a286 100644
--- a/toMkdocs/toMkdocs.py
+++ b/toMkdocs/toMkdocs.py
@@ -12,13 +12,49 @@ from __future__ import annotations
 
 import argparse, os, shutil
 from rich import print
-from makrdownTools import Line, Document, analyseMarkdown, setScreenPrinters
+from markdownTools import Line, Document, analyseMarkdown, setLoggers
 from regexMatches import match2spaceListIndention
 
 verbose = False
 veryVerbose = False
 
 
+def printDebug(text:str) -> None:
+	"""	Print a debug message.
+
+		Args:
+			text: The text of the debug message.
+	"""
+	if verbose:
+		print(f'[dim]{text}')
+
+
+def printInfo(text:str) -> None:
+	"""	Print an information message.
+
+		Args:
+			text: The text of the information message.
+	"""
+	print(f'[green]{text}')
+
+
+def printWarning(text:str) -> None:
+	"""	Print a warning message.
+
+		Args:
+			text: The text of the warning message.
+	"""
+	print(f'[yellow]{text}')
+
+
+def printError(text:str) -> None:
+	"""	Print an error message.
+
+		Args:
+			text: The text of the error message.
+	"""
+	print(f'[red]{text}')
+
 def prepareForMkdocs(document:Document, includeHangingParagraphs:bool = False) -> None:
 	"""	Prepare the clauses for MkDocs. This includes removing the heading
 		from the clauses and marking the clauses that are only for navigation.
@@ -47,7 +83,7 @@ def prepareForMkdocs(document:Document, includeHangingParagraphs:bool = False) -
 			# Check if there is a sub-clause in the next clause
 			if i + 1 < len(document.clauses) and document.clauses[i+1].level > clause.level:
 				# This is a hanging paragraph. Remove the text from the current clause.
-				print(f'[yellow]Hanging paragraph in clause "{clause.title}" {"(removed)" if not includeHangingParagraphs else "(kept)"}')
+				printWarning(f'Hanging paragraph in clause "{clause.title}" {"(removed)" if not includeHangingParagraphs else "(kept)"}')
 				if not includeHangingParagraphs:
 					document.clauses[i].lines = []
 				else:
@@ -72,15 +108,14 @@ def writeClausesMkDocs(document:Document, filename:str, navTitle:str, addNavTitl
 			addNavTitle: Add the title as an extra navigation level to the navigation file.
 	"""
 
-	print(f'[green]Writing clauses to files')
+	printInfo(f'Writing clauses to files')
 	# create directory first
 	os.makedirs(f'{os.path.dirname(filename)}/{navTitle}', exist_ok = True)
 
 	# Write the files
 	for i, f in enumerate(document.clauses):
 		# write to single files, even empty ones
-		if verbose:
-			print(f'[dim]Writing "{f.clauseNumber}.md" - "{f.title}"')
+		printDebug(f'Writing "{f.clauseNumber}.md" - "{f.title}"')
 		with open(f'{os.path.dirname(filename)}/{navTitle}/{f.clauseNumber}.md', 'w') as file:
 			# Add one empty line before the clause. This is done to avoid
 			# a bug in MkDocs that does not display the first line of a clause
@@ -90,11 +125,10 @@ def writeClausesMkDocs(document:Document, filename:str, navTitle:str, addNavTitl
 
 	
 	# write nav.yml file
-	print(f'[green]Writing "_nav.yml"')
+	printInfo(f'Writing "_nav.yml"')
 	indentation = '  ' if addNavTitle else ''	# TODO make number of spaces configurable
 	with open(f'{os.path.dirname(filename)}/_nav.yml', 'w') as file:
-		if veryVerbose:
-			print(f'[dim]Writing navigation file')
+		printDebug(f'Writing navigation file')
 		if addNavTitle:
 			file.write(f'{indentation}- {navTitle}:\n')
 		for i, f in enumerate(document.clauses):
@@ -130,10 +164,10 @@ def copyMediaFiles(filename:str, navTitle:str, mediaDirectory:str = 'media') ->
 	targetDirectory = f'{os.path.dirname(filename)}/{navTitle}/{mediaDirectory}'
 
 	if os.path.exists(sourceDirectory):
-		print(f'[green]Copying media files from "{sourceDirectory}" to "{targetDirectory}"')
+		printInfo(f'Copying media files from "{sourceDirectory}" to "{targetDirectory}"')
 		shutil.copytree(sourceDirectory, targetDirectory, dirs_exist_ok = True)
 	else:
-		print(f'[red]Media directory "{sourceDirectory}" does not exist')
+		printError(f'Media directory "{sourceDirectory}" does not exist')
 
 	
 def processDocument(args:argparse.Namespace) -> None:
@@ -177,8 +211,9 @@ def main() -> None:
 
 	parser.add_argument('document', type = str, help = 'a oneM2M markdown specification document to process')
 	args = parser.parse_args()
-	setScreenPrinters(info = lambda text: print(f'[green]{text}'), 
-				   	  debug = lambda text: print(f'[dim]{text}'))
+	setLoggers(info = printInfo, 
+			   debug = printDebug,
+			   error = printError)
 	processDocument(args)
 
 
-- 
GitLab