Commit 62a4c4a4 authored by ankraft's avatar ankraft
Browse files

Adapted debug output. Corrections in the main script. Renamed wrong filename

parent 06c05a87
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -3,3 +3,4 @@
*/ts-*
*/.python-version
.python-version
toMkdocs/__pycache__
+123 −106
Original line number Diff line number Diff line
@@ -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
+37 −21
Original line number Diff line number Diff line
@@ -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)

+47 −12
Original line number Diff line number Diff line
@@ -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)