aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/azure/ai/ml/_utils/_html_utils.py
blob: faf6a406d1de63105d4f33188539ca6e4ff098e6 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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)