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
« 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.
5 Author: Andreas Merkle (andreas.merkle@newtec.de)
6"""
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/>.
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
31# Variables ********************************************************************
33# Classes **********************************************************************
35# pylint: disable-next=too-few-public-methods
36class MarkdownElement(ABC):
37 """Abstract base class for Markdown block elements.
38 """
40 @abstractmethod
41 def render(self) -> str:
42 """Render the element into a Markdown string.
44 Returns:
45 str: Rendered Markdown
46 """
48# pylint: disable-next=too-few-public-methods
49class Heading(MarkdownElement):
50 """Markdown heading block element.
51 """
53 def __init__(self, text: str, level: int, escape: bool = True) -> None:
54 """Initialize the heading element.
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
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.
70 Returns:
71 str: Markdown heading
72 """
73 result = ""
75 if 1 <= self._level:
76 text_raw = self._text
78 if self._escape is True:
79 text_raw = MarkdownText.escape(self._text)
81 result = f"{'#' * self._level} {text_raw}\n"
83 else:
84 log_error(f"Invalid heading level {self._level} for {self._text}.")
86 return result
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 """
94 def __init__(self, column_titles: List[str], rows: List[List[str]]) -> None:
95 """Initialize the table element.
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
104 def render(self) -> str:
105 # lobster-trace: SwRequirements.sw_req_markdown_table
106 """Render the table in HTML format.
108 Returns:
109 str: Markdown table
110 """
111 table = "<table>\n"
112 table += "<thead>\n"
113 table += "<tr>\n"
115 for column_title in self._column_titles:
116 table += f"<th>{column_title}</th>\n"
118 table += "</tr>\n"
119 table += "</thead>\n"
120 table += "<tbody>\n"
122 for row_values in self._rows:
123 table += "<tr>\n"
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"
135 table += "</tr>\n"
137 table += "</tbody>\n"
138 table += "</table>\n"
140 return table
142# pylint: disable-next=too-few-public-methods
143class BulletList(MarkdownElement):
144 """Unordered Markdown list block element.
145 """
147 def __init__(self, values: List[str], escape: bool = True) -> None:
148 """Initialize the list element.
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
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.
162 Returns:
163 str: Markdown list
164 """
165 list_str = ""
167 for value_raw in self._values:
168 value = value_raw
170 if self._escape is True: # Escape the value if necessary.
171 value = MarkdownText.escape(value)
173 list_str += f"- {value}\n"
175 return list_str
177# pylint: disable-next=too-few-public-methods
178class Image(MarkdownElement):
179 """Markdown image / diagram link block element.
180 """
182 def __init__(self, file_name: str, caption: str, escape: bool = True) -> None:
183 """Initialize the image element.
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
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.
199 Returns:
200 str: Markdown diagram link
201 """
202 caption_raw = self._caption
204 if self._escape is True:
205 caption_raw = MarkdownText.escape(self._caption)
207 # Allowed are absolute and relative to source paths.
208 file_name = os.path.normpath(self._file_name)
210 return f"\n"
212# pylint: disable-next=too-few-public-methods
213class RawText(MarkdownElement):
214 """Raw Markdown text block element passed through unchanged.
215 """
217 def __init__(self, text: str) -> None:
218 """Initialize the raw text element.
220 Args:
221 text (str): Raw Markdown text
222 """
223 self._text = text
225 def render(self) -> str:
226 # lobster-trace: SwRequirements.sw_req_markdown_record
227 """Render the raw text.
229 Returns:
230 str: Raw Markdown text
231 """
232 return self._text
234# Functions ********************************************************************
236# Main *************************************************************************