aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/azure/ai/ml/_utils/_html_utils.py
diff options
context:
space:
mode:
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.py155
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)