WireViz/src/wireviz/wv_gv_html.py
2021-10-17 16:13:45 +02:00

223 lines
7.5 KiB
Python

# -*- coding: utf-8 -*-
import re
from itertools import zip_longest
from typing import List, Optional, Union
from wireviz.DataClasses import Color, Connector, Options
from wireviz.wv_colors import translate_color
from wireviz.wv_helper import pn_info_string, remove_links
from wireviz.wv_table_util import * # TODO: explicitly import each needed tag later
HEADER_PN = "P/N"
HEADER_MPN = "MPN"
HEADER_SPN = "SPN"
# TODO: remove harness argument; only used by get_additional_component_table()
def gv_node_connector(connector: Connector, harness_options: Options) -> str:
# If no wires connected (except maybe loop wires)?
if not (connector.ports_left or connector.ports_right):
connector.ports_left = True # Use left side pins by default
# generate all rows to be shown in the node
if connector.show_name:
row_name = [
f"{html_bgcolor(connector.bgcolor_title)}{remove_links(connector.name)}"
]
else:
row_name = []
row_pn = [
pn_info_string(HEADER_PN, None, connector.pn),
pn_info_string(HEADER_MPN, connector.manufacturer, connector.mpn),
pn_info_string(HEADER_SPN, connector.supplier, connector.spn),
]
row_pn = [html_line_breaks(cell) for cell in row_pn]
row_info = [
html_line_breaks(connector.type),
html_line_breaks(connector.subtype),
f"{connector.pincount}-pin" if connector.show_pincount else None,
translate_color(connector.color, harness_options.color_mode),
html_colorbar(connector.color),
]
row_image = [html_image(connector.image)]
row_image_caption = [html_caption(connector.image)]
row_notes = [html_line_breaks(connector.notes)]
# row_additional_component_table = get_additional_component_table(self, connector)
row_additional_component_table = None
if connector.style != "simple":
pin_tuples = zip_longest(
connector.pins,
connector.pinlabels,
connector.pincolors,
)
pin_rows = []
for pinindex, (pinname, pinlabel, pincolor) in enumerate(pin_tuples):
if connector.should_show_pin(pinname):
pin_rows.append(
gv_pin_row(pinindex, pinname, pinlabel, pincolor, connector)
)
table_attribs = Attribs(
{"border": 0, "cellspacing": 0, "cellpadding": 3, "cellborder": 1}
)
row_connector_table = str(Table(pin_rows, attribs=table_attribs))
else:
row_connector_table = None
rows = [
row_name,
row_pn,
row_info,
row_connector_table,
row_image,
row_image_caption,
row_additional_component_table,
row_notes,
]
html = "\n".join(nested_html_table(rows, html_bgcolor_attr(connector.bgcolor)))
return html
def gv_pin_row(pin_index, pin_name, pin_label, pin_color, connector):
cell_pin_left = Td(
pin_name, attribs=Attribs({"port": f"p{pin_index+1}l"}), flat=True
)
cell_pin_label = Td(pin_label, flat=True)
cell_pin_right = Td(
pin_name, attribs=Attribs({"port": f"p{pin_index+1}r"}), flat=True
)
cells = [
cell_pin_left if connector.ports_left else None,
cell_pin_label,
cell_pin_right if connector.ports_right else None,
]
return Tr(cells)
def gv_connector_loops(connector: Connector) -> List:
loop_edges = []
if connector.ports_left:
loop_side = "l"
loop_dir = "w"
elif connector.ports_right:
loop_side = "r"
loop_dir = "e"
else:
raise Exception("No side for loops")
for loop in connector.loops:
head = f"{connector.name}:p{loop[0]}{loop_side}:{loop_dir}"
tail = f"{connector.name}:p{loop[1]}{loop_side}:{loop_dir}"
loop_edges.append((head, tail))
return loop_edges
def nested_html_table(
rows: List[Union[str, List[Optional[str]], None]], table_attrs: str = ""
) -> str:
# input: list, each item may be scalar or list
# output: a parent table with one child table per parent item that is list, and one cell per parent item that is scalar
# purpose: create the appearance of one table, where cell widths are independent between rows
# attributes in any leading <tdX> inside a list are injected into to the preceeding <td> tag
html = []
html.append(
f'<table border="0" cellspacing="0" cellpadding="0"{table_attrs or ""}>'
)
num_rows = 0
for row in rows:
if isinstance(row, List):
if len(row) > 0 and any(row):
html.append(" <tr><td>")
# fmt: off
html.append(' <table border="0" cellspacing="0" cellpadding="3" cellborder="1"><tr>')
# fmt: on
for cell in row:
if cell is not None:
# Inject attributes to the preceeding <td> tag where needed
# fmt: off
html.append(f' <td balign="left">{cell}</td>'.replace("><tdX", ""))
# fmt: on
html.append(" </tr></table>")
html.append(" </td></tr>")
num_rows = num_rows + 1
elif row is not None:
html.append(" <tr><td>")
html.append(f" {row}")
html.append(" </td></tr>")
num_rows = num_rows + 1
if num_rows == 0: # empty table
# generate empty cell to avoid GraphViz errors
html.append("<tr><td></td></tr>")
html.append("</table>")
return html
def html_bgcolor_attr(color: Color) -> str:
"""Return attributes for bgcolor or '' if no color."""
return f' bgcolor="{translate_color(color, "HEX")}"' if color else ""
def html_bgcolor(color: Color, _extra_attr: str = "") -> str:
"""Return <td> attributes prefix for bgcolor or '' if no color."""
return f"<tdX{html_bgcolor_attr(color)}{_extra_attr}>" if color else ""
def html_colorbar(color: Color) -> str:
"""Return <tdX> attributes prefix for bgcolor and minimum width or None if no color."""
return html_bgcolor(color, ' width="4"') if color else None
def html_image(image):
from wireviz.DataClasses import Image
if not image:
return None
# The leading attributes belong to the preceeding tag. See where used below.
html = f'{html_size_attr(image)}><img scale="{image.scale}" src="{image.src}"/>'
if image.fixedsize:
# Close the preceeding tag and enclose the image cell in a table without
# borders to avoid narrow borders when the fixed width < the node width.
html = f""">
<table border="0" cellspacing="0" cellborder="0"><tr>
<td{html}</td>
</tr></table>
"""
return f"""<tdX{' sides="TLR"' if image.caption else ''}{html_bgcolor_attr(image.bgcolor)}{html}"""
def html_caption(image):
from wireviz.DataClasses import Image
return (
f'<tdX sides="BLR"{html_bgcolor_attr(image.bgcolor)}>{html_line_breaks(image.caption)}'
if image and image.caption
else None
)
def html_size_attr(image):
from wireviz.DataClasses import Image
# Return Graphviz HTML attributes to specify minimum or fixed size of a TABLE or TD object
return (
(
(f' width="{image.width}"' if image.width else "")
+ (f' height="{image.height}"' if image.height else "")
+ (' fixedsize="true"' if image.fixedsize else "")
)
if image
else ""
)
def html_line_breaks(inp):
return remove_links(inp).replace("\n", "<br />") if isinstance(inp, str) else inp