statstables 0.0a1__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.
@@ -0,0 +1 @@
1
+ from statstables import tables, renderers, utils
@@ -0,0 +1,237 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class Renderer(ABC):
5
+ def __init__(self):
6
+ pass
7
+
8
+ @abstractmethod
9
+ def render(self):
10
+ pass
11
+
12
+ @abstractmethod
13
+ def generate_header(self): ...
14
+
15
+ @abstractmethod
16
+ def generate_body(self): ...
17
+
18
+ @abstractmethod
19
+ def generate_footer(self): ...
20
+
21
+ @abstractmethod
22
+ def _create_line(self): ...
23
+
24
+
25
+ class LatexRenderer(Renderer):
26
+
27
+ # LaTeX escape characters, borrowed from pandas.io.formats.latex and Stargazer
28
+ _ESCAPE_CHARS = [
29
+ ("\\", r"\textbackslash "),
30
+ ("_", r"\_"),
31
+ ("%", r"\%"),
32
+ ("$", r"\$"),
33
+ ("#", r"\#"),
34
+ ("{", r"\{"),
35
+ ("}", r"\}"),
36
+ ("~", r"\textasciitilde "),
37
+ ("^", r"\textasciicircum "),
38
+ ("&", r"\&"),
39
+ ]
40
+
41
+ def __init__(self, table):
42
+ self.table = table
43
+
44
+ def render(self, only_tabular=False):
45
+ out = self.generate_header(only_tabular)
46
+ out += self.generate_body()
47
+ out += self.generate_footer(only_tabular)
48
+
49
+ return out
50
+
51
+ def generate_header(self, only_tabular=False, column_alignment=None):
52
+ header = ""
53
+ if not only_tabular:
54
+ header += "\\begin{table}[!htbp]\n \\centering\n"
55
+
56
+ if self.table.caption_location == "top":
57
+ if self.table.caption is not None:
58
+ header += " \\caption{" + self.table.caption + "}\n"
59
+
60
+ if self.table.label is not None:
61
+ header += " \\label{" + self.table.label + "}\n"
62
+
63
+ content_columns = "c" * self.table.ncolumns
64
+ if self.table.include_index:
65
+ content_columns = "l" + content_columns
66
+ header += "\\begin{tabular}{" + content_columns + "}\n"
67
+ header += " \\toprule\n"
68
+ for col, spans in self.table._multicolumns:
69
+ header += (" " + self.table.index_name + " & ") * self.table.include_index
70
+ header += " & ".join(
71
+ [
72
+ f"\\multicolumn{{{s}}}{{c}}{{{self._escape(c)}}}"
73
+ for c, s in zip(col, spans)
74
+ ]
75
+ )
76
+ header += " \\\\\n"
77
+ if self.table.custom_tex_lines["after-multicolumns"]:
78
+ for line in self.table.custom_tex_lines["after-multicolumns"]:
79
+ header += " " + line + "\n"
80
+ if self.table.show_columns:
81
+ header += (" " + self.table.index_name + " & ") * self.table.include_index
82
+ header += " & ".join(
83
+ [
84
+ self._escape(self.table._column_labels.get(col, col))
85
+ for col in self.table.columns
86
+ ]
87
+ )
88
+ header += "\\\\\n"
89
+ if self.table.custom_tex_lines["after-columns"]:
90
+ for line in self.table.custom_tex_lines["after-columns"]:
91
+ header += " " + line + "\n"
92
+ if self.table.custom_lines["after-columns"]:
93
+ for line in self.table.custom_lines["after-columns"]:
94
+ header += self._create_line(line)
95
+ header += " \\midrule\n"
96
+
97
+ return header
98
+
99
+ def generate_body(self):
100
+ rows = self.table._create_rows()
101
+ row_str = ""
102
+ for row in rows:
103
+ row_str += " " + " & ".join([self._escape(r) for r in row]) + " \\\\\n"
104
+ for line in self.table.custom_lines["after-body"]:
105
+ row_str += self._create_line(line)
106
+ for line in self.table.custom_tex_lines["after-body"]:
107
+ row_str += line
108
+ return row_str
109
+
110
+ def generate_footer(self, only_tabular=False):
111
+ footer = " \\bottomrule\n"
112
+ if self.table.custom_lines["after-footer"]:
113
+ for line in self.table.custom_lines["after-footer"]:
114
+ footer += self._create_line(line)
115
+ footer += " \\bottomrule\n"
116
+ if self.table.notes:
117
+ for note, alignment, escape in self.table.notes:
118
+ align_cols = self.table.ncolumns + self.table.include_index
119
+ footer += f" \\multicolumn{{{align_cols}}}{{{alignment}}}"
120
+ _note = self._escape(note) if escape else note
121
+ footer += "{{" + "\\small \\textit{" + _note + "}}}\\\\\n"
122
+
123
+ footer += "\\end{tabular}\n"
124
+ if not only_tabular:
125
+ if self.table.caption_location == "bottom":
126
+ if self.table.caption is not None:
127
+ footer += " \\caption{" + self.table.caption + "}\n"
128
+
129
+ if self.table.label is not None:
130
+ footer += " \\label{" + self.table.label + "}\n"
131
+ footer += "\\end{table}\n"
132
+
133
+ return footer
134
+
135
+ def _escape(self, text):
136
+ for char, escaped in self._ESCAPE_CHARS:
137
+ text = text.replace(char, escaped)
138
+ return text
139
+
140
+ def _create_line(self, line):
141
+ out = (" " + line["label"] + " & ") * self.table.include_index
142
+ out += " & ".join(line["line"])
143
+ out += "\\\\\n"
144
+
145
+ return out
146
+
147
+
148
+ class HTMLRenderer(Renderer):
149
+
150
+ ALIGNMENTS = {"l": "left", "c": "center", "r": "right"}
151
+
152
+ def __init__(self, table):
153
+ self.table = table
154
+ self.ncolumns = self.table.ncolumns + int(self.table.include_index)
155
+
156
+ def render(self):
157
+ out = self.generate_header()
158
+ out += self.generate_body()
159
+ out += self.generate_footer()
160
+ return out
161
+
162
+ def generate_header(self):
163
+ header = "<table>\n"
164
+ header += " <thead>\n"
165
+ for col, spans in self.table._multicolumns:
166
+ header += " <tr>\n"
167
+ header += (
168
+ f" <th>{self.table.index_name}</th>\n"
169
+ ) * self.table.include_index
170
+ header += " " + " ".join(
171
+ [
172
+ f'<th colspan="{s}" style="text-align:center;">{c}</th>'
173
+ for c, s in zip(col, spans)
174
+ ]
175
+ )
176
+ header += "\n"
177
+ header += " </tr>\n"
178
+ for line in self.table.custom_html_lines["after-multicolumns"]:
179
+ # TODO: Implement
180
+ pass
181
+ if self.table.show_columns:
182
+ header += " <tr>\n"
183
+ header += (
184
+ f" <th>{self.table.index_name}</th>\n"
185
+ ) * self.table.include_index
186
+ for col in self.table.columns:
187
+ header += f' <th style="text-align:center;">{self.table._column_labels.get(col, col)}</th>\n'
188
+ header += " </tr>\n"
189
+ if self.table.custom_lines["after-columns"]:
190
+ for line in self.table.custom_lines["after-columns"]:
191
+ header += self._create_line(line)
192
+ header += " </thead>\n"
193
+ header += " <tbody>\n"
194
+ return header
195
+
196
+ def generate_body(self):
197
+ rows = self.table._create_rows()
198
+ row_str = ""
199
+ for row in rows:
200
+ row_str += " <tr>\n"
201
+ for r in row:
202
+ row_str += f" <td>{r}</td>\n"
203
+ row_str += " </tr>\n"
204
+ for line in self.table.custom_lines["after-body"]:
205
+ row_str += self._create_line(line)
206
+ for line in self.table.custom_html_lines["after-body"]:
207
+ row_str += line
208
+ pass
209
+ return row_str
210
+
211
+ def generate_footer(self):
212
+ footer = ""
213
+ if self.table.custom_lines["after-footer"]:
214
+ footer += " <tr>\n"
215
+ for line in self.table.custom_lines["after-footer"]:
216
+ footer += self._create_line(line)
217
+ footer += " </tr>\n"
218
+ if self.table.notes:
219
+ ncols = self.table.ncolumns + self.table.include_index
220
+ for note, alignment, _ in self.table.notes:
221
+ # TODO: This doesn't actually align where expected in a notebook. Fix.
222
+ footer += (
223
+ f' <tr><td colspan="{ncols}" '
224
+ f'style="text-align:{self.ALIGNMENTS[alignment]};'
225
+ f'"><i>{note}</i></td></tr>\n'
226
+ )
227
+ footer += " </tbody>\n"
228
+ return footer
229
+
230
+ def _create_line(self, line):
231
+ out = " <tr>\n"
232
+ out += (f" <th>{line['label']}</th>\n") * self.table.include_index
233
+ for l in line["line"]:
234
+ out += f" <th>{l}</th>\n"
235
+ out += " </tr>\n"
236
+
237
+ return out