from panflute import Element, ListContainer, Inline, Block from panflute import Cite, Code, Emph, Image, LineBreak, Link, Math, Note, Quoted, RawInline, SmallCaps, SoftBreak, Space, Span, Str, Strikeout, Strong, Subscript, Superscript, Underline from panflute import BlockQuote, BulletList, Citation, CodeBlock, Definition, DefinitionItem, DefinitionList, Div, Figure, Header, HorizontalRule, LineBlock, LineItem, ListItem, MetaBlocks, MetaBool, MetaInlines, MetaList, MetaMap, MetaString, Null, OrderedList, Para, Plain, RawBlock, Table, TableBody, TableFoot, TableHead from panflute import TableRow, TableCell, Caption, Doc from panflute import MetaValue from panflute import stringify from typing import Union, Callable from .whitespace import NBSP from .elements import FQuoted from .context import Group, InlineGroup, BlockGroup, Context import sys class UnknownElementError(Exception): "An unknown Element has been passed to the OutputGenerator, probably because panflute introduced a new one." pass class FormatitkoRecursiveError(Exception): "A generic exception which wraps other exceptions and adds element-based traceback" elements: list[Union[Element, ListContainer, list[Union[Element, ListContainer]]]] context: Context def __init__(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]], context: Context, *args): self.elements = [e] self.context = context super().__init__(args) def add_element(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]): self.elements.append(e) def pretty_print(self): def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) def print_filename_recursive(context: Context): return context.path +\ ((" (included from " + print_filename_recursive(context.parent) + ")") if context.parent else "") eprint(f"Error occured in file {print_filename_recursive(self.context)} in ", end="") line = None for i in range(len(self.elements)-1, 0, -1): if hasattr(self.elements[i], "content") and len(self.elements[i].content) > 0 and isinstance(self.elements[i].content[0], Inline) and line is None: line = self.elements[i] eprint(type(self.elements[i]).__name__ + "[" + (str(self.elements[i-1].index) if isinstance(self.elements[i-1].index, int) else "") + "]", end=": ") if line: eprint() eprint('on line: "' + stringify(line).strip() + '"', end="") eprint() eprint("in element: " + str(self.elements[0]).replace("\n", "\\n")) sys.tracebacklimit = 0 raise self.__cause__ from None class OutputGenerator: _empty_lines: int context: Union[Context, None] indent_level: int indent_str: str output_file: ... TYPE_DICT_BLOCK: dict[type, Callable] TYPE_DICT_INLINE: dict[type, Callable] TYPE_DICT_MISC: dict[type, Callable] def __init__(self, output_file, indent_str: str="\t", initial_indent_level: int=0): self.output_file = output_file self.indent_str = indent_str self.indent_level = initial_indent_level self._empty_lines = 1 self.context = None self.TYPE_DICT_MISC = { TableRow: self.generate_TableRow, TableCell: self.generate_TableCell, Caption: self.generate_Caption, Doc: self.generate_Doc, LineItem: self.generate_LineItem, ListItem: self.generate_ListItem } self.TYPE_DICT_BLOCK = { BlockQuote: self.generate_BlockQuote, BulletList: self.generate_BulletList, Citation: self.generate_Citation, CodeBlock: self.generate_CodeBlock, Definition: self.generate_Definition, DefinitionItem: self.generate_DefinitionItem, DefinitionList: self.generate_DefinitionList, Div: self.generate_Div, Figure: self.generate_Figure, Header: self.generate_Header, HorizontalRule: self.generate_HorizontalRule, LineBlock: self.generate_LineBlock, Null: self.generate_Null, OrderedList: self.generate_OrderedList, Para: self.generate_Para, Plain: self.generate_Plain, RawBlock: self.generate_RawBlock, Table: self.generate_Table, TableBody: self.generate_TableBody, TableFoot: self.generate_TableFoot, TableHead: self.generate_TableHead, BlockGroup: self.generate_BlockGroup } self.TYPE_DICT_INLINE = { Cite: self.generate_Cite, Code: self.generate_Code, Emph: self.generate_Emph, Image: self.generate_Image, LineBreak: self.generate_LineBreak, Link: self.generate_Link, Math: self.generate_Math, Note: self.generate_Note, Quoted: self.generate_Quoted, RawInline: self.generate_RawInline, SmallCaps: self.generate_SmallCaps, SoftBreak: self.generate_SoftBreak, Space: self.generate_Space, Span: self.generate_Span, Str: self.generate_Str, Strikeout: self.generate_Strikeout, Strong: self.generate_Strong, Subscript: self.generate_Subscript, Superscript: self.generate_Superscript, Underline: self.generate_Underline, NBSP: self.generate_NBSP, FQuoted: self.generate_FQuoted, InlineGroup: self.generate_InlineGroup } self.TYPE_DICT_META = { MetaBlocks: self.generate_MetaBlocks, MetaBool: self.generate_MetaBool, MetaInlines: self.generate_MetaInlines, MetaMap: self.generate_MetaMap, MetaString: self.generate_MetaString, } def generate(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]): try: if isinstance(e, Group): old_context = self.context self.context = e.context if isinstance(e, list): self.generate_list(e) elif isinstance(e, ListContainer): self.generate_ListContainer(e) elif isinstance(e, Inline): self.generate_Inline(e) elif isinstance(e, Block): self.generate_Block(e) elif isinstance(e, MetaValue): self.generate_MetaValue(e) elif isinstance(e, MetaList): self.generate_MetaList(e) else: try: self.TYPE_DICT_MISC[type(e)](e) except KeyError as err: raise UnknownElementError(type(e)) from err if isinstance(e, Group): self.context = old_context except FormatitkoRecursiveError as err: if not isinstance(e, ListContainer): err.add_element(e) raise err except Exception as err: raise FormatitkoRecursiveError(e, self.context) from err def escape_special_chars(self, text: str) -> str: return text def indent(self) -> str: return self.indent_str*self.indent_level def indent_more(self): self.indent_level += 1 def indent_less(self): self.indent_level -= 1 def write(self, text: str=""): if self._empty_lines > 0: self.output_file.write(self.indent()) self.output_file.write(text) self._empty_lines = 0 def writeln(self, text: str=""): if self._empty_lines == 0: self.output_file.write("\n") self.output_file.write(self.indent()) self.output_file.write(text+"\n") self._empty_lines = 1 def writeraw(self, text: str=""): if self._empty_lines == 0: self.output_file.write("\n") self.output_file.write(text+"\n") self._empty_lines = 1 def ensure_empty(self, n: int=1): while self._empty_lines < n: self.output_file.write("\n") self._empty_lines+=1 def endln(self): self.ensure_empty(1) def start_tag(self, tag: str, attributes: dict[str,str]={}) -> str: return tag def end_tag(self, tag: str, attributes: dict[str,str]={}) -> str: return "/" + tag def single_tag(self, tag: str, attributes: dict[str,str]={}) -> str: return "/" + tag + "/" def tagname(self, e) -> str: return type(e).__name__ def common_attributes(self, e: Element) -> dict[str,str]: return {} def generate_simple_tag(self, e: Union[Element, None]=None, tag: str="", attributes: Union[dict[str,str],None]=None, content: Union[ListContainer, Element, list[Union[Element, ListContainer]], str, None]=None, inline: Union[bool, None]=None): if not tag and e: tag = self.tagname(e) if attributes is None: if e: attributes = self.common_attributes(e) else: attributes = {} if content is None and e and hasattr(e, "content"): content = e.content if content is None and e and hasattr(e, "text"): content = e.text if inline is None and e: inline = isinstance(e, Inline) if content is None: self.generate_empty_block_tag(tag, attributes) return if inline: if isinstance(content, str): self.generate_raw_inline_tag(tag, content, attributes) else: self.generate_simple_inline_tag(tag, content, attributes) else: if isinstance(content, str): self.generate_raw_block_tag(tag, content, attributes) else: self.generate_simple_block_tag(tag, content, attributes) def generate_simple_inline_tag(self, tag: str, content: Union[ListContainer, Element, list[Union[Element, ListContainer]]], attributes: dict[str,str]={}): self.write(self.start_tag(tag, attributes)) self.generate(content) self.write(self.end_tag(tag)) def generate_simple_block_tag(self, tag: str, content: Union[ListContainer, Element, list[Union[Element, ListContainer]]], attributes: dict[str,str]={}): self.writeln(self.start_tag(tag, attributes)) self.indent_more() self.generate(content) self.indent_less() self.writeln(self.end_tag(tag)) def generate_raw_inline_tag(self, tag: str, text: str, attributes: dict[str,str]={}): self.write(self.start_tag(tag, attributes)) self.write(text) self.write(self.end_tag(tag)) def generate_raw_block_tag(self, tag: str, text: str, attributes: dict[str,str]={}): self.writeln(self.start_tag(tag, attributes)) self.writeraw(text) self.writeln(self.end_tag(tag)) def generate_empty_block_tag(self, tag: str, attributes: dict[str,str]={}): self.writeln(self.single_tag(tag, attributes)) def generate_ListContainer(self, e: ListContainer): for child in e: self.generate(child) def generate_list(self, e: list): for el in e: self.generate(el) def generate_MetaList(self, e: MetaList): for child in e: self.generate(child) def generate_MetaValue(self, e: MetaValue): try: self.TYPE_DICT_META[type(e)](e) except KeyError: self.generate(e.content) def generate_MetaBlocks(self, e: MetaBlocks): self.generate(e.content) def generate_MetaInlines(self, e: MetaInlines): self.generate(e.content) def generate_MetaBool(self, e: MetaBool): self.generate_simple_tag(e) def generate_MetaMap(self, e: MetaMap): self.generate_simple_tag(e) def generate_MetaString(self, e: MetaString): self.generate_simple_tag(e) def generate_Inline(self, e: Inline): self.TYPE_DICT_INLINE[type(e)](e) def generate_Str(self, e: Str): self.write(self.escape_special_chars(e.text)) def generate_Space(self, e: Space): self.write(" ") def generate_NBSP(self, e: NBSP): self.write("~") def generate_SoftBreak(self, e: SoftBreak): self.endln() def generate_FQuoted(self, e: FQuoted): if e.style == "cs": if e.quote_type == "SingleQuote": self.write("‚") self.generate(e.content) self.write("‘") elif e.quote_type == "DoubleQuote": self.write("„") self.generate(e.content) self.write("“") elif e.style == "en": if e.quote_type == "SingleQuote": self.write("‘") self.generate(e.content) self.write("’") elif e.quote_type == "DoubleQuote": self.write("“") self.generate(e.content) self.write("”") else: if e.quote_type == "SingleQuote": self.write("'") self.generate(e.content) self.write("'") elif e.quote_type == "DoubleQuote": self.write("\"") self.generate(e.content) self.write("\"") else: self.write("\"") self.generate(e.content) self.write("\"") # Inline Elements def generate_Cite(self, e: Cite): self.generate_simple_tag(e) def generate_Emph(self, e: Emph): self.generate_simple_tag(e) def generate_Image(self, e: Image): self.generate_simple_tag(e) def generate_Link(self, e: Link): self.generate_simple_tag(e) def generate_Note(self, e: Note): self.generate_simple_tag(e) def generate_Quoted(self, e: Quoted): self.generate_simple_tag(e) def generate_SmallCaps(self, e: SmallCaps): self.generate_simple_tag(e) def generate_Span(self, e: Span): self.generate_simple_tag(e) def generate_Strikeout(self, e: Strikeout): self.generate_simple_tag(e) def generate_Strong(self, e: Strong): self.generate_simple_tag(e) def generate_Subscript(self, e: Subscript): self.generate_simple_tag(e) def generate_Superscript(self, e: Superscript): self.generate_simple_tag(e) def generate_Underline(self, e: Underline): self.generate_simple_tag(e) # Raw Inline elements def generate_Math(self, e: Math): self.generate_simple_tag(e) def generate_Code(self, e: Code): self.generate_simple_tag(e) def generate_RawInline(self, e: RawInline): self.generate_simple_tag(e) def generate_Block(self, e: Block): self.TYPE_DICT_BLOCK[type(e)](e) # Block elements def generate_BlockQuote(self, e: BlockQuote): self.generate_simple_tag(e) def generate_BulletList(self, e: BulletList): self.generate_simple_tag(e) def generate_Citation(self, e: Citation): self.generate_simple_tag(e) def generate_Definition(self, e: Definition): self.generate_simple_tag(e) def generate_DefinitionItem(self, e: DefinitionItem): self.generate_simple_tag(e) def generate_DefinitionList(self, e: DefinitionList): self.generate_simple_tag(e) def generate_Div(self, e: Div): self.generate_simple_tag(e) def generate_Header(self, e: Header): self.generate_simple_tag(e) def generate_LineBlock(self, e: LineBlock): self.generate_simple_tag(e) def generate_LineItem(self, e: LineItem): self.generate_simple_tag(e) def generate_ListItem(self, e: ListItem): self.generate_simple_tag(e) def generate_OrderedList(self, e: OrderedList): self.generate_simple_tag(e) def generate_Para(self, e: Para): self.generate_simple_tag(e) def generate_Plain(self, e: Plain): self.generate_simple_tag(e) def generate_Caption(self, e: Caption): self.generate_simple_tag(e) def generate_TableBody(self, e: TableBody): self.generate_simple_tag(e) def generate_TableCell(self, e: TableCell): self.generate_simple_tag(e) def generate_TableFoot(self, e: TableFoot): self.generate_simple_tag(e) def generate_TableHead(self, e: TableHead): self.generate_simple_tag(e) def generate_TableRow(self, e: TableRow): self.generate_simple_tag(e) def generate_Doc(self, e: Doc): if "header_content" in e.metadata: self.generate(e.metadata["header_content"]) self.generate_simple_tag(e) if "footer_content" in e.metadata: self.generate(e.metadata["footer_content"]) def generate_BlockGroup(self, e: BlockGroup): self.generate_simple_tag(e) def generate_InlineGroup(self, e: InlineGroup): self.generate_simple_tag(e) # Special elements with more contents def generate_Table(self, e: Table): self.generate_simple_tag(e, content=[e.head, e.content, e.foot]) def generate_Figure(self, e: Figure): self.generate_simple_tag(e, content=[e.content, e.caption]) # Emtpy tags def generate_Null(self, e: Null): self.generate_simple_tag(e) def generate_HorizontalRule(self, e: HorizontalRule): self.generate_simple_tag(e) def generate_LineBreak(self, e: LineBreak): self.generate_simple_tag(e) # Raw Block tags def generate_CodeBlock(self, e: CodeBlock): self.generate_simple_tag(e) def generate_RawBlock(self, e: RawBlock): self.generate_simple_tag(e) # Maybe move this to ImageProcessor? def get_image_processor_args(self, attributes:dict[str,str]) -> dict: # Attributes → image processor args additional_args = {} if "file-width" in attributes: additional_args["width"] = int(attributes["file-width"]) if "file-height" in attributes: additional_args["height"] = int(attributes["file-height"]) if "file-quality" in attributes: additional_args["quality"] = int(attributes["file-quality"]) if "file-dpi" in attributes: additional_args["dpi"] = int(attributes["file-dpi"]) if "file-deps" in attributes: additional_args["deps"] = attributes["file-deps"].split(",") return additional_args