diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/azure/ai/ml/_utils/_html_utils.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/azure/ai/ml/_utils/_html_utils.py | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/_utils/_html_utils.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/_utils/_html_utils.py new file mode 100644 index 00000000..faf6a406 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/_utils/_html_utils.py @@ -0,0 +1,155 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + +import logging +from collections import OrderedDict +from datetime import datetime, timedelta +from html import escape + +SUPPORTED_VALUE_TYPE_TUPLE = (int, float, str, datetime, timedelta) +TABLE_FMT = '<table style="width:100%">{0}</table>' +ROW_FMT = "<tr>{0}</tr>" +HEADER_FMT = "<th>{0}</th>" +DATA_FMT = "<td>{0}</td>" +# target="_blank" opens in new tab, rel="noopener" is for perf + security +LINK_FMT = '<a href="{0}" target="_blank" rel="noopener">{1}</a>' + + +def convert_dict_to_table(object_to_convert): + # Case 1: All non-collection values -> table of 1 row + # Case 2: All collection values, lens eq N -> assert lengths eq, table of N rows + # Case 3: collection and non-collection values -> table of 1 row, collections nest + # Case 4: All collection values, lens unequal -> table of 1 row, each is nested collection? + + if not isinstance(object_to_convert, dict): + raise AssertionError("Expected a dict or subclass, got {0}".format(type(object_to_convert))) + + if len(object_to_convert) == 0: + return "" + + ordered_obj = OrderedDict(object_to_convert) + + def is_collection_type(val): + return hasattr(val, "__len__") and not isinstance(val, str) + + is_collection = [is_collection_type(value) for value in ordered_obj.values()] + + all_rows = [get_header_row_string(ordered_obj.keys())] + + def values_to_data_row(values): + cells = [to_html(value) for value in values] + return get_data_row_string(cells) + + if all(is_collection): + # Cases 2 and 4 + length = len(list(ordered_obj.values)[0]) + if any(len(v) != length for v in ordered_obj.values()): + # Case 4 + logging.warning("Uneven column lengths in table conversion") + all_rows.append(values_to_data_row(ordered_obj.values())) + + else: + # Case 2 - sad transpose + for i in range(length): + value_list = [val[i] for val in ordered_obj.values()] + all_rows.append(values_to_data_row(value_list)) + + else: + # Cases 1 and 3 + # Table of 1 row of values or mixed types, table of 1 row + all_rows.append(values_to_data_row(ordered_obj.values())) + + return table_from_html_rows(all_rows) + + +def convert_list_to_table(object_to_convert): + if not isinstance(object_to_convert, list): + raise AssertionError("Expected a list or subclass, got {0}".format(type(object_to_convert))) + + if len(object_to_convert) == 0: + return "" + + all_values = [to_html(element) for element in object_to_convert] + all_rows = [get_data_row_string([element]) for element in all_values] + return table_from_html_rows(all_rows) + + +# Mapping from complex type to HTML converters +# Unspecified types default to convert_value +_type_to_converter = {list: convert_list_to_table, dict: convert_dict_to_table} + + +def to_html(object_to_convert): + candidate_converters = [k for k in _type_to_converter if isinstance(object_to_convert, k)] + + if len(candidate_converters) == 0: + converter = convert_value + elif len(candidate_converters) == 1: + converter = _type_to_converter[candidate_converters[0]] + else: + logging.warning("Multiple candidate converters found for type %s", type(object_to_convert)) + converter = convert_value + + converted_value = converter(object_to_convert) + return converted_value + + +def is_string_link(string: str) -> bool: + return isinstance(string, str) and string.strip().lower().startswith("http") + + +def make_link(link_string: str, link_text: str = "") -> str: + if not link_text: # Actually want truthy string + link_text = "Link" + return LINK_FMT.format(escape(link_string), link_text) + + +def convert_value(value: str) -> str: + if value is None: + return "" + if is_string_link(value): + return make_link(value) + if not isinstance(value, SUPPORTED_VALUE_TYPE_TUPLE): + logging.warning("Unsupported type %s for html, converting", type(value)) + + # TODO: Figure out a good escaping story here right now it breaks existing tags + return str(value) + + +def get_header_row_string(column_headers): + headers = [HEADER_FMT.format(header) for header in column_headers] + return ROW_FMT.format("".join(headers)) + + +def get_data_row_string(data_values): + data = [DATA_FMT.format(datum) for datum in data_values] + return ROW_FMT.format("".join(data)) + + +def table_from_html_rows(list_of_rows): + # type/: (List[str]) -> str + return TABLE_FMT.format("".join(list_of_rows)) + + +def to_formatted_html_table(rows, header): + html = ["""<table style="width:100%; border:2px solid black" >"""] + if header is not None: + html_row = "</td><td>".join(column for column in header) + html.append( + """<tr style="font-weight:bold; border-bottom:1pt solid black; border-right: 1pt solid black; + text-align: center"><td>{}</td></tr>""".format( + html_row + ) + ) + + for row in rows: + html_row = "</td><td>".join(str(value) for value in row) + html.append( + """<tr style="width:100%; word-wrap: break-word; border-bottom:1pt solid black; + text-align: center"><td>{}</td></tr>""".format( + html_row + ) + ) + html.append("</table>") + return "".join(html) |