diff --git a/assets/style-course.css b/assets/style-course.css index ff5703b..d74c881 100644 --- a/assets/style-course.css +++ b/assets/style-course.css @@ -2,11 +2,6 @@ margin-bottom: 2ex; } -.table_inner { - padding: 1ex; - background: #ddd; -} - .table tr { border-bottom: 1px solid #aaa; } diff --git a/renderer/fifty_ohm_html_renderer.py b/renderer/fifty_ohm_html_renderer.py index 9e00d84..8cb9e66 100644 --- a/renderer/fifty_ohm_html_renderer.py +++ b/renderer/fifty_ohm_html_renderer.py @@ -13,7 +13,7 @@ from .qso import Qso from .question import Question from .quote import Quote from .references import References -from .table import Table +from .table import Table, TableBody, TableCell, TableHeader, TableRow from .tag import Tag from .underline import Underline from .unit import Unit @@ -46,6 +46,10 @@ class FiftyOhmHtmlRenderer(HtmlRenderer): Picture, Photo, Table, + TableHeader, + TableRow, + TableCell, + TableBody, Qso, Include, *extras, @@ -104,7 +108,7 @@ class FiftyOhmHtmlRenderer(HtmlRenderer): for char in morse_code: result += '\n' for symbol in char: - result += '\n' + result += '\n' if symbol == 1: result += "▄" elif symbol == 2: @@ -217,28 +221,35 @@ class FiftyOhmHtmlRenderer(HtmlRenderer): self.photo_handler(token.id) return self.render_photo_helper(token.id, token.ref, token.text, token.number) - @staticmethod - def render_cell_helper(cell, alignment, type): - style = "" - if alignment in table_alignment: - style = f' style="text-align: {table_alignment[alignment]};"' - return f"<{type}{style}>{cell}\n" - - def render_table(self, token): - alignment = token.alignment - table = "\n" - - for i, row in enumerate(token.children): - content = "" - for j, cell in enumerate(row.children): - content += self.render_cell_helper(self.render_inner(cell), alignment[j], ("th" if i == 0 else "td")) - table += f"\n{content}\n" + def render_table(self, token: Table): + table = f'
\n{self.render_inner(token)}' if token.caption != "": - table += f"" + table += f"\n" + table += "
{token.caption}{token.caption}
" return table + def render_table_row(self, token: TableRow): + return f"\n{self.render_inner(token)}\n" + + def render_table_header(self, token: TableHeader): + return self.render_table_row(token) + + def render_table_cell(self, token: TableCell): + type = "th" if token.header else "td" + style = "" + if token.alignment in table_alignment: + style = f' style="text-align: {table_alignment[token.alignment]};"' + return f"<{type}{style}>{self.render_inner(token)}\n" + + def render_table_body(self, token: TableBody): + if token.children is None or len(token.children) == 0: + return None + else: + type = "thead" if token.header else "tbody" + return f"<{type}>\n{self.render_inner(token)}\n" + def render_include(self, token): return self.include_handler(token.ident) diff --git a/renderer/fifty_ohm_latex_renderer.py b/renderer/fifty_ohm_latex_renderer.py index 21a9883..508983e 100644 --- a/renderer/fifty_ohm_latex_renderer.py +++ b/renderer/fifty_ohm_latex_renderer.py @@ -8,7 +8,7 @@ from .photo import Photo from .picture import Picture from .question import Question from .quote import Quote -from .table import Table +from .table import Table, TableBody, TableCell, TableHeader, TableRow from .tag import Tag from .underline import Underline @@ -28,9 +28,17 @@ class FiftyOhmLaTeXRenderer(LaTeXRenderer): Picture, Photo, Table, + TableBody, + TableRow, + TableHeader, + TableCell, ) self.question_renderer = question_renderer + def render_inner(self, token, base="") -> str: + # Filter out None values, so block tokens can return None to not be rendered. + return base.join(filter(lambda x: x is not None, [self.render(child) for child in token.children])) + def render_document(self, token): self.footnotes.update(token.footnotes) return self.render_inner(token) @@ -118,23 +126,25 @@ class FiftyOhmLaTeXRenderer(LaTeXRenderer): return self.render_photo_helper(token.id, token.ref, token.text, token.number) def render_table(self, token): - alignments = token.alignment - align = "" - for alignment in alignments: - align += alignment - - table = f"\\begin{{DARCtabular}}{{{align}}}\n" - - for row in token.children: - for j, cell in enumerate(row.children): - table += self.render_inner(cell) - if j < len(row.children) - 1: - table += " & " - table += r"\\" + "\n" - - table += "\\end{DARCtabular}" + table = "\\begin{DARCtabular}" + f"{self.render_inner(token)}" + "\\end{DARCtabular}" if token.caption != "": table += f"\\captionof{{figure}}{{{token.caption}}}\n" table += f"\\label{{{token.name}}}\n" return table + + def render_table_body(self, token: TableBody): + return self.render_inner(token) + + def render_table_row(self, token: TableRow): + return rf"{self.render_inner(token, ' & ')}\\" + "\n" + + def render_table_header(self, token: TableHeader): + align = "" + for alignment in token.alignment: + align += alignment + + return f"{{{align}}}\n{self.render_table_row(token)}" + + def render_table_cell(self, token: TableCell): + return self.render_inner(token) diff --git a/renderer/table.py b/renderer/table.py index 93b1c23..21966f3 100644 --- a/renderer/table.py +++ b/renderer/table.py @@ -1,37 +1,99 @@ import re +from enum import Enum from mistletoe import span_token from mistletoe.block_token import BlockToken +class CellAlignment(Enum): + LEFT = "l" + CENTER = "c" + RIGHT = "r" + EXPAND = "X" + + +class TableCell(BlockToken): + alignment: CellAlignment + header: bool + + @staticmethod + def start(_): + return False + + def __init__(self, match, alignment: CellAlignment, header=False): + super().__init__(match, span_token.tokenize_inner) + self.alignment, self.header = alignment, header + + +class TableRow(BlockToken): + @staticmethod + def start(_): + return False + + @classmethod + def parse(cls, line) -> list[str]: + line = line.strip().strip("|") + return [col.strip() for col in line.split("|")] + + def __init__(self, match, alignment: list[CellAlignment]): + self.children = [TableCell(cell, alignment[i]) for i, cell in enumerate(self.parse(match))] + + +class TableHeader(TableRow): + alignment: list[CellAlignment] + + @staticmethod + def start(_): + return False + + @classmethod + def parse(cls, line) -> list[str]: + alignment: list[CellAlignment] = [] + columns: list[str] = [] + for column in super().parse(line): + attr: CellAlignment + value: str + attr, value = re.match(r"^ ?(?:([lcrX]):)? ?(.*)", column).group(1, 2) + + alignment.append(attr) + columns.append(value) + + return columns, alignment + + def __init__(self, match): + columns, self.alignment = self.parse(match) + self.children = [TableCell(cell, self.alignment[i], True) for i, cell in enumerate(columns)] + + +class TableBody(BlockToken): + header: bool + + @staticmethod + def start(_): + return False + + def __init__(self, rows: TableRow, header=False): + self.children, self.header = rows, header + + class Table(BlockToken): + name: str + caption: str + header: list[TableHeader] + rows: list[TableRow] + @staticmethod def start(line): return "|" in line - @classmethod - def parse_header(cls, line): - # Parse header, extract alignment and make an ordinary row out of it - line = line.strip().strip("|") # Remove leading/trailing whitespace and pipes - columns = [col.strip() for col in line.split("|")] # Split by pipe | - - alignment = [] - header = "| " - - for column in columns: - attr, val = re.match(r"^ ?(?:([lcrX]):)?(.*)", column).group(1, 2) - alignment.append(attr) - header += val + " | " - - return header, alignment - @classmethod def read(cls, lines): - header = next(lines) - header, alignment = cls.parse_header(header) + header = TableHeader(next(lines)) + alignment = header.alignment + name = "" caption = "" - rows = [TableRow(header)] + children = [TableBody([header], True)] # Read table until the end: No more column definitions, caption, or empty line. line_buffer = [] @@ -48,31 +110,9 @@ class Table(BlockToken): line_buffer.append(next(lines)) next_line = lines.peek() - rows.extend([TableRow(line) for line in line_buffer]) + children.append(TableBody([TableRow(line, alignment) for line in line_buffer])) - return alignment, rows, name, caption + return children, name, caption def __init__(self, match): - self.alignment, self.children, self.name, self.caption = match - - -class TableRow(BlockToken): - @classmethod - def parse_row(cls, line): - line = line.strip().strip("|") - columns = [col.strip() for col in line.split("|")] - - data = [] - - for column in columns: - data.append(TableCell(column)) - - return data - - def __init__(self, match): - self.children = self.parse_row(match) - - -class TableCell(BlockToken): - def __init__(self, match): - super().__init__(match, span_token.tokenize_inner) + self.children, self.name, self.caption = match diff --git a/test/test_table.py b/test/test_table.py index deaba13..1f37ffe 100644 --- a/test/test_table.py +++ b/test/test_table.py @@ -8,17 +8,17 @@ from renderer.fifty_ohm_latex_renderer import FiftyOhmLaTeXRenderer @pytest.mark.html def test_table_html(): assertions = { - "| l: a |": '\n\n\n\n
a
\n', - "| r: a |": '\n\n\n\n
a
\n', - "| c: a |": '\n\n\n\n
a
\n', - "| l: a |\n| b |": '\n\n\n\n\n\n\n
a
b
\n', # noqa: E501 - "| l: a |\n| b |\n| c |": '\n\n\n\n\n\n\n\n\n\n
a
b
c
\n', # noqa: E501 - "| a |\n| b |\n| c |": "\n\n\n\n\n\n\n\n\n\n
a
b
c
\n", # noqa: E501 - "| l: a |\n| *b* |": '\n\n\n\n\n\n\n
a
b
\n', # noqa: E501 - "| l: a |\n| b |\n[table:n_ab:A und B]": '\n\n\n\n\n\n\n
a
b
A und B
\n', # noqa: E501 - "| l: *a* |": '\n\n\n\n
a
\n', - "| l: a |\n| [morse:a] |": '\n\n\n\n\n\n\n
a
\n\n▄\n\n▄▄▄\n\n
\n', # noqa: E501 - "| l: abc | r: abc | X: abc |\n| 123 | 456 | 789 | \n | 987 | 654 | 321 | ": '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
abcabcabc
123456789
987654321
\n', # noqa: E501 + "| l: a |": '\n\n\n\n\n\n
a
\n', # noqa: E501 + "| r: a |": '\n\n\n\n\n\n
a
\n', # noqa: E501 + "| c: a |": '\n\n\n\n\n\n
a
\n', # noqa: E501 + "| l: a |\n| b |": '\n\n\n\n\n\n\n\n\n\n\n
a
b
\n', # noqa: E501 + "| l: a |\n| b |\n| c |": '\n\n\n\n\n\n\n\n\n\n\n\n\n\n
a
b
c
\n', # noqa: E501 + "| a |\n| b |\n| c |": '\n\n\n\n\n\n\n\n\n\n\n\n\n\n
a
b
c
\n', # noqa: E501 + "| l: a |\n| *b* |": '\n\n\n\n\n\n\n\n\n\n\n
a
b
\n', # noqa: E501 + "| l: a |\n| b |\n[table:n_ab:A und B]": '\n\n\n\n\n\n\n\n\n\n\n\n
a
b
A und B
\n', # noqa: E501 + "| l: *a* |": '\n\n\n\n\n\n
a
\n', # noqa: E501 + "| l: a |\n| [morse:a] |": '\n\n\n\n\n\n\n\n\n\n\n
a
\n\n▄\n\n▄▄▄\n\n
\n', # noqa: E501 + "| l: abc | r: abc | X: abc |\n| 123 | 456 | 789 | \n | 987 | 654 | 321 | ": '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
abcabcabc
123456789
987654321
\n', # noqa: E501 } with FiftyOhmHtmlRenderer() as renderer: