Coverage for src/pyTRLCConverter/marko/md2reqif_renderer.py: 100%
30 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"""ReqIF Renderer for Marko.
2 It is used to convert CommonMark AST to ReqIF-compatible XHTML.
4 Author: Andreas Merkle (andreas.merkle@newtec.de)
5"""
7# pyTRLCConverter - A tool to convert TRLC files to specific formats.
8# Copyright (c) 2024 - 2026 NewTec GmbH
9#
10# This file is part of pyTRLCConverter program.
11#
12# The pyTRLCConverter program is free software: you can redistribute it and/or modify it under
13# the terms of the GNU General Public License as published by the Free Software Foundation,
14# either version 3 of the License, or (at your option) any later version.
15#
16# The pyTRLCConverter program is distributed in the hope that it will be useful, but
17# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License along with pyTRLCConverter.
21# If not, see <https://www.gnu.org/licenses/>.
23# Imports **********************************************************************
25from __future__ import annotations
26import hashlib
27import html
28import os
29from typing import TYPE_CHECKING, Optional
30from marko.html_renderer import HTMLRenderer
31from pyTRLCConverter.plantuml import PlantUML
33if TYPE_CHECKING:
34 from marko import block
36# Variables ********************************************************************
38# Classes **********************************************************************
41class Md2ReqifRenderer(HTMLRenderer):
42 # lobster-trace: SwRequirements.sw_req_reqif_render_md
43 # lobster-trace: SwRequirements.sw_req_reqif_render_plantuml
44 # lobster-trace: SwRequirements.sw_req_plantuml
45 """Renderer for ReqIF XHTML output.
47 Extends marko's CommonMark HTML renderer so that fenced code blocks tagged
48 ``plantuml`` are rendered as embedded SVG images referenced via XHTML
49 ``<object>`` elements. Other fenced code blocks are rendered by the parent
50 class as ``<pre><code>`` blocks.
51 """
53 # Destination directory where generated SVG images are written. The
54 # converter is responsible for setting this before each ``convert()`` call
55 # and for ensuring the directory exists and survives until the external
56 # files have been copied to the final output location.
57 image_dir: Optional[str] = None
59 # List of ``(source_path, local_name)`` tuples collected during rendering.
60 # The converter merges these into its own ``_external_files`` list so the
61 # images are copied alongside the ReqIF document.
62 external_files: Optional[list] = None
64 def render_fenced_code(self, element: "block.FencedCode") -> str:
65 # lobster-trace: SwRequirements.sw_req_reqif_render_md
66 # lobster-trace: SwRequirements.sw_req_plantuml
67 """Render a fenced code block as ReqIF XHTML.
69 If the language tag is ``plantuml``, the diagram source is rendered as
70 an embedded SVG image referenced via an XHTML ``<object>`` element.
71 If PlantUML is not available, an error string is emitted instead.
72 For all other language tags the block is rendered by the parent class.
74 Args:
75 element (block.FencedCode): The fenced code block element to render.
77 Returns:
78 str: XHTML representation of the fenced code block.
79 """
80 if element.lang == "plantuml":
81 return self._render_plantuml(element.children[0].children)
83 return super().render_fenced_code(element)
85 def _render_plantuml(self, diagram_source: str) -> str:
86 # lobster-trace: SwRequirements.sw_req_reqif_render_md
87 # lobster-trace: SwRequirements.sw_req_plantuml
88 """Render a PlantUML diagram as an embedded SVG reference.
90 Generates SVG bytes via the PlantUML tool, writes them to a temporary
91 file under :attr:`image_dir`, registers the file in
92 :attr:`external_files` so the converter can copy it next to the ReqIF
93 document, and returns an XHTML ``<object>`` element referencing the
94 image by its local name.
96 On failure (PlantUML not available, server error, ...) an error
97 paragraph is emitted instead.
99 Args:
100 diagram_source (str): The PlantUML diagram source text.
102 Returns:
103 str: XHTML fragment referencing the generated image, or an error
104 paragraph if image generation failed.
105 """
106 assert Md2ReqifRenderer.image_dir is not None
107 assert Md2ReqifRenderer.external_files is not None
109 try:
110 plantuml = PlantUML()
111 svg_bytes = plantuml.generate_to_bytes("svg", diagram_source)
113 # Derive a stable, content-addressed file name from the diagram
114 # source so identical diagrams share a single SVG and different
115 # diagrams never collide when several documents are written to the
116 # same output directory (multi-document mode).
117 digest = hashlib.sha1(diagram_source.encode("utf-8")).hexdigest()[:12]
118 local_name = f"plantuml_{digest}.svg"
119 svg_path = os.path.join(Md2ReqifRenderer.image_dir, local_name)
120 with open(svg_path, "wb") as svg_file:
121 svg_file.write(svg_bytes)
123 Md2ReqifRenderer.external_files.append((svg_path, local_name))
125 result = f'<p><object type="image/svg+xml" data="{html.escape(local_name)}"></object></p>\n'
126 except (FileNotFoundError, OSError) as exc:
127 result = f"<p>[PlantUML error: {html.escape(str(exc))}]</p>\n"
129 return result
131# Functions ********************************************************************
133# Main *************************************************************************