Coverage for moptipy / utils / html.py: 79%
75 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""The HTML text format driver."""
3from io import TextIOBase
4from typing import Final
6from moptipy.utils.formatted_string import (
7 NAN,
8 NEGATIVE_INFINITY,
9 NUMBER,
10 POSITIVE_INFINITY,
11 SPECIAL,
12 TEXT,
13)
14from moptipy.utils.text_format import MODE_TABLE_HEADER, TextFormatDriver
16#: the default border style
17_BORDER: Final[str] = "1pt solid black"
19#: special characters in HTML
20SPECIAL_CHARS: Final[dict[str, str]] = {
21 "\u2205": "∅",
22 "\u221E": "∞",
23 "-\u221E": "-∞",
24 "inf": "∞",
25 "-inf": "-∞",
26 "nan": "∅",
27 "\u03b1": "α",
28 "\u2014": "—",
29}
32class HTML(TextFormatDriver):
33 """The HTML text driver."""
35 def begin_table_body(self, stream: TextIOBase, cols: str) -> None:
36 """Write the beginning of the table body."""
37 stream.write(
38 f'<table style="border:{_BORDER};border-collapse:collapse">')
40 def end_table_body(self, stream: TextIOBase, cols: str) -> None:
41 """Write the ending of the table body."""
42 stream.write("</tbody></table>")
44 def begin_table_header(self, stream: TextIOBase, cols: str) -> None:
45 """Begin the header of an HTML table."""
46 stream.write("<thead>")
48 def end_table_header(self, stream: TextIOBase, cols: str) -> None:
49 """End the header of an HTML table."""
50 stream.write("</thead><tbody>")
52 def begin_table_row(self, stream: TextIOBase, cols: str,
53 section_index: int, row_index: int,
54 row_mode: int) -> None:
55 """Begin a row in an HTML table."""
56 stream.write("<tr>")
58 def end_table_row(self, stream: TextIOBase, cols: str,
59 section_index: int, row_index: int) -> None:
60 """End a row in a HTML table."""
61 stream.write("</tr>")
63 def begin_table_cell(self, stream: TextIOBase, cols: str,
64 section_index: int, row_index: int,
65 col_index: int, cell_mode: int) -> None:
66 """Begin an HTML table cell."""
67 style: str = cols[col_index]
68 style = "center" if style == "c" \
69 else "left" if style == "l" else "right"
70 style = f"padding:3pt;text-align:{style}"
71 if col_index < (len(cols) - 1):
72 style = f"{style};border-right:{_BORDER}"
73 if row_index <= 0 <= section_index:
74 style = f"{style};border-top:{_BORDER}"
75 cell: str = "th" if (cell_mode == MODE_TABLE_HEADER) else "td"
76 stream.write(f'<{cell} style="{style}">') # noqa: B028
78 def end_table_cell(self, stream: TextIOBase, cols: str,
79 section_index: int, row_index: int,
80 col_index: int, cell_mode: int) -> None:
81 """End an HTML table cell."""
82 stream.write("</th>" if (cell_mode == MODE_TABLE_HEADER) else "</td>")
84 def text(self, stream: TextIOBase, text: str, bold: bool, italic: bool,
85 code: bool, mode: int) -> None:
86 """Print a text string."""
87 if len(text) <= 0:
88 return
89 styles: str = ""
90 if bold:
91 styles = "font-weight:bold;"
92 if italic:
93 styles += "font-style:italic;"
94 if code:
95 styles += "font-family:monospace;"
96 if len(styles) > 0:
97 stream.write(f'<span style="{styles[0:-1]}">') # noqa: B028
99 if mode == TEXT:
100 stream.write(text)
101 elif mode == NUMBER:
102 i: int = text.find("e")
103 if i < 0:
104 i = text.find("E")
105 if i > 0:
106 stream.write(f"{text[:i]}×10<sup>{text[i + 1:]}</sup>")
107 else:
108 stream.write(text)
109 elif mode == NAN:
110 stream.write("∅")
111 elif mode == POSITIVE_INFINITY:
112 stream.write("∞")
113 elif mode == NEGATIVE_INFINITY:
114 stream.write("-∞")
115 elif mode == SPECIAL:
116 s: Final[str] = str(text)
117 if s not in SPECIAL_CHARS:
118 raise ValueError(f"invalid special character: {s!r}")
119 stream.write(SPECIAL_CHARS[s])
120 else:
121 raise ValueError(f"invalid mode {mode} for text {text!r}.")
123 if len(styles) > 0:
124 stream.write("</span>")
126 def __str__(self):
127 """
128 Get the appropriate file suffix.
130 :returns: the file suffix
131 :retval 'html': always
132 """
133 return "html"
135 @staticmethod
136 def instance() -> "HTML":
137 """
138 Get the HTML format singleton instance.
140 :returns: the singleton instance of the HTML format
141 """
142 attr: Final[str] = "_instance"
143 func: Final = HTML.instance
144 if not hasattr(func, attr):
145 setattr(func, attr, HTML())
146 return getattr(func, attr)