# -*- coding: utf-8 -*- import base64 import re from pathlib import Path from typing import Callable, Dict, List, Union import wireviz # for doing wireviz.__file__ from wireviz import APP_NAME, APP_URL, __version__ from wireviz.wv_dataclasses import Metadata, Options from wireviz.wv_utils import ( file_read_text, file_write_text, html_line_breaks, smart_file_resolve, ) mime_subtype_replacements = {"jpg": "jpeg", "tif": "tiff"} # TODO: Share cache and code between data_URI_base64() and embed_svg_images() def data_URI_base64(file: Union[str, Path], media: str = "image") -> str: """Return Base64-encoded data URI of input file.""" file = Path(file) b64 = base64.b64encode(file.read_bytes()).decode("utf-8") uri = f"data:{media}/{get_mime_subtype(file)};base64, {b64}" # print(f"data_URI_base64('{file}', '{media}') -> {len(uri)}-character URI") if len(uri) > 65535: print( "data_URI_base64(): Warning: Browsers might have different URI length limitations" ) return uri def embed_svg_images(svg_in: str, base_path: Union[str, Path] = Path.cwd()) -> str: images_b64 = {} # cache of base64-encoded images def image_tag(pre: str, url: str, post: str) -> str: return f'' def replace(match: re.Match) -> str: imgurl = match["URL"] if not imgurl in images_b64: # only encode/cache every unique URL once imgurl_abs = (Path(base_path) / imgurl).resolve() image = imgurl_abs.read_bytes() images_b64[imgurl] = base64.b64encode(image).decode("utf-8") return image_tag( match["PRE"] or "", f"data:image/{get_mime_subtype(imgurl)};base64, {images_b64[imgurl]}", match["POST"] or "", ) pattern = re.compile( image_tag(r"(?P
 [^>]*?)?", r'(?P[^"]*?)', r"(?P [^>]*?)?"),
        re.IGNORECASE,
    )
    return pattern.sub(replace, svg_in)


def get_mime_subtype(filename: Union[str, Path]) -> str:
    mime_subtype = Path(filename).suffix.lstrip(".").lower()
    if mime_subtype in mime_subtype_replacements:
        mime_subtype = mime_subtype_replacements[mime_subtype]
    return mime_subtype


def _get_latest_revision(metadata: Dict) -> str:
    if not "revisions" in metadata:
        return ""
    revision = list(metadata.get("revisions"))[-1]
    return revision


def embed_svg_images_file(
    filename_in: Union[str, Path], overwrite: bool = True
) -> None:
    filename_in = Path(filename_in).resolve()
    filename_out = filename_in.with_suffix(".b64.svg")
    filename_out.write_text(  # TODO?: Verify xml encoding="utf-8" in SVG?
        embed_svg_images(filename_in.read_text(), filename_in.parent)
    )  # TODO: Use encoding="utf-8" in both read_text() and write_text()
    if overwrite:
        filename_out.replace(filename_in)


def generate_html_output(
    filename: Union[str, Path],
    bom: List[List[str]],
    metadata: Metadata,
    options: Options,
):
    # load HTML template
    templatename = metadata.get("template", {}).get("name")
    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",
            [Path(filename).parent, Path(__file__).parent / "templates"],
        )
    else:
        # fall back to built-in simple template if no template was provided
        templatefile = Path(wireviz.__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
    # 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 if item is not None else ""}\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, "": options.bgcolor.html, "": 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", "" ), "": _get_latest_revision(metadata), } 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)