refactor: avoid parsing document twice for figure numbers
This commit is contained in:
@@ -2,6 +2,9 @@ import mistletoe
|
||||
|
||||
|
||||
class Document(mistletoe.Document):
|
||||
# margin_count = 0
|
||||
# margin_anchor_count = 0
|
||||
references = {}
|
||||
def __init__(self, *args, **kwargs):
|
||||
# references must exist before super().__init__ tokenizes the lines,
|
||||
# since ReferencedToken reads it from _root_node during tokenization.
|
||||
self.references: dict[str, str] = {}
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -8,16 +8,15 @@ from .dash import Dash
|
||||
from .document import Document
|
||||
from .formula import Formula
|
||||
from .halfwidth_spaces import HalfwidthSpaces
|
||||
from .image import Image
|
||||
from .include import Include
|
||||
from .index import Index
|
||||
from .morse import Morse
|
||||
from .nonbreaking_spaces import NonbreakingSpaces, NonbreakingSpacesDots
|
||||
from .photo import Photo
|
||||
from .picture import Picture
|
||||
from .qso import Qso
|
||||
from .question import Question
|
||||
from .quote import Quote
|
||||
from .references import References
|
||||
from .reference import Reference
|
||||
from .table import Table, TableBody, TableCell, TableHeader, TableRow
|
||||
from .tag import Tag
|
||||
from .underline import Underline
|
||||
@@ -30,14 +29,6 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
||||
margin_anchor_id = 0
|
||||
margin_id = 0
|
||||
|
||||
edition: str = None
|
||||
chapter: str = None
|
||||
section: str = None
|
||||
section_url: str = None
|
||||
|
||||
# TODO: Add type
|
||||
references = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*extras,
|
||||
@@ -62,10 +53,9 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
||||
HalfwidthSpaces,
|
||||
NonbreakingSpaces,
|
||||
NonbreakingSpacesDots,
|
||||
References,
|
||||
Reference,
|
||||
Question,
|
||||
Picture,
|
||||
Photo,
|
||||
Image,
|
||||
Table,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
@@ -90,23 +80,21 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
||||
self.section = section
|
||||
|
||||
# Set section URL if provided, otherwise use default
|
||||
if section_url is not None:
|
||||
self.section_url = section_url
|
||||
self.section_url = section_url if section_url is not None else "section.html"
|
||||
|
||||
# Figure map: stores ref -> hierarchical number mapping
|
||||
self.figure_map = {}
|
||||
|
||||
# Single unified counter for all figure types (pictures, photos, tables)
|
||||
self.figure_counter = 0
|
||||
self.references: dict[str, str] = {}
|
||||
|
||||
# Keep track of index ids already emitted in this document.
|
||||
self.index_anchor_ids = set()
|
||||
|
||||
def get_reference_label(self, marker: str):
|
||||
def _format_reference_label(self, marker: str):
|
||||
if marker in self.references:
|
||||
return f"{self.edition}-{self.chapter}.{self.section}.{self.references[marker]}", True
|
||||
label = self.references[marker]
|
||||
if self.edition and self.chapter and self.section:
|
||||
label = f"{self.edition}-{self.chapter}.{self.section}.{label}"
|
||||
return label
|
||||
else:
|
||||
return marker, False
|
||||
return None
|
||||
|
||||
def render_dash(self, token):
|
||||
return " – "
|
||||
@@ -222,47 +210,11 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
||||
lookup = {"": "", " ": " "}
|
||||
return f"{lookup[token.first]}{token.second}{lookup[token.third]}"
|
||||
|
||||
def collect_figures(self, document):
|
||||
"""First pass: collect all figures and assign hierarchical numbers."""
|
||||
self.figure_counter = 0
|
||||
self.figure_map = {}
|
||||
|
||||
self._collect_figures_recursive(document)
|
||||
|
||||
def _collect_figures_recursive(self, token):
|
||||
"""Recursively traverse the document tree to collect figures."""
|
||||
if hasattr(token, "children") and token.children is not None:
|
||||
for child in token.children:
|
||||
# Check if this is a Picture, Photo, or Table token
|
||||
if isinstance(child, Picture | Photo | Table):
|
||||
# Get the reference ID (ref for Picture/Photo, name for Table)
|
||||
ref_id = getattr(child, "ref", None) or getattr(child, "name", None)
|
||||
|
||||
if ref_id: # Only number figures with a reference ID
|
||||
self.figure_counter += 1
|
||||
hierarchical_num = self._format_figure_number(self.figure_counter)
|
||||
self.figure_map[ref_id] = hierarchical_num
|
||||
child.number = hierarchical_num
|
||||
else:
|
||||
child.number = ""
|
||||
|
||||
# Recurse into children
|
||||
self._collect_figures_recursive(child)
|
||||
|
||||
def _format_figure_number(self, counter):
|
||||
"""Format the hierarchical figure number based on context."""
|
||||
if self.edition and self.chapter and self.section:
|
||||
return f"{self.edition}-{self.chapter}.{self.section}.{counter}"
|
||||
else:
|
||||
# Fallback for tests or when context is not provided
|
||||
return str(counter)
|
||||
|
||||
def render_references(self, token):
|
||||
def render_reference(self, token: Reference):
|
||||
# Look up the figure number from the map
|
||||
figure_num = self.figure_map.get(token.first, "?")
|
||||
label = self._format_reference_label(token.marker) or "?"
|
||||
return (
|
||||
f'<a href="{self.section_url}#ref_{token.first}"'
|
||||
f" onclick=\"highlightRef('{token.first}');\">{figure_num}</a>"
|
||||
f'<a href="{self.section_url}#ref_{token.marker}" onclick="highlightRef(\'{token.marker}\');">{label}</a>'
|
||||
)
|
||||
|
||||
def render_question(self, token):
|
||||
@@ -282,31 +234,34 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
||||
return (
|
||||
f'<figure class="picture" id="ref_{marker}" name="{marker}">\n'
|
||||
f' <img src="pictures/{id}.svg" alt="{alt_text}">\n'
|
||||
f" <figcaption>Abbildung {self.get_reference_label(marker)}: {text}</figcaption>\n"
|
||||
f" <figcaption>Abbildung {label}: {text}</figcaption>\n"
|
||||
"</figure>\n"
|
||||
)
|
||||
|
||||
def render_picture(self, token):
|
||||
alt_text = ""
|
||||
if self.picture_handler is not None:
|
||||
alt_text = self.picture_handler(token.id)
|
||||
|
||||
return self.render_picture_helper(token.id, token.marker, token.text, token.label, alt_text)
|
||||
|
||||
@staticmethod
|
||||
def render_photo_helper(id, ref, text, number, alt_text):
|
||||
def render_photo_helper(id, ref, text, label, alt_text):
|
||||
return f"""
|
||||
<figure class="photo" id="ref_{ref}" name="{ref}">
|
||||
<img src="photos/{id}.png" alt="{alt_text}">
|
||||
<figcaption>Abbildung {number}: {text}</figcaption>
|
||||
<figcaption>Abbildung {label}: {text}</figcaption>
|
||||
</figure>
|
||||
"""
|
||||
|
||||
def render_photo(self, token):
|
||||
alt_text = ""
|
||||
if self.photo_handler is not None:
|
||||
alt_text = self.photo_handler(token.id) or alt_text
|
||||
return self.render_photo_helper(token.id, token.ref, token.text, token.number, alt_text)
|
||||
def render_image(self, token: Image):
|
||||
label = self._format_reference_label(token.marker) or "?"
|
||||
|
||||
# Only supported kinds are photo and picture
|
||||
if token.kind == "photo":
|
||||
alt_text = ""
|
||||
if self.photo_handler is not None:
|
||||
alt_text = self.photo_handler(token.id) or alt_text
|
||||
return self.render_photo_helper(token.id, token.marker, token.text, label, alt_text)
|
||||
if token.kind == "picture":
|
||||
alt_text = ""
|
||||
if self.picture_handler is not None:
|
||||
alt_text = self.picture_handler(token.id)
|
||||
return self.render_picture_helper(token.id, token.marker, token.text, label, alt_text)
|
||||
return ""
|
||||
|
||||
def render_table(self, token: Table):
|
||||
# Add id and name attributes if table has a name
|
||||
@@ -317,10 +272,11 @@ class FiftyOhmHtmlRenderer(HtmlRenderer):
|
||||
table = f'<table class="table table-hover"{table_attrs}>\n{self.render_inner(token)}'
|
||||
|
||||
if token.caption != "":
|
||||
# Include hierarchical number in caption if available
|
||||
# Include hierarchical number in caption if the table is named.
|
||||
caption_text = token.caption
|
||||
if hasattr(token, "number") and token.number:
|
||||
caption_text = f"Tabelle {token.number}: {token.caption}"
|
||||
label = self._format_reference_label(token.name)
|
||||
if label:
|
||||
caption_text = f"Tabelle {label}: {token.caption}"
|
||||
table += f"<caption>{caption_text}</caption>\n"
|
||||
|
||||
table += "</table>"
|
||||
|
||||
@@ -3,10 +3,9 @@ from mistletoe.latex_renderer import LaTeXRenderer
|
||||
from .comment import BlockComment
|
||||
from .dash import Dash
|
||||
from .halfwidth_spaces import HalfwidthSpaces
|
||||
from .image import Image
|
||||
from .index import Index
|
||||
from .nonbreaking_spaces import NonbreakingSpaces, NonbreakingSpacesDots
|
||||
from .photo import Photo
|
||||
from .picture import Picture
|
||||
from .question import Question
|
||||
from .quote import Quote
|
||||
from .table import Table, TableBody, TableCell, TableHeader, TableRow
|
||||
@@ -26,8 +25,7 @@ class FiftyOhmLaTeXRenderer(LaTeXRenderer):
|
||||
NonbreakingSpaces,
|
||||
NonbreakingSpacesDots,
|
||||
Question,
|
||||
Picture,
|
||||
Photo,
|
||||
Image,
|
||||
Table,
|
||||
TableBody,
|
||||
TableRow,
|
||||
@@ -115,17 +113,18 @@ class FiftyOhmLaTeXRenderer(LaTeXRenderer):
|
||||
\captionof{{figure}}{{{text}}}
|
||||
\label{{{ref}}}"""
|
||||
|
||||
def render_picture(self, token):
|
||||
return self.render_picture_helper(token.id, token.ref, token.text, token.number)
|
||||
|
||||
@staticmethod
|
||||
def render_photo_helper(id, ref, text, number):
|
||||
return rf"""\includegraphics[width=1.0\linewidth]{{photo/{id}}}
|
||||
\captionof{{figure}}{{{text}}}
|
||||
\label{{{ref}}}"""
|
||||
|
||||
def render_photo(self, token):
|
||||
return self.render_photo_helper(token.id, token.ref, token.text, token.number)
|
||||
def render_image(self, token: Image):
|
||||
if token.kind == "photo":
|
||||
return self.render_photo_helper(token.id, token.marker, token.text, token.label)
|
||||
if token.kind == "picture":
|
||||
return self.render_picture_helper(token.id, token.marker, token.text, token.label)
|
||||
return ""
|
||||
|
||||
def render_table(self, token):
|
||||
table = "\\begin{DARCtabular}" + f"{self.render_inner(token)}" + "\\end{DARCtabular}"
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import re
|
||||
|
||||
from mistletoe.block_tokenizer import FileWrapper
|
||||
|
||||
from renderer.referenced_token import ReferencedToken
|
||||
|
||||
|
||||
class Image(ReferencedToken):
|
||||
"""
|
||||
Block token for pictures and photos.
|
||||
Examples:
|
||||
[picture:727:amplitude_periode_halbwellen:Positive und negative Halbwellen einer Sinusschwingung]
|
||||
[photo:123:n_wasserfalldiagramm_trx_collage:Ansichten verschiedener Transceiver]
|
||||
"""
|
||||
|
||||
pattern = re.compile(r"^\[(?P<kind>picture|photo):(?P<id>\d+):(?P<marker>[^:\]]+):(?P<text>[^:\]]+)\]$")
|
||||
|
||||
@classmethod
|
||||
def start(cls, line: str):
|
||||
return cls.pattern.match(line.strip()) is not None
|
||||
|
||||
@classmethod
|
||||
def check_interrupts_paragraph(cls, lines: FileWrapper):
|
||||
line = lines.peek()
|
||||
if line is not None:
|
||||
return cls.start(line)
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def read(cls, lines: FileWrapper):
|
||||
match = cls.pattern.match(next(lines).strip())
|
||||
return match.group("kind"), match.group("id"), match.group("marker"), match.group("text")
|
||||
|
||||
def __init__(self, match):
|
||||
kind, id, marker, text = match
|
||||
super().__init__(marker)
|
||||
self.kind = kind
|
||||
self.id = id
|
||||
self.text = text
|
||||
@@ -1,19 +0,0 @@
|
||||
import re
|
||||
|
||||
from mistletoe.span_token import SpanToken
|
||||
|
||||
|
||||
class Photo(SpanToken):
|
||||
"""
|
||||
Span token for photos.
|
||||
Example: [photo:123:n_wasserfalldiagramm_trx_collage:Ansichten verschiedener Transceiver]
|
||||
"""
|
||||
|
||||
parse_inner = False
|
||||
pattern = re.compile(r"\[photo:(\d+):([^:\]]+):([^:\]]+)\]")
|
||||
|
||||
def __init__(self, match_object):
|
||||
self.id = match_object.group(1)
|
||||
self.ref = match_object.group(2)
|
||||
self.text = match_object.group(3)
|
||||
self.number = "TODO"
|
||||
@@ -1,33 +0,0 @@
|
||||
import re
|
||||
|
||||
from mistletoe.block_tokenizer import FileWrapper
|
||||
|
||||
from renderer.referenced_token import ReferencedToken
|
||||
|
||||
|
||||
class Picture(ReferencedToken):
|
||||
"""
|
||||
Span token for pictures.
|
||||
Example: [picture:727:amplitude_periode_halbwellen:Positive und negative Halbwellen einer Sinusschwingung]
|
||||
"""
|
||||
|
||||
pattern = re.compile(r"^\[picture:(?P<id>\d+):(?P<marker>[^:\]]+):(?P<text>[^:\]]+)\]$")
|
||||
|
||||
@classmethod
|
||||
def start(cls, line: str):
|
||||
return cls.pattern.match(line.strip()) is not None
|
||||
|
||||
@classmethod
|
||||
def check_interrupts_paragraph(cls, lines: FileWrapper):
|
||||
return cls.pattern.match(lines.peek())
|
||||
|
||||
@classmethod
|
||||
def read(cls, lines: FileWrapper):
|
||||
match = cls.pattern.match(next(lines))
|
||||
return match.group("id"), match.group("marker"), match.group("text")
|
||||
|
||||
def __init__(self, match):
|
||||
id, marker, text = match
|
||||
super().__init__(marker)
|
||||
self.id = id
|
||||
self.text = text
|
||||
@@ -3,7 +3,7 @@ import re
|
||||
from mistletoe.span_token import SpanToken
|
||||
|
||||
|
||||
class References(SpanToken):
|
||||
class Reference(SpanToken):
|
||||
"""
|
||||
Resolve references for html
|
||||
Identifies "[ref:$]"
|
||||
@@ -12,4 +12,4 @@ class References(SpanToken):
|
||||
pattern = re.compile(r"\[ref\:(\S+)\]")
|
||||
|
||||
def __init__(self, match_object):
|
||||
self.first = match_object.group(1)
|
||||
self.marker: str = match_object.group(1)
|
||||
@@ -3,15 +3,16 @@ from mistletoe.block_token import BlockToken
|
||||
|
||||
|
||||
class ReferencedToken(BlockToken):
|
||||
line_number: int = -1
|
||||
marker: str = None
|
||||
label: str = None
|
||||
|
||||
def __init__(self, marker: str):
|
||||
self.marker = marker
|
||||
|
||||
if marker not in mistletoe.token._root_node.references:
|
||||
self.label = len(mistletoe.token._root_node.references) + 1
|
||||
mistletoe.token._root_node.references[marker] = self.label
|
||||
if marker != "":
|
||||
self.marker = marker
|
||||
references = mistletoe.token._root_node.references
|
||||
if marker not in references:
|
||||
label = str(len(references) + 1)
|
||||
references[marker] = label
|
||||
else:
|
||||
label = references[marker]
|
||||
self.label = label
|
||||
else:
|
||||
self.label = mistletoe._root_node.references[marker]
|
||||
self.marker = None
|
||||
self.label = None
|
||||
|
||||
+8
-2
@@ -4,6 +4,8 @@ from enum import Enum
|
||||
from mistletoe import span_token
|
||||
from mistletoe.block_token import BlockToken
|
||||
|
||||
from renderer.referenced_token import ReferencedToken
|
||||
|
||||
|
||||
class CellAlignment(Enum):
|
||||
LEFT = "l"
|
||||
@@ -76,7 +78,7 @@ class TableBody(BlockToken):
|
||||
self.children, self.header = rows, header
|
||||
|
||||
|
||||
class Table(BlockToken):
|
||||
class Table(ReferencedToken):
|
||||
name: str
|
||||
caption: str
|
||||
header: list[TableHeader]
|
||||
@@ -115,4 +117,8 @@ class Table(BlockToken):
|
||||
return children, name, caption
|
||||
|
||||
def __init__(self, match):
|
||||
self.children, self.name, self.caption = match
|
||||
children, name, caption = match
|
||||
super().__init__(name)
|
||||
self.children = children
|
||||
self.name = name
|
||||
self.caption = caption
|
||||
|
||||
Reference in New Issue
Block a user