Source code for cave.html.html_builder

import inspect
import json
import logging
import os
import re
import shutil
from collections import OrderedDict
from traceback import print_exc

from cave.__version__ import __version__ as version
from cave.html.html_helpers import figure_to_html
from cave.utils.tooltips import get_tooltip

__author__ = "Marius Lindauer"
__copyright__ = "Copyright 2016, ML4AAD"
__license__ = "MIT"
__email__ = "lindauer@cs.uni-freiburg.de"


[docs]class HTMLBuilder(object): def __init__(self, output_dn: str, scenario_name: str, logo_fn: str, logo_custom: bool=False): ''' The dictionary structure in the HTML-Builder follows the following syntax: :: {"top1" : { "tooltip": str|None, "subtop1: { # generates a further bottom if it is dictionary "tooltip": str|None, ... } "table": str|None (html table) "figure" : str|None (file name) "bokeh" : (str, str)|None # (script, div as returned by components()) } "top2: { ... } } Arguments --------- output_dn:str output directory name scenario_name:str name of scenario logo_fn: str path to the logo of the configurator logo_custom: bool if true, logo ist treated as external logo that needs to be copied ''' self.logger = logging.getLogger("HTMLBuilder") self.own_folder = os.path.realpath(os.path.abspath(os.path.split(inspect.getfile(inspect.currentframe()))[0])) self.logo_fn = logo_fn self.logo_custom = logo_custom self.output_dn = output_dn self.unique_id_counter = 0 self.budget = '' self.relative_content_js = os.path.join('content', 'js') self.relative_content_images = os.path.join('content', 'images') if output_dn: os.makedirs(os.path.join(self.output_dn, self.relative_content_js), exist_ok=True) os.makedirs(os.path.join(self.output_dn, self.relative_content_images), exist_ok=True) # todo make relative dirs again # Copy subfolders subfolders = ["css", "images", "js", "font"] for sf in subfolders: try: shutil.rmtree(os.path.join(self.output_dn, "html", sf), ignore_errors=True) shutil.copytree(os.path.join(self.own_folder, "web_files", sf), os.path.join(self.output_dn, "html", sf)) except OSError: print_exc() self.header_part_1 = ''' <!DOCTYPE html> <html> <head> <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> <meta content="utf-8" http-equiv="encoding"> <title>CAVE</title> <link href="html/css/accordion.css" rel="stylesheet" /> <link href="html/css/table.css" rel="stylesheet" /> <link href="html/css/lightbox.min.css" rel="stylesheet" /> <link href="html/css/help-tip.css" rel="stylesheet" /> <link href="html/css/global.css" rel="stylesheet" /> <link href="html/css/back-to-top.css" rel="stylesheet" /> <link href="html/css/tabs.css" rel="stylesheet" /> <link href="html/css/bokeh-1.1.0.min.css" rel="stylesheet" type="text/css"> <link href="html/css/bokeh-widgets-1.1.0.min.css" rel="stylesheet" type="text/css"> <link href="html/css/bokeh-tables-1.1.0.min.css" rel="stylesheet" type="text/css"> <script src="html/js/tabs.js"></script> <script src="html/js/bokeh-1.1.0.min.js"></script> <script src="html/js/bokeh-widgets-1.1.0.min.js"></script> <script src="html/js/bokeh-tables-1.1.0.min.js"></script> <!--Below here are the includes of scripts for the report (e.g. bokeh)--> ''' self.header_part_2 = ''' <!--Above here are the includes of scripts for the report (e.g. bokeh)--> </head> <body> <script src="http://www.w3schools.com/lib/w3data.js"></script> <script src="html/js/lightbox-plus-jquery.min.js"></script> <header> <div class='l-wrapper'> <img class='logo logo--configurator' src="html/images/{}" /> <img class='logo logo--cave' src="html/images/CAVE_logo.png" /> <img class='logo logo--ml' src="html/images/automl-logo.png" /> </div> </header> <div class='l-wrapper'> <h1></h1> '''.format(self.logo_fn if not self.logo_custom else 'custom_logo.png') self.footer = ''' </div> <footer> <div class='l-wrapper'> Generated by <a href="https://github.com/automl/CAVE">CAVE v{}</a> and developed by <a href="http://www.automl.org">autoML</a> | Optimized for Chrome and Firefox </div> </footer>'''.format(version) + ''' <script> var acc = document.getElementsByClassName("accordion"); var i; for (i = 0; i < acc.length; i++) { acc[i].onclick = function(){ this.classList.toggle("active"); this.nextElementSibling.classList.toggle("show"); } } </script> <script src="html/js/back-to-top.js"></script> </body> </html> '''
[docs] def generate_webpage(self, data_dict: OrderedDict): ''' Arguments --------- data_dict : OrderedDict see constructor ''' html_head, html_body = "", "" html_head += self.header_part_1 # Get components (script, div) for each entry in report scripts, divs = self.generate_html(data_dict) # Scripts go into header, divs go into body for script in scripts: html_head += script # e.g. bokeh-scripts used for hover for div in divs: html_body += div html_head += self.header_part_2 # Close header after adding all scripts html = html_head + html_body + self.footer # Write webpage to file with open(os.path.join(self.output_dn, "report.html"), "w") as fp: fp.write(html) # If available, add custom logo if self.logo_custom: original_path = self.logo_fn self.logo_fn = os.path.join(self.output_dn, "html", 'images', 'custom_logo.png') self.logger.debug("Attempting to copy %s to %s", original_path, self.logo_fn) shutil.copyfile(original_path, self.logo_fn) self.logo_custom = False
[docs] def generate_html(self, data_dict: OrderedDict): with open(os.path.join(self.output_dn, 'debug', 'webpage_dict.json'), 'w') as f: f.write(json.dumps(data_dict, indent=2)) # Generate scripts, divs = [], [] for k, v in data_dict.items(): if not v: # ignore empty entry self.logger.debug("No content for %s, skipping in html-generation", k) continue script, div = self.add_layer(layer_name=k, data_dict=v) if script: scripts.append(script) divs.append(div) return scripts, divs
[docs] def add_layer(self, layer_name, data_dict: OrderedDict, is_tab: bool=False): ''' add a further layer of top data_dict keys Parameters ---------- layer_name: str name of the layer data_dict : OrderedDict see constructor is_tab: bool if True, don't use accordion but tab-structure to wrap content Returns ------- (script, div): (str, str) script goes into header, div goes into body ''' script, div = "", "" if layer_name is None: layer_name = "" unique_layer_name = layer_name + self.get_unique_id() # Add tooltip, if possible tooltip = data_dict.get("tooltip", None) if tooltip is not None: tooltip = "<div class=\"help-tip\"><p>{}</p></div>".format(tooltip) # TODO elif is obsolete / can be merged into first option (simplify!) elif get_tooltip(layer_name): # if no tooltip is parsed, try to look it up tooltip = "<div class=\"help-tip\"><p>{}</p></div>".format(get_tooltip(layer_name)) else: tooltip = "" # Start accordion-panel if not is_tab: div += "<div class=\"accordion\">{0} {1}</div>\n".format(layer_name, tooltip) div += "<div class=\"panel\">\n" # If this layer represents budgets, add tabs for this layer, add tabs-code sublayer_names = [k for k, v in data_dict.items() if isinstance(v, dict)] use_tabs = False if len(sublayer_names) >= 1 and all([sn.lower().startswith('budget') for sn in sublayer_names]): use_tabs = True if use_tabs: div += "<div class=\"tab\">\n" tabs_names = [k.replace('_', ' ') for k, v in data_dict.items() if isinstance(v, dict)] default_open_id = "defaultOpen" + self.get_unique_id() div += " <button class=\"tablinks\" onclick=\"openTab(event, '{0}', '{1}')\" "\ "id=\"{2}\">{1}</button>\n".format(unique_layer_name, tabs_names[0], default_open_id) for name in tabs_names[1:]: div += " <button class=\"tablinks\" onclick=\"openTab(event, '{0}', '{1}')\">{1}</button>\n".format( unique_layer_name, name) div += "</div>\n" for k, v in data_dict.items(): if k == "tooltip": continue if k.startswith('budget'): self.budget = k[7:] if not v: if isinstance(v, dict): continue else: return '', '' elif isinstance(v, dict): if use_tabs: div += "<div id=\"{0}\" class=\"tabcontent\">\n".format(unique_layer_name + k.replace('_', ' ')) div += "<div class=\"pane\">\n" add_script, add_div = self.add_layer(k, v, is_tab=use_tabs) script += add_script div += add_div if use_tabs: # close div div += "</div>\n" div += "</div>\n" elif k == "figure": div += figure_to_html(v, prefix=self.output_dn) elif k == "figure_x2": div += figure_to_html(v, prefix=self.output_dn, max_in_a_row=2) elif k == "table": div += "<div style=\"overflow-x: auto\" align=\"center\">\n{}\n</div>\n".format(v) elif k == "html": div += ("<div align=\"center\">\n<a href='{}'>Interactive " "Plot</a>\n</div>\n".format(v[len(self.output_dn):].lstrip("/"))) elif k == "bokeh": # Escape path for URL (remove spaces, slashes and single quotes) path_script = os.path.join(self.relative_content_js, '_'.join([layer_name, self.budget, self.get_unique_id(), 'script.js'])) path_script = path_script.translate({ord(c): None for c in ' \''}) # Write script to file if self.output_dn: with open(os.path.join(self.output_dn, path_script), 'w') as fn: js_code = re.sub('<.*?>', '', v[0].strip()) # Remove script-tags fn.write(js_code) script += "<script src=\"" + path_script + "\"></script>\n" else: script += v[0] div += "<div align=\"center\">\n{}\n</div>\n".format(v[1]) else: try: div += v except Exception as err: self.logger.warning("Failed on interpreting: %s, %s, %s (Error: %s)", str(layer_name), str(k), str(v), err, exc_info=1) if use_tabs: # close tab with selecting first element by default div += "<script> \n" div += "// Get the element with id=\"{}\" and click on it \n".format(default_open_id) div += "document.getElementById(\"{}\").click(); \n".format(default_open_id) div += "</script> \n" if not is_tab: div += "</div>" return script, div
[docs] def get_unique_id(self): self.unique_id_counter += 1 return str(self.unique_id_counter)