Merge branch 'main' into refactor/chat-links
This commit is contained in:
@@ -2,11 +2,6 @@
|
|||||||
margin-bottom: 2ex;
|
margin-bottom: 2ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table_inner {
|
|
||||||
padding: 1ex;
|
|
||||||
background: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table tr {
|
.table tr {
|
||||||
border-bottom: 1px solid #aaa;
|
border-bottom: 1px solid #aaa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from .qso import Qso
|
|||||||
from .question import Question
|
from .question import Question
|
||||||
from .quote import Quote
|
from .quote import Quote
|
||||||
from .references import References
|
from .references import References
|
||||||
from .table import Table
|
from .table import Table, TableBody, TableCell, TableHeader, TableRow
|
||||||
from .tag import Tag
|
from .tag import Tag
|
||||||
from .underline import Underline
|
from .underline import Underline
|
||||||
from .unit import Unit
|
from .unit import Unit
|
||||||
@@ -46,6 +46,10 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
|||||||
Picture,
|
Picture,
|
||||||
Photo,
|
Photo,
|
||||||
Table,
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
TableBody,
|
||||||
Qso,
|
Qso,
|
||||||
Include,
|
Include,
|
||||||
*extras,
|
*extras,
|
||||||
@@ -104,7 +108,7 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
|||||||
for char in morse_code:
|
for char in morse_code:
|
||||||
result += '<span class="morse_char">\n'
|
result += '<span class="morse_char">\n'
|
||||||
for symbol in char:
|
for symbol in char:
|
||||||
result += '<span class="morse_char">\n'
|
result += '<span class="morse_symbol">\n'
|
||||||
if symbol == 1:
|
if symbol == 1:
|
||||||
result += "▄"
|
result += "▄"
|
||||||
elif symbol == 2:
|
elif symbol == 2:
|
||||||
@@ -217,28 +221,35 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
|||||||
self.photo_handler(token.id)
|
self.photo_handler(token.id)
|
||||||
return self.render_photo_helper(token.id, token.ref, token.text, token.number)
|
return self.render_photo_helper(token.id, token.ref, token.text, token.number)
|
||||||
|
|
||||||
@staticmethod
|
def render_table(self, token: Table):
|
||||||
def render_cell_helper(cell, alignment, type):
|
table = f'<table class="table table-hover">\n{self.render_inner(token)}'
|
||||||
style = ""
|
|
||||||
if alignment in table_alignment:
|
|
||||||
style = f' style="text-align: {table_alignment[alignment]};"'
|
|
||||||
return f"<{type}{style}>{cell}</{type}>\n"
|
|
||||||
|
|
||||||
def render_table(self, token):
|
|
||||||
alignment = token.alignment
|
|
||||||
table = "<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"<tr>\n{content}</tr>\n"
|
|
||||||
|
|
||||||
if token.caption != "":
|
if token.caption != "":
|
||||||
table += f"<caption>{token.caption}</caption>"
|
table += f"<caption>{token.caption}</caption>\n"
|
||||||
|
|
||||||
table += "</table>"
|
table += "</table>"
|
||||||
|
|
||||||
return table
|
return table
|
||||||
|
|
||||||
|
def render_table_row(self, token: TableRow):
|
||||||
|
return f"<tr>\n{self.render_inner(token)}</tr>\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)}</{type}>\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)}</{type}>\n"
|
||||||
|
|
||||||
def render_include(self, token):
|
def render_include(self, token):
|
||||||
return self.include_handler(token.ident)
|
return self.include_handler(token.ident)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from .photo import Photo
|
|||||||
from .picture import Picture
|
from .picture import Picture
|
||||||
from .question import Question
|
from .question import Question
|
||||||
from .quote import Quote
|
from .quote import Quote
|
||||||
from .table import Table
|
from .table import Table, TableBody, TableCell, TableHeader, TableRow
|
||||||
from .tag import Tag
|
from .tag import Tag
|
||||||
from .underline import Underline
|
from .underline import Underline
|
||||||
|
|
||||||
@@ -28,9 +28,17 @@ class FiftyOhmLaTeXRenderer(LaTeXRenderer):
|
|||||||
Picture,
|
Picture,
|
||||||
Photo,
|
Photo,
|
||||||
Table,
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableRow,
|
||||||
|
TableHeader,
|
||||||
|
TableCell,
|
||||||
)
|
)
|
||||||
self.question_renderer = question_renderer
|
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):
|
def render_document(self, token):
|
||||||
self.footnotes.update(token.footnotes)
|
self.footnotes.update(token.footnotes)
|
||||||
return self.render_inner(token)
|
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)
|
return self.render_photo_helper(token.id, token.ref, token.text, token.number)
|
||||||
|
|
||||||
def render_table(self, token):
|
def render_table(self, token):
|
||||||
alignments = token.alignment
|
table = "\\begin{DARCtabular}" + f"{self.render_inner(token)}" + "\\end{DARCtabular}"
|
||||||
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}"
|
|
||||||
if token.caption != "":
|
if token.caption != "":
|
||||||
table += f"\\captionof{{figure}}{{{token.caption}}}\n"
|
table += f"\\captionof{{figure}}{{{token.caption}}}\n"
|
||||||
table += f"\\label{{{token.name}}}\n"
|
table += f"\\label{{{token.name}}}\n"
|
||||||
|
|
||||||
return table
|
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)
|
||||||
|
|||||||
+84
-44
@@ -1,37 +1,99 @@
|
|||||||
import re
|
import re
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from mistletoe import span_token
|
from mistletoe import span_token
|
||||||
from mistletoe.block_token import BlockToken
|
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):
|
class Table(BlockToken):
|
||||||
|
name: str
|
||||||
|
caption: str
|
||||||
|
header: list[TableHeader]
|
||||||
|
rows: list[TableRow]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def start(line):
|
def start(line):
|
||||||
return "|" in 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
|
@classmethod
|
||||||
def read(cls, lines):
|
def read(cls, lines):
|
||||||
header = next(lines)
|
header = TableHeader(next(lines))
|
||||||
header, alignment = cls.parse_header(header)
|
alignment = header.alignment
|
||||||
|
|
||||||
name = ""
|
name = ""
|
||||||
caption = ""
|
caption = ""
|
||||||
rows = [TableRow(header)]
|
children = [TableBody([header], True)]
|
||||||
|
|
||||||
# Read table until the end: No more column definitions, caption, or empty line.
|
# Read table until the end: No more column definitions, caption, or empty line.
|
||||||
line_buffer = []
|
line_buffer = []
|
||||||
@@ -48,31 +110,9 @@ class Table(BlockToken):
|
|||||||
line_buffer.append(next(lines))
|
line_buffer.append(next(lines))
|
||||||
next_line = lines.peek()
|
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):
|
def __init__(self, match):
|
||||||
self.alignment, self.children, self.name, self.caption = match
|
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)
|
|
||||||
|
|||||||
+11
-11
@@ -8,17 +8,17 @@ from renderer.fifty_ohm_latex_renderer import FiftyOhmLaTeXRenderer
|
|||||||
@pytest.mark.html
|
@pytest.mark.html
|
||||||
def test_table_html():
|
def test_table_html():
|
||||||
assertions = {
|
assertions = {
|
||||||
"| l: a |": '<table>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n</table>\n',
|
"| l: a |": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n</thead>\n</table>\n', # noqa: E501
|
||||||
"| r: a |": '<table>\n<tr>\n<th style="text-align: right;">a</th>\n</tr>\n</table>\n',
|
"| r: a |": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: right;">a</th>\n</tr>\n</thead>\n</table>\n', # noqa: E501
|
||||||
"| c: a |": '<table>\n<tr>\n<th style="text-align: center;">a</th>\n</tr>\n</table>\n',
|
"| c: a |": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: center;">a</th>\n</tr>\n</thead>\n</table>\n', # noqa: E501
|
||||||
"| l: a |\n| b |": '<table>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n<tr>\n<td style="text-align: left;">b</td>\n</tr>\n</table>\n', # noqa: E501
|
"| l: a |\n| b |": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style="text-align: left;">b</td>\n</tr>\n</tbody>\n</table>\n', # noqa: E501
|
||||||
"| l: a |\n| b |\n| c |": '<table>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n<tr>\n<td style="text-align: left;">b</td>\n</tr>\n<tr>\n<td style="text-align: left;">c</td>\n</tr>\n</table>\n', # noqa: E501
|
"| l: a |\n| b |\n| c |": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style="text-align: left;">b</td>\n</tr>\n<tr>\n<td style="text-align: left;">c</td>\n</tr>\n</tbody>\n</table>\n', # noqa: E501
|
||||||
"| a |\n| b |\n| c |": "<table>\n<tr>\n<th>a</th>\n</tr>\n<tr>\n<td>b</td>\n</tr>\n<tr>\n<td>c</td>\n</tr>\n</table>\n", # noqa: E501
|
"| a |\n| b |\n| c |": '<table class="table table-hover">\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b</td>\n</tr>\n<tr>\n<td>c</td>\n</tr>\n</tbody>\n</table>\n', # noqa: E501
|
||||||
"| l: a |\n| *b* |": '<table>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n<tr>\n<td style="text-align: left;"><em>b</em></td>\n</tr>\n</table>\n', # noqa: E501
|
"| l: a |\n| *b* |": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style="text-align: left;"><em>b</em></td>\n</tr>\n</tbody>\n</table>\n', # noqa: E501
|
||||||
"| l: a |\n| b |\n[table:n_ab:A und B]": '<table>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n<tr>\n<td style="text-align: left;">b</td>\n</tr>\n<caption>A und B</caption></table>\n', # noqa: E501
|
"| l: a |\n| b |\n[table:n_ab:A und B]": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style="text-align: left;">b</td>\n</tr>\n</tbody>\n<caption>A und B</caption>\n</table>\n', # noqa: E501
|
||||||
"| l: *a* |": '<table>\n<tr>\n<th style="text-align: left;"><em>a</em></th>\n</tr>\n</table>\n',
|
"| l: *a* |": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: left;"><em>a</em></th>\n</tr>\n</thead>\n</table>\n', # noqa: E501
|
||||||
"| l: a |\n| [morse:a] |": '<table>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n<tr>\n<td style="text-align: left;"><span class="morse"><span class="morse_char">\n<span class="morse_char">\n▄</span>\n<span class="morse_char">\n▄▄▄</span>\n</span>\n</span></td>\n</tr>\n</table>\n', # noqa: E501
|
"| l: a |\n| [morse:a] |": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: left;">a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style="text-align: left;"><span class="morse"><span class="morse_char">\n<span class="morse_symbol">\n▄</span>\n<span class="morse_symbol">\n▄▄▄</span>\n</span>\n</span></td>\n</tr>\n</tbody>\n</table>\n', # noqa: E501
|
||||||
"| l: abc | r: abc | X: abc |\n| 123 | 456 | 789 | \n | 987 | 654 | 321 | ": '<table>\n<tr>\n<th style="text-align: left;">abc</th>\n<th style="text-align: right;">abc</th>\n<th>abc</th>\n</tr>\n<tr>\n<td style="text-align: left;">123</td>\n<td style="text-align: right;">456</td>\n<td>789</td>\n</tr>\n<tr>\n<td style="text-align: left;">987</td>\n<td style="text-align: right;">654</td>\n<td>321</td>\n</tr>\n</table>\n', # noqa: E501
|
"| l: abc | r: abc | X: abc |\n| 123 | 456 | 789 | \n | 987 | 654 | 321 | ": '<table class="table table-hover">\n<thead>\n<tr>\n<th style="text-align: left;">abc</th>\n<th style="text-align: right;">abc</th>\n<th>abc</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style="text-align: left;">123</td>\n<td style="text-align: right;">456</td>\n<td>789</td>\n</tr>\n<tr>\n<td style="text-align: left;">987</td>\n<td style="text-align: right;">654</td>\n<td>321</td>\n</tr>\n</tbody>\n</table>\n', # noqa: E501
|
||||||
}
|
}
|
||||||
|
|
||||||
with FiftyOhmHtmlRenderer() as renderer:
|
with FiftyOhmHtmlRenderer() as renderer:
|
||||||
|
|||||||
Reference in New Issue
Block a user