Coverage for src/pyTRLCConverter/markdown/element.py: 97%

75 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-26 12:41 +0000

1"""Markdown block elements. 

2 Each element renders itself into a Markdown string. Elements are collected 

3 by the MarkdownDocument and rendered once when the output file is written. 

4 

5 Author: Andreas Merkle (andreas.merkle@newtec.de) 

6""" 

7 

8# pyTRLCConverter - A tool to convert TRLC files to specific formats. 

9# Copyright (c) 2024 - 2026 NewTec GmbH 

10# 

11# This file is part of pyTRLCConverter program. 

12# 

13# The pyTRLCConverter program is free software: you can redistribute it and/or modify it under 

14# the terms of the GNU General Public License as published by the Free Software Foundation, 

15# either version 3 of the License, or (at your option) any later version. 

16# 

17# The pyTRLCConverter program is distributed in the hope that it will be useful, but 

18# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 

19# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 

20# 

21# You should have received a copy of the GNU General Public License along with pyTRLCConverter. 

22# If not, see <https://www.gnu.org/licenses/>. 

23 

24# Imports ********************************************************************** 

25import os 

26from abc import ABC, abstractmethod 

27from typing import List 

28from pyTRLCConverter.logger import log_error 

29from pyTRLCConverter.markdown.text import MarkdownText 

30 

31# Variables ******************************************************************** 

32 

33# Classes ********************************************************************** 

34 

35# pylint: disable-next=too-few-public-methods 

36class MarkdownElement(ABC): 

37 """Abstract base class for Markdown block elements. 

38 """ 

39 

40 @abstractmethod 

41 def render(self) -> str: 

42 """Render the element into a Markdown string. 

43 

44 Returns: 

45 str: Rendered Markdown 

46 """ 

47 

48# pylint: disable-next=too-few-public-methods 

49class Heading(MarkdownElement): 

50 """Markdown heading block element. 

51 """ 

52 

53 def __init__(self, text: str, level: int, escape: bool = True) -> None: 

54 """Initialize the heading element. 

55 

56 Args: 

57 text (str): Heading text 

58 level (int): Heading level [1; inf] 

59 escape (bool): Escape the text (default: True). 

60 """ 

61 self._text = text 

62 self._level = level 

63 self._escape = escape 

64 

65 def render(self) -> str: 

66 # lobster-trace: SwRequirements.sw_req_markdown_heading 

67 """Render the heading. 

68 The text will be automatically escaped for Markdown if necessary. 

69 

70 Returns: 

71 str: Markdown heading 

72 """ 

73 result = "" 

74 

75 if 1 <= self._level: 

76 text_raw = self._text 

77 

78 if self._escape is True: 

79 text_raw = MarkdownText.escape(self._text) 

80 

81 result = f"{'#' * self._level} {text_raw}\n" 

82 

83 else: 

84 log_error(f"Invalid heading level {self._level} for {self._text}.") 

85 

86 return result 

87 

88# pylint: disable-next=too-few-public-methods 

89class Table(MarkdownElement): 

90 """Markdown table block element. 

91 Rendered as HTML to support multi-line cells and other complex content. 

92 """ 

93 

94 def __init__(self, column_titles: List[str], rows: List[List[str]]) -> None: 

95 """Initialize the table element. 

96 

97 Args: 

98 column_titles (List[str]): List of column titles. 

99 rows (List[List[str]]): List of row values. 

100 """ 

101 self._column_titles = column_titles 

102 self._rows = rows 

103 

104 def render(self) -> str: 

105 # lobster-trace: SwRequirements.sw_req_markdown_table 

106 """Render the table in HTML format. 

107 

108 Returns: 

109 str: Markdown table 

110 """ 

111 table = "<table>\n" 

112 table += "<thead>\n" 

113 table += "<tr>\n" 

114 

115 for column_title in self._column_titles: 

116 table += f"<th>{column_title}</th>\n" 

117 

118 table += "</tr>\n" 

119 table += "</thead>\n" 

120 table += "<tbody>\n" 

121 

122 for row_values in self._rows: 

123 table += "<tr>\n" 

124 

125 for cell_value in row_values: 

126 # To allow Markdown content inside table cells, a blank line is required 

127 # before and after the cell content. 

128 # See https://spec.commonmark.org/0.31.2/#html-blocks 

129 table += "<td>\n" 

130 table += "\n" 

131 table += f"{cell_value}\n" 

132 table += "\n" 

133 table += "</td>\n" 

134 

135 table += "</tr>\n" 

136 

137 table += "</tbody>\n" 

138 table += "</table>\n" 

139 

140 return table 

141 

142# pylint: disable-next=too-few-public-methods 

143class BulletList(MarkdownElement): 

144 """Unordered Markdown list block element. 

145 """ 

146 

147 def __init__(self, values: List[str], escape: bool = True) -> None: 

148 """Initialize the list element. 

149 

150 Args: 

151 values (List[str]): List of list values. 

152 escape (bool): Escapes every list value (default: True). 

153 """ 

154 self._values = values 

155 self._escape = escape 

156 

157 def render(self) -> str: 

158 # lobster-trace: SwRequirements.sw_req_markdown_list 

159 """Render the unordered list. 

160 The values will be automatically escaped for Markdown if necessary. 

161 

162 Returns: 

163 str: Markdown list 

164 """ 

165 list_str = "" 

166 

167 for value_raw in self._values: 

168 value = value_raw 

169 

170 if self._escape is True: # Escape the value if necessary. 

171 value = MarkdownText.escape(value) 

172 

173 list_str += f"- {value}\n" 

174 

175 return list_str 

176 

177# pylint: disable-next=too-few-public-methods 

178class Image(MarkdownElement): 

179 """Markdown image / diagram link block element. 

180 """ 

181 

182 def __init__(self, file_name: str, caption: str, escape: bool = True) -> None: 

183 """Initialize the image element. 

184 

185 Args: 

186 file_name (str): Diagram file name 

187 caption (str): Diagram caption 

188 escape (bool): Escapes caption (default: True). 

189 """ 

190 self._file_name = file_name 

191 self._caption = caption 

192 self._escape = escape 

193 

194 def render(self) -> str: 

195 # lobster-trace: SwRequirements.sw_req_markdown_image 

196 """Render the image link. 

197 The caption will be automatically escaped for Markdown if necessary. 

198 

199 Returns: 

200 str: Markdown diagram link 

201 """ 

202 caption_raw = self._caption 

203 

204 if self._escape is True: 

205 caption_raw = MarkdownText.escape(self._caption) 

206 

207 # Allowed are absolute and relative to source paths. 

208 file_name = os.path.normpath(self._file_name) 

209 

210 return f"![{caption_raw}]({file_name})\n" 

211 

212# pylint: disable-next=too-few-public-methods 

213class RawText(MarkdownElement): 

214 """Raw Markdown text block element passed through unchanged. 

215 """ 

216 

217 def __init__(self, text: str) -> None: 

218 """Initialize the raw text element. 

219 

220 Args: 

221 text (str): Raw Markdown text 

222 """ 

223 self._text = text 

224 

225 def render(self) -> str: 

226 # lobster-trace: SwRequirements.sw_req_markdown_record 

227 """Render the raw text. 

228 

229 Returns: 

230 str: Raw Markdown text 

231 """ 

232 return self._text 

233 

234# Functions ******************************************************************** 

235 

236# Main *************************************************************************