pl2html 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pl2html/__init__.py ADDED
@@ -0,0 +1,168 @@
1
+ import polars as pl
2
+
3
+ __version__ = '0.1.0'
4
+
5
+
6
+ def _escape_polars_string(col_name: str) -> pl.Expr:
7
+ """
8
+ Escapes a column's string representations for secure HTML compatibility.
9
+ Because standard HTML entities use look-arounds or loops, we can fall back to
10
+ a native character loop or custom mapper if extensive characters are used,
11
+ but basic safety replaces characters sequentially.
12
+ """
13
+ return (
14
+ pl.col(col_name)
15
+ .cast(pl.String)
16
+ .str.replace_all('&', '&')
17
+ .str.replace_all('<', '&lt;')
18
+ .str.replace_all('>', '&gt;')
19
+ .str.replace_all('"', '&quot;')
20
+ .str.replace_all("'", '&#x27;')
21
+ )
22
+
23
+
24
+ def _format_integer(col_name: str) -> pl.Expr:
25
+ """Adds thousands separators to integers using look-around-free logic."""
26
+ return (
27
+ pl.col(col_name)
28
+ .fill_null(0)
29
+ .cast(pl.String)
30
+ .str.reverse()
31
+ .str.replace_all(r'\d{3}', '$0,')
32
+ .str.reverse()
33
+ .str.replace(r'^,', '')
34
+ .str.replace(r'^-,', '-')
35
+ )
36
+
37
+
38
+ def _format_float(col_name: str) -> pl.Expr:
39
+ """Formats floats with 3 decimals and thousands separators."""
40
+ rounded_str = pl.col(col_name).round(3).cast(pl.String)
41
+ int_part = rounded_str.str.split_exact('.', 1).struct.field('field_0')
42
+ dec_part = rounded_str.str.split_exact('.', 1).struct.field('field_1')
43
+
44
+ formatted_int = (
45
+ int_part.str.reverse()
46
+ .str.replace_all(r'\d{3}', '$0,')
47
+ .str.reverse()
48
+ .str.replace(r'^,', '')
49
+ .str.replace(r'^-,', '-')
50
+ )
51
+
52
+ return (
53
+ pl.when(dec_part.is_not_null() & (dec_part != ''))
54
+ .then(formatted_int + pl.lit('.') + dec_part)
55
+ .otherwise(formatted_int)
56
+ )
57
+
58
+
59
+ def _build_cell_expr(
60
+ col_name: str,
61
+ dtype: pl.DataType,
62
+ attrs: dict[str, dict[str, pl.Expr]],
63
+ ) -> pl.Expr:
64
+ """
65
+ Applies secure auto-escaping or data formatting overrides, then dynamically
66
+ compiles HTML attributes into a valid single opening <td> tag block.
67
+ """
68
+ # 1. Base Data Evaluation (Formatted and HTML Escaped safely)
69
+ if dtype.is_integer():
70
+ fmt_expr = _format_integer(col_name)
71
+ elif dtype.is_float():
72
+ fmt_expr = _format_float(col_name)
73
+ else:
74
+ fmt_expr = _escape_polars_string(col_name)
75
+
76
+ fmt_expr = fmt_expr.fill_null('')
77
+
78
+ # 2. Build Attributes Expression Natively (e.g., style="...", class="...")
79
+ attr_expr_list = []
80
+ if col_name in attrs:
81
+ for attr_name, val_expr in attrs[col_name].items():
82
+ # If the expression returns null or empty for a specific row, skip writing the attribute
83
+ compiled_attr = (
84
+ pl.when(val_expr.is_not_null() & (val_expr != ''))
85
+ .then(
86
+ pl.lit(f' {attr_name}="')
87
+ + val_expr.cast(pl.String)
88
+ + pl.lit('"')
89
+ )
90
+ .otherwise(pl.lit(''))
91
+ )
92
+ attr_expr_list.append(compiled_attr)
93
+
94
+ # Combine attribute strings together
95
+ if attr_expr_list:
96
+ opening_td = (
97
+ pl.lit('<td') + pl.concat_str(attr_expr_list) + pl.lit('>')
98
+ )
99
+ else:
100
+ opening_td = pl.lit('<td>')
101
+
102
+ return opening_td + fmt_expr + pl.lit('</td>')
103
+
104
+
105
+ def _build_html_skeleton(
106
+ visible_columns: list[str], add_row_no: bool
107
+ ) -> tuple[pl.Expr, pl.Expr]:
108
+ header_parts = ['<table>', ' <thead>\n <tr>']
109
+ if add_row_no:
110
+ header_parts.append(' <th>#</th>')
111
+ for c in visible_columns:
112
+ header_parts.append(f' <th>{c}</th>')
113
+ header_parts.append(' </tr>\n </thead>\n <tbody>')
114
+
115
+ html_header = pl.lit('\n'.join(header_parts) + '\n')
116
+ html_footer = pl.lit('\n </tbody>\n</table>')
117
+ return html_header, html_footer
118
+
119
+
120
+ def to_html(
121
+ df: pl.DataFrame | pl.LazyFrame,
122
+ *,
123
+ attrs: dict[str, dict[str, pl.Expr]] | None = None,
124
+ exclude_columns: list[str] | None = None,
125
+ add_row_no: bool = True,
126
+ ) -> pl.LazyFrame:
127
+ """
128
+ Compiles a Polars DataFrame safely into an HTML string layout.
129
+ Accepts structural custom attributes mappings to handle layout modifications natively.
130
+ """
131
+ lf = df.lazy() if isinstance(df, pl.DataFrame) else df
132
+
133
+ exclude_columns = exclude_columns or []
134
+ attrs = attrs or {}
135
+
136
+ schema = lf.collect_schema()
137
+ visible_columns = [c for c in schema.names() if c not in exclude_columns]
138
+
139
+ cell_expressions = []
140
+
141
+ # 1. Row Index Element Processing
142
+ if add_row_no:
143
+ index_expr = (
144
+ pl.lit('<td>')
145
+ + (pl.arange(1, pl.len() + 1).cast(pl.String))
146
+ + pl.lit('</td>')
147
+ )
148
+ cell_expressions.append(index_expr)
149
+
150
+ # 2. Map structural cell loops
151
+ for c in visible_columns:
152
+ cell_expressions.append(_build_cell_expr(c, schema[c], attrs))
153
+
154
+ # 3. Concatenate columns horizontally into rows
155
+ row_expr = (
156
+ pl.lit(' <tr>') + pl.concat_str(cell_expressions) + pl.lit('</tr>')
157
+ )
158
+
159
+ # 4. Generate wrappers and frame the query graph
160
+ html_header, html_footer = _build_html_skeleton(
161
+ visible_columns, add_row_no
162
+ )
163
+
164
+ return lf.select(row_expr.alias('html_row')).select(
165
+ (html_header + pl.col('html_row').str.join('\n') + html_footer).alias(
166
+ 'html_table'
167
+ )
168
+ )
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.3
2
+ Name: pl2html
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Requires-Dist: polars>=1.42.1
6
+ Requires-Python: >=3.14
7
+ Project-URL: GitHub, https://github.com/5j9/pl2html
8
+ Description-Content-Type: text/markdown
9
+
@@ -0,0 +1,4 @@
1
+ pl2html/__init__.py,sha256=HLrvU73JLQsIomnKdRfqJsyeZSY4pIghhwDzbOmfuP0,5285
2
+ pl2html-0.1.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
3
+ pl2html-0.1.0.dist-info/METADATA,sha256=6B3vix34Jd3L9vMDQOIIELmkCYXwNOqXmuOkQtvBdr8,233
4
+ pl2html-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any