Implement template-based HTML output
This commit is contained in:
parent
6b1e274d57
commit
eae2694b5d
284
src/wireviz/templates/din-6771.html
generated
Normal file
284
src/wireviz/templates/din-6771.html
generated
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<meta name="generator" content="<!-- %generator% -->">
|
||||||
|
<title><!-- %title% --></title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border: 0.35mm solid black; /* line weight based on DIN 15 */
|
||||||
|
padding: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-right: 0;
|
||||||
|
border-spacing: 0mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
border: 0.25mm solid black; /* line weight based on DIN 15 */
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 0;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
/* display: inline-block; */
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 2.8mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Canvas size based on DIN 823 / DIN 6771 / EN ISO 5457 */
|
||||||
|
|
||||||
|
#frame {
|
||||||
|
position: relative;
|
||||||
|
border: 0.35mm solid black
|
||||||
|
}
|
||||||
|
|
||||||
|
.A4, .sheetsize_default { /* portrait */
|
||||||
|
width: 180mm;
|
||||||
|
height: 277mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.A3 { /* landscape */
|
||||||
|
width: 390mm;
|
||||||
|
height: 277mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.A2 { /* landscape */
|
||||||
|
width: 564mm;
|
||||||
|
height: 400mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
#diagram {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
height: calc(100% - 13 * 4.25mm);
|
||||||
|
/* TODO: auto-adapt to height of title block + BOM table;
|
||||||
|
BOM table might be above (A4) or to the left (A3 and larger) of the title block */
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#diagram svg, #diagram img {
|
||||||
|
max-width: 95%;
|
||||||
|
max-height: 100%;
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#titleblock {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0mm;
|
||||||
|
right: -0mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titleblock table {
|
||||||
|
width: 180mm;
|
||||||
|
height: 38.25mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titleblock tr, #bom tr {
|
||||||
|
height: 4.25mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.A4 #bom { /* BOM on top of title block */
|
||||||
|
position: absolute;
|
||||||
|
bottom: 38.25mm;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.A3 #bom, .A2 #bom { /* BOM to the left of title block */
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0mm;
|
||||||
|
left: 0mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bom table {
|
||||||
|
width:180mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bom th, td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bom .bom_col_id {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bom .bom_col_qty {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
width: 16mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
width: 10mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.revno {
|
||||||
|
text-align: center;
|
||||||
|
width: 6mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog {
|
||||||
|
width: 22mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process {
|
||||||
|
width: 18mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 82mm;
|
||||||
|
font-size: 5.6mm;
|
||||||
|
text-align: center;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company {
|
||||||
|
font-size: 4mm;
|
||||||
|
text-align: center;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partno {
|
||||||
|
font-size: 4mm;
|
||||||
|
text-align: center;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheetno {
|
||||||
|
width: 12.75mm;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<style type="text/css" media="print">
|
||||||
|
|
||||||
|
@page {
|
||||||
|
size: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: auto-adjust based on portrait (larger margin on left) or landscape (larger margin on top) */
|
||||||
|
#page {
|
||||||
|
margin: 10mm;
|
||||||
|
margin-left: 20mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="page">
|
||||||
|
<div id="frame" class="sheetsize_default">
|
||||||
|
|
||||||
|
<div id="diagram">
|
||||||
|
|
||||||
|
<div id="description">
|
||||||
|
<!-- %description% -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- %diagram% -->
|
||||||
|
|
||||||
|
<div id="notes">
|
||||||
|
<!-- %notes% -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="bom">
|
||||||
|
<!-- %bom_reversed% -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="titleblock">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="revno"><!-- %revisions_8% --></td>
|
||||||
|
<td class="changelog"><!-- %revisions_8_changelog% --></td>
|
||||||
|
<td class="date"><!-- %revisions_8_date% --></td>
|
||||||
|
<td class="name"><!-- %revisions_8_name% --></td>
|
||||||
|
<td class="process"></td>
|
||||||
|
<td class="date">Date</td>
|
||||||
|
<td class="name">Name</td>
|
||||||
|
<td class="title" colspan="3" rowspan="5"><!-- %title% --></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="revno"><!-- %revisions_7% --></td>
|
||||||
|
<td><!-- %revisions_7_changelog% --></td>
|
||||||
|
<td><!-- %revisions_7_date% --></td>
|
||||||
|
<td><!-- %revisions_7_name% --></td>
|
||||||
|
<td><!-- %authors_1% --></td>
|
||||||
|
<td><!-- %authors_1_date% --></td>
|
||||||
|
<td><!-- %authors_1_name% --></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="revno"><!-- %revisions_6% --></td>
|
||||||
|
<td><!-- %revisions_6_changelog% --></td>
|
||||||
|
<td><!-- %revisions_6_date% --></td>
|
||||||
|
<td><!-- %revisions_6_name% --></td>
|
||||||
|
<td><!-- %authors_2% --></td>
|
||||||
|
<td><!-- %authors_2_date% --></td>
|
||||||
|
<td><!-- %authors_2_name% --></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="revno"><!-- %revisions_5% --></td>
|
||||||
|
<td><!-- %revisions_5_changelog% --></td>
|
||||||
|
<td><!-- %revisions_5_date% --></td>
|
||||||
|
<td><!-- %revisions_5_name% --></td>
|
||||||
|
<td><!-- %authors_3% --></td>
|
||||||
|
<td><!-- %authors_3_date% --></td>
|
||||||
|
<td><!-- %authors_3_name% --></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="revno"><!-- %revisions_4% --></td>
|
||||||
|
<td><!-- %revisions_4_changelog% --></td>
|
||||||
|
<td><!-- %revisions_4_date% --></td>
|
||||||
|
<td><!-- %revisions_4_name% --></td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="revno"><!-- %revisions_3% --></td>
|
||||||
|
<td><!-- %revisions_3_changelog% --></td>
|
||||||
|
<td><!-- %revisions_3_date% --></td>
|
||||||
|
<td><!-- %revisions_3_name% --></td>
|
||||||
|
<td class="company" colspan="3" rowspan="3"><!-- %company% --></td>
|
||||||
|
<td class="partno" colspan="2" rowspan="3"><!-- %pn% --></td>
|
||||||
|
<td class="sheetno" rowspan="2">Sheet<br /><!-- %sheet_current% --></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="revno"><!-- %revisions_2% --></td>
|
||||||
|
<td><!-- %revisions_2_changelog% --></td>
|
||||||
|
<td><!-- %revisions_2_date% --></td>
|
||||||
|
<td><!-- %revisions_2_name% --></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="revno"><!-- %revisions_1% --></td>
|
||||||
|
<td><!-- %revisions_1_changelog% --></td>
|
||||||
|
<td><!-- %revisions_1_date% --></td>
|
||||||
|
<td><!-- %revisions_1_name% --></td>
|
||||||
|
<td class="sheetno">of <!-- %sheet_total% --></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Rev</td>
|
||||||
|
<td>Changelog</td>
|
||||||
|
<td>Date</td>
|
||||||
|
<td>Name</td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div> <!-- /titleblock -->
|
||||||
|
|
||||||
|
</div> <!-- /frame -->
|
||||||
|
</div> <!-- /page -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
45
src/wireviz/templates/simple.html
generated
Normal file
45
src/wireviz/templates/simple.html
generated
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"><head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="generator" content="<!-- %generator% -->">
|
||||||
|
<title><!-- %title% --></title>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
#bom table, th, td {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bom th, td {
|
||||||
|
padding: 4px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bom_col_qty {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head><body style="font-family:arial;background-color:#FFFFFF">
|
||||||
|
<h1><!-- %title% --></h1>
|
||||||
|
<h2>Diagram</h2>
|
||||||
|
|
||||||
|
<div id="description">
|
||||||
|
<!-- %description% -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="diagram">
|
||||||
|
<!-- %diagram% -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="notes">
|
||||||
|
<!-- %notes% -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Bill of Materials</h2>
|
||||||
|
|
||||||
|
<div id="bom">
|
||||||
|
<!-- %bom% -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
awg_equiv_table = {
|
awg_equiv_table = {
|
||||||
@ -134,3 +135,21 @@ def aspect_ratio(image_src):
|
|||||||
except Exception as error:
|
except Exception as error:
|
||||||
print(f'aspect_ratio(): {type(error).__name__}: {error}')
|
print(f'aspect_ratio(): {type(error).__name__}: {error}')
|
||||||
return 1 # Assume 1:1 when unable to read actual image size
|
return 1 # Assume 1:1 when unable to read actual image size
|
||||||
|
|
||||||
|
|
||||||
|
def smart_file_resolve(filename, possible_paths):
|
||||||
|
filename = Path(filename)
|
||||||
|
if filename.is_absolute():
|
||||||
|
if filename.exists():
|
||||||
|
return filename
|
||||||
|
else:
|
||||||
|
raise Exception(f'{filename} does not exist.')
|
||||||
|
else: # search all possible paths in decreasing order of precedence
|
||||||
|
possible_paths = [Path(path).resolve() for path in possible_paths]
|
||||||
|
for possible_path in possible_paths:
|
||||||
|
resolved_path = (possible_path / filename).resolve()
|
||||||
|
if (resolved_path).exists():
|
||||||
|
return resolved_path
|
||||||
|
else:
|
||||||
|
raise Exception(f'{filename} was not found in any of the following locations: \n' +
|
||||||
|
'\n'.join([str(x) for x in possible_paths]))
|
||||||
|
|||||||
@ -1,54 +1,86 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Union
|
from typing import Dict, List, Union
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from wireviz import __version__, APP_NAME, APP_URL, wv_colors
|
from wireviz import __version__, APP_NAME, APP_URL, wv_colors
|
||||||
from wireviz.DataClasses import Metadata, Options
|
from wireviz.DataClasses import Metadata, Options
|
||||||
from wireviz.wv_helper import flatten2d, open_file_read, open_file_write
|
from wireviz.wv_helper import flatten2d, open_file_read, open_file_write, smart_file_resolve
|
||||||
|
from wireviz.wv_gv_html import html_line_breaks
|
||||||
|
|
||||||
def generate_html_output(filename: Union[str, Path], bom_list: List[List[str]], metadata: Metadata, options: Options):
|
def generate_html_output(filename: Union[str, Path], bom_list: List[List[str]], metadata: Metadata, options: Options):
|
||||||
|
|
||||||
|
# load HTML template
|
||||||
|
if 'name' in metadata.get('template',{}):
|
||||||
|
# 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'{metadata["template"]["name"]}.html', [Path(filename).parent, Path(__file__).parent / 'templates'])
|
||||||
|
else:
|
||||||
|
# fall back to built-in simple template if no template was provided
|
||||||
|
templatefile = Path(__file__).parent / 'templates/simple.html'
|
||||||
|
|
||||||
|
with open_file_read(templatefile) as file:
|
||||||
|
html = file.read()
|
||||||
|
|
||||||
|
# embed SVG diagram
|
||||||
|
with open_file_read(f'{filename}.svg') as file:
|
||||||
|
svgdata = file.read()
|
||||||
|
svgdata = re.sub(
|
||||||
|
'^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>',
|
||||||
|
'<!-- XML and DOCTYPE declarations from SVG file removed -->',
|
||||||
|
svgdata, 1)
|
||||||
|
html = html.replace('<!-- %diagram% -->', svgdata)
|
||||||
|
|
||||||
|
# generate BOM table
|
||||||
|
bom = flatten2d(bom_list)
|
||||||
|
|
||||||
|
# generate BOM header (may be at the top or bottom of the table)
|
||||||
|
bom_header_html = ' <tr>\n'
|
||||||
|
for item in bom[0]:
|
||||||
|
th_class = f'bom_col_{item.lower()}'
|
||||||
|
bom_header_html = f'{bom_header_html} <th class="{th_class}">{item}</th>\n'
|
||||||
|
bom_header_html = f'{bom_header_html} </tr>\n'
|
||||||
|
|
||||||
|
# generate BOM contents
|
||||||
|
bom_contents = []
|
||||||
|
for row in bom[1:]:
|
||||||
|
row_html = ' <tr>\n'
|
||||||
|
for i, item in enumerate(row):
|
||||||
|
td_class = f'bom_col_{bom[0][i].lower()}'
|
||||||
|
row_html = f'{row_html} <td class="{td_class}">{item}</td>\n'
|
||||||
|
row_html = f'{row_html} </tr>\n'
|
||||||
|
bom_contents.append(row_html)
|
||||||
|
|
||||||
|
bom_html = '<table>\n' + bom_header_html + ''.join(bom_contents) + '</table>\n'
|
||||||
|
bom_html_reversed = '<table>\n' + ''.join(list(reversed(bom_contents))) + bom_header_html + '</table>\n'
|
||||||
|
|
||||||
|
# insert BOM table
|
||||||
|
html = html.replace('<!-- %bom% -->', bom_html)
|
||||||
|
html = html.replace('<!-- %bom_reversed% -->', bom_html_reversed)
|
||||||
|
|
||||||
|
# insert generator
|
||||||
|
html = html.replace('<!-- %generator% -->', f'{APP_NAME} {__version__} - {APP_URL}')
|
||||||
|
|
||||||
|
# insert other metadata
|
||||||
|
if metadata:
|
||||||
|
|
||||||
|
html = html.replace(f'"sheetsize_default"', '"{}"'.format(metadata.get('template',{}).get('sheetsize', ''))) # include quotes so no replacement happens within <style> definition
|
||||||
|
|
||||||
|
# TODO: handle multi-page documents
|
||||||
|
html = html.replace('<!-- %sheet_current% -->', '1')
|
||||||
|
html = html.replace('<!-- %sheet_total% -->', '1')
|
||||||
|
|
||||||
|
# fill out other generic metadata
|
||||||
|
for item, contents in metadata.items():
|
||||||
|
if isinstance(contents, (str, int, float)):
|
||||||
|
html = html.replace(f'<!-- %{item}% -->', 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):
|
||||||
|
html = html.replace(f'<!-- %{item}_{index+1}% -->', str(category))
|
||||||
|
for entry_key, entry_value in entry.items():
|
||||||
|
html = html.replace(f'<!-- %{item}_{index+1}_{entry_key}% -->', html_line_breaks(str(entry_value)))
|
||||||
|
|
||||||
|
|
||||||
with open_file_write(f'{filename}.html') as file:
|
with open_file_write(f'{filename}.html') as file:
|
||||||
file.write('<!DOCTYPE html>\n')
|
file.write(html)
|
||||||
file.write('<html lang="en"><head>\n')
|
|
||||||
file.write(' <meta charset="UTF-8">\n')
|
|
||||||
file.write(f' <meta name="generator" content="{APP_NAME} {__version__} - {APP_URL}">\n')
|
|
||||||
file.write(f' <title>{metadata["title"]}</title>\n')
|
|
||||||
file.write(f'</head><body style="font-family:{options.fontname};background-color:'
|
|
||||||
f'{wv_colors.translate_color(options.bgcolor, "HEX")}">\n')
|
|
||||||
|
|
||||||
file.write(f'<h1>{metadata["title"]}</h1>\n')
|
|
||||||
description = metadata.get('description')
|
|
||||||
if description:
|
|
||||||
file.write(f'<p>{description}</p>\n')
|
|
||||||
file.write('<h2>Diagram</h2>\n')
|
|
||||||
with open_file_read(f'{filename}.svg') as svg:
|
|
||||||
file.write(re.sub(
|
|
||||||
'^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>',
|
|
||||||
'<!-- XML and DOCTYPE declarations from SVG file removed -->',
|
|
||||||
svg.read(1024), 1))
|
|
||||||
for svgdata in svg:
|
|
||||||
file.write(svgdata)
|
|
||||||
|
|
||||||
file.write('<h2>Bill of Materials</h2>\n')
|
|
||||||
listy = flatten2d(bom_list)
|
|
||||||
file.write('<table style="border:1px solid #000000; font-size: 14pt; border-spacing: 0px">\n')
|
|
||||||
file.write(' <tr>\n')
|
|
||||||
for item in listy[0]:
|
|
||||||
file.write(f' <th style="text-align:left; border:1px solid #000000; padding: 8px">{item}</th>\n')
|
|
||||||
file.write(' </tr>\n')
|
|
||||||
for row in listy[1:]:
|
|
||||||
file.write(' <tr>\n')
|
|
||||||
for i, item in enumerate(row):
|
|
||||||
item_str = item.replace('\u00b2', '²')
|
|
||||||
align = '; text-align:right' if listy[0][i] == 'Qty' else ''
|
|
||||||
file.write(f' <td style="border:1px solid #000000; padding: 4px{align}">{item_str}</td>\n')
|
|
||||||
file.write(' </tr>\n')
|
|
||||||
file.write('</table>\n')
|
|
||||||
|
|
||||||
notes = metadata.get('notes')
|
|
||||||
if notes:
|
|
||||||
file.write(f'<h2>Notes</h2>\n<p>{notes}</p>\n')
|
|
||||||
|
|
||||||
file.write('</body></html>\n')
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user