# -*- coding: utf-8 -*- import re from pathlib import Path from typing import Callable, Dict, List, Union from wireviz import APP_NAME, APP_URL, __version__, wv_colors from wireviz.DataClasses import Metadata, Options from wireviz.svgembed import data_URI_base64 from wireviz.wv_gv_html import html_line_breaks from wireviz.wv_helper import ( file_read_text, file_write_text, flatten2d, smart_file_resolve, ) def generate_html_output( filename: Union[str, Path], bom_list: List[List[str]], metadata: Metadata, options: Options, source: Union[str, Path] = None, ): # load HTML template templatename = metadata.get("template", {}).get("name") template_search_paths = [ Path(filename).parent, Path(__file__).parent / "templates"] if source is not None: template_search_paths.insert(0, Path(source).parent) if templatename: # if relative path to template was provided, check directory of YAML file first, fall back to built-in template directory templatefile = smart_file_resolve( f"{templatename}.html", template_search_paths ) else: # fall back to built-in simple template if no template was provided templatefile = Path(__file__).parent / "templates/simple.html" html = file_read_text(templatefile) # TODO?: Warn if unexpected meta charset? # embed SVG diagram (only if used) def svgdata() -> str: return re.sub( # TODO?: Verify xml encoding="utf-8" in SVG? "^<[?]xml [^?>]*[?]>[^<]*]*>", "", file_read_text(f"{filename}.tmp.svg"), 1, ) # generate BOM table bom = flatten2d(bom_list) # generate BOM header (may be at the top or bottom of the table) bom_header_html = " \n" for item in bom[0]: th_class = f"bom_col_{item.lower()}" bom_header_html = f'{bom_header_html} {item}\n' bom_header_html = f"{bom_header_html} \n" # generate BOM contents bom_contents = [] for row in bom[1:]: row_html = " \n" for i, item in enumerate(row): td_class = f"bom_col_{bom[0][i].lower()}" row_html = f'{row_html} {item}\n' row_html = f"{row_html} \n" bom_contents.append(row_html) bom_html = ( '\n' + bom_header_html + "".join(bom_contents) + "
\n" ) bom_html_reversed = ( '\n' + "".join(list(reversed(bom_contents))) + bom_header_html + "
\n" ) # prepare simple replacements replacements = { "": f"{APP_NAME} {__version__} - {APP_URL}", "": options.fontname, "": wv_colors.translate_color(options.bgcolor, "hex"), "": str(filename), "": Path(filename).stem, "": bom_html, "": bom_html_reversed, "": "1", # TODO: handle multi-page documents "": "1", # TODO: handle multi-page documents "": metadata.get("template", {}).get( "sheetsize", "" ), } def replacement_if_used(key: str, func: Callable[[], str]) -> None: """Append replacement only if used in html.""" if key in html: replacements[key] = func() replacement_if_used("", svgdata) replacement_if_used( "", lambda: data_URI_base64(f"{filename}.png") ) # prepare metadata replacements if metadata: for item, contents in metadata.items(): if isinstance(contents, (str, int, float)): replacements[f""] = html_line_breaks(str(contents)) elif isinstance(contents, Dict): # useful for authors, revisions for index, (category, entry) in enumerate(contents.items()): if isinstance(entry, Dict): replacements[f""] = str(category) for entry_key, entry_value in entry.items(): replacements[ f"" ] = html_line_breaks(str(entry_value)) elif isinstance(entry, (str, int, float)): pass # TODO?: replacements[f""] = html_line_breaks(str(entry)) # perform replacements # regex replacement adapted from: # https://gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729 # longer replacements first, just in case replacements_sorted = sorted(replacements, key=len, reverse=True) replacements_escaped = map(re.escape, replacements_sorted) pattern = re.compile("|".join(replacements_escaped)) html = pattern.sub(lambda match: replacements[match.group(0)], html) file_write_text(f"{filename}.html", html)