modm_data.html.table
1# Copyright 2022, Niklas Hauser 2# SPDX-License-Identifier: MPL-2.0 3 4import re 5from functools import cached_property 6from collections import defaultdict 7from .text import replace as html_replace, ReDict, Text 8 9 10class Cell(Text): 11 def __init__(self, x: int, y: int, xs: int=1, ys: int=1, head: bool=False): 12 super().__init__() 13 self._span = (xs, ys) 14 self._pos = [(x, y)] 15 self._head = head 16 17 @property 18 def span(self) -> tuple[int,int]: 19 return self._span 20 21 @property 22 def positions(self) -> list[tuple[int,int]]: 23 return self._pos 24 25 @property 26 def x(self) -> int: 27 return self._pos[0][0] 28 29 @property 30 def y(self) -> int: 31 return self._pos[0][1] 32 33 @property 34 def xspan(self) -> int: 35 return self._span[0] 36 37 @property 38 def yspan(self) -> int: 39 return self._span[1] 40 41 def __hash__(self) -> int: 42 return hash(str(self._pos)) 43 44 def __repr__(self) -> str: 45 return f"Cell({','.join(map(str, self._pos))})" 46 47 48class Domains: 49 def __init__(self, table, domains, columns, pattern=None): 50 self._table = table 51 self._domains = domains 52 self._columns = columns 53 self._pattern = pattern 54 55 def domains_y(self, pattern=None, **subs) -> list[str]: 56 domains = sorted(self._table._domains_y(self._columns, **subs).keys()) 57 if pattern is not None: 58 domains = [d for d in domains if re.search(pattern, d, re.IGNORECASE)] 59 return domains 60 61 def cells(self, pattern_x: str, pattern_y: str = None, **subs) -> list[Cell]: 62 domains_y = self.domains_y(pattern_y, **subs) 63 domains_x = self._table.domains_x(pattern_x, **subs) 64 cells = defaultdict(lambda: defaultdict(set)) 65 for dom_y in domains_y: 66 for dom_x in domains_x: 67 for x in self._table._domains_x(**subs)[dom_x]: 68 for y in self._table._domains_y(self._columns, **subs)[dom_y]: 69 # print(x, y, dom_x, dom_y) 70 cells[dom_y][dom_x].add(self._table.cell(x, y)) 71 72 return ReDict({k:ReDict({vk:list(vv) for vk,vv in v.items()}) for k,v in cells.items()}) 73 74 def __repr__(self) -> str: 75 return f"Domains({self.domains_x()}, {self.domains_y()})" 76 77 78class Table: 79 def __init__(self, heading=None): 80 self._heading = heading or Heading("") 81 self._cells = [] 82 self._size = (0,0) 83 self._grid = None 84 self._hrows = 0 85 self._caption = Text("") 86 87 def __repr__(self) -> str: 88 return f"Table({self.columns}×{self.rows})" 89 90 def heading(self, **filters): 91 return self._heading.text(**filters) 92 93 def caption(self, **filters): 94 return self._caption.text(**filters) 95 96 def _domains_x(self, **subs) -> dict[str, list[int]]: 97 domains = defaultdict(list) 98 for x in range(self.columns): 99 cell = None 100 domain = [] 101 for y in range(self._hrows): 102 if (ncell := self.cell(x, y)) != cell: 103 cell = ncell 104 domain.append(cell) 105 domain = ":".join(cell.text(**subs).replace(":", "") for cell in domain) 106 domains[domain].append(x) 107 return dict(domains) 108 109 def _domains_y(self, columns: list[int], **subs) -> dict[str, list[int]]: 110 domains = defaultdict(list) 111 for y in range(self._hrows, self.rows): 112 cell = None 113 cells = [] 114 for x in columns: 115 if (ncell := self.cell(x, y)) != cell: 116 cell = ncell 117 cells.append(cell) 118 if cells: 119 domain = ":".join(cell.text(**subs).replace(":", "") for cell in cells) 120 domains[domain].append(y) 121 return dict(domains) 122 123 def domains_x(self, pattern=None, **subs) -> list[str]: 124 domains = sorted(self._domains_x(**subs).keys()) 125 if pattern is not None: 126 domains = [d for d in domains if re.search(pattern, d, re.IGNORECASE)] 127 return domains 128 129 def domains(self, pattern: str, **subs) -> Domains: 130 domains = [] 131 columns = [] 132 for domain, cols in self._domains_x(**subs).items(): 133 if re.search(pattern, domain, re.IGNORECASE): 134 domains.append(domain) 135 columns.extend(cols) 136 return Domains(self, domains, columns, pattern) 137 138 def cell_rows(self, pattern: str = None, **subs) -> dict[str, list[Cell]]: 139 columns = [] 140 for domain, cols in self._domains_x(**subs).items(): 141 if pattern is None or re.search(pattern, domain, re.IGNORECASE): 142 columns.append((domain, cols)) 143 for y in range(self._hrows, self.rows): 144 values = defaultdict(list) 145 for domain, cols in columns: 146 values[domain].extend(self.cell(c, y) for c in cols) 147 yield ReDict(values) 148 149 def cell(self, x: int, y: int) -> Cell: 150 assert x < self.columns 151 assert y < self.rows 152 return self._grid[y][x] 153 154 @property 155 def columns(self) -> int: 156 return self._size[0] 157 158 @property 159 def rows(self) -> int: 160 return self._size[1] 161 162 @property 163 def size(self) -> tuple[int,int]: 164 return self._size 165 166 def cell(self, x, y) -> Cell: 167 return self._grid[y][x] 168 169 def _normalize(self): 170 xsize = sum(c._span[0] for c in self._cells if c._pos[0][1] == 0) 171 ysize = max(c._pos[0][1] + c._span[1] for c in self._cells) 172 self._size = (xsize, ysize) 173 self._grid = [[None for _ in range(xsize)] for _ in range(ysize)] 174 175 xpos, ypos = 0, 0 176 for cell in self._cells: 177 # print(cell._pos, cell._span, cell._data) 178 ypos = cell._pos[0][1] 179 if cell._head: 180 self._hrows = ypos + 1 181 cell._pos = [] 182 # Previous cells with multiple rows may already have taken our current xpos 183 # We must find the next cell that's still empty 184 xpos = next((x for x, c in enumerate(self._grid[ypos]) if c is None), xpos) 185 for y in range(ypos, min(ypos + cell._span[1], ysize)): 186 for x in range(xpos, min(xpos + cell._span[0], xsize)): 187 self._grid[y][x] = cell 188 cell._pos.append((x, y)) 189 xpos += cell._span[0] 190 191 def render(self): 192 for y in range(self.rows): 193 for x in range(self.columns): 194 print(self.cell(x, y).text()[:15] if self.cell(x, y) is not None else "None", end="\t") 195 print()
11class Cell(Text): 12 def __init__(self, x: int, y: int, xs: int=1, ys: int=1, head: bool=False): 13 super().__init__() 14 self._span = (xs, ys) 15 self._pos = [(x, y)] 16 self._head = head 17 18 @property 19 def span(self) -> tuple[int,int]: 20 return self._span 21 22 @property 23 def positions(self) -> list[tuple[int,int]]: 24 return self._pos 25 26 @property 27 def x(self) -> int: 28 return self._pos[0][0] 29 30 @property 31 def y(self) -> int: 32 return self._pos[0][1] 33 34 @property 35 def xspan(self) -> int: 36 return self._span[0] 37 38 @property 39 def yspan(self) -> int: 40 return self._span[1] 41 42 def __hash__(self) -> int: 43 return hash(str(self._pos)) 44 45 def __repr__(self) -> str: 46 return f"Cell({','.join(map(str, self._pos))})"
Inherited Members
class
Domains:
49class Domains: 50 def __init__(self, table, domains, columns, pattern=None): 51 self._table = table 52 self._domains = domains 53 self._columns = columns 54 self._pattern = pattern 55 56 def domains_y(self, pattern=None, **subs) -> list[str]: 57 domains = sorted(self._table._domains_y(self._columns, **subs).keys()) 58 if pattern is not None: 59 domains = [d for d in domains if re.search(pattern, d, re.IGNORECASE)] 60 return domains 61 62 def cells(self, pattern_x: str, pattern_y: str = None, **subs) -> list[Cell]: 63 domains_y = self.domains_y(pattern_y, **subs) 64 domains_x = self._table.domains_x(pattern_x, **subs) 65 cells = defaultdict(lambda: defaultdict(set)) 66 for dom_y in domains_y: 67 for dom_x in domains_x: 68 for x in self._table._domains_x(**subs)[dom_x]: 69 for y in self._table._domains_y(self._columns, **subs)[dom_y]: 70 # print(x, y, dom_x, dom_y) 71 cells[dom_y][dom_x].add(self._table.cell(x, y)) 72 73 return ReDict({k:ReDict({vk:list(vv) for vk,vv in v.items()}) for k,v in cells.items()}) 74 75 def __repr__(self) -> str: 76 return f"Domains({self.domains_x()}, {self.domains_y()})"
62 def cells(self, pattern_x: str, pattern_y: str = None, **subs) -> list[Cell]: 63 domains_y = self.domains_y(pattern_y, **subs) 64 domains_x = self._table.domains_x(pattern_x, **subs) 65 cells = defaultdict(lambda: defaultdict(set)) 66 for dom_y in domains_y: 67 for dom_x in domains_x: 68 for x in self._table._domains_x(**subs)[dom_x]: 69 for y in self._table._domains_y(self._columns, **subs)[dom_y]: 70 # print(x, y, dom_x, dom_y) 71 cells[dom_y][dom_x].add(self._table.cell(x, y)) 72 73 return ReDict({k:ReDict({vk:list(vv) for vk,vv in v.items()}) for k,v in cells.items()})
class
Table:
79class Table: 80 def __init__(self, heading=None): 81 self._heading = heading or Heading("") 82 self._cells = [] 83 self._size = (0,0) 84 self._grid = None 85 self._hrows = 0 86 self._caption = Text("") 87 88 def __repr__(self) -> str: 89 return f"Table({self.columns}×{self.rows})" 90 91 def heading(self, **filters): 92 return self._heading.text(**filters) 93 94 def caption(self, **filters): 95 return self._caption.text(**filters) 96 97 def _domains_x(self, **subs) -> dict[str, list[int]]: 98 domains = defaultdict(list) 99 for x in range(self.columns): 100 cell = None 101 domain = [] 102 for y in range(self._hrows): 103 if (ncell := self.cell(x, y)) != cell: 104 cell = ncell 105 domain.append(cell) 106 domain = ":".join(cell.text(**subs).replace(":", "") for cell in domain) 107 domains[domain].append(x) 108 return dict(domains) 109 110 def _domains_y(self, columns: list[int], **subs) -> dict[str, list[int]]: 111 domains = defaultdict(list) 112 for y in range(self._hrows, self.rows): 113 cell = None 114 cells = [] 115 for x in columns: 116 if (ncell := self.cell(x, y)) != cell: 117 cell = ncell 118 cells.append(cell) 119 if cells: 120 domain = ":".join(cell.text(**subs).replace(":", "") for cell in cells) 121 domains[domain].append(y) 122 return dict(domains) 123 124 def domains_x(self, pattern=None, **subs) -> list[str]: 125 domains = sorted(self._domains_x(**subs).keys()) 126 if pattern is not None: 127 domains = [d for d in domains if re.search(pattern, d, re.IGNORECASE)] 128 return domains 129 130 def domains(self, pattern: str, **subs) -> Domains: 131 domains = [] 132 columns = [] 133 for domain, cols in self._domains_x(**subs).items(): 134 if re.search(pattern, domain, re.IGNORECASE): 135 domains.append(domain) 136 columns.extend(cols) 137 return Domains(self, domains, columns, pattern) 138 139 def cell_rows(self, pattern: str = None, **subs) -> dict[str, list[Cell]]: 140 columns = [] 141 for domain, cols in self._domains_x(**subs).items(): 142 if pattern is None or re.search(pattern, domain, re.IGNORECASE): 143 columns.append((domain, cols)) 144 for y in range(self._hrows, self.rows): 145 values = defaultdict(list) 146 for domain, cols in columns: 147 values[domain].extend(self.cell(c, y) for c in cols) 148 yield ReDict(values) 149 150 def cell(self, x: int, y: int) -> Cell: 151 assert x < self.columns 152 assert y < self.rows 153 return self._grid[y][x] 154 155 @property 156 def columns(self) -> int: 157 return self._size[0] 158 159 @property 160 def rows(self) -> int: 161 return self._size[1] 162 163 @property 164 def size(self) -> tuple[int,int]: 165 return self._size 166 167 def cell(self, x, y) -> Cell: 168 return self._grid[y][x] 169 170 def _normalize(self): 171 xsize = sum(c._span[0] for c in self._cells if c._pos[0][1] == 0) 172 ysize = max(c._pos[0][1] + c._span[1] for c in self._cells) 173 self._size = (xsize, ysize) 174 self._grid = [[None for _ in range(xsize)] for _ in range(ysize)] 175 176 xpos, ypos = 0, 0 177 for cell in self._cells: 178 # print(cell._pos, cell._span, cell._data) 179 ypos = cell._pos[0][1] 180 if cell._head: 181 self._hrows = ypos + 1 182 cell._pos = [] 183 # Previous cells with multiple rows may already have taken our current xpos 184 # We must find the next cell that's still empty 185 xpos = next((x for x, c in enumerate(self._grid[ypos]) if c is None), xpos) 186 for y in range(ypos, min(ypos + cell._span[1], ysize)): 187 for x in range(xpos, min(xpos + cell._span[0], xsize)): 188 self._grid[y][x] = cell 189 cell._pos.append((x, y)) 190 xpos += cell._span[0] 191 192 def render(self): 193 for y in range(self.rows): 194 for x in range(self.columns): 195 print(self.cell(x, y).text()[:15] if self.cell(x, y) is not None else "None", end="\t") 196 print()
130 def domains(self, pattern: str, **subs) -> Domains: 131 domains = [] 132 columns = [] 133 for domain, cols in self._domains_x(**subs).items(): 134 if re.search(pattern, domain, re.IGNORECASE): 135 domains.append(domain) 136 columns.extend(cols) 137 return Domains(self, domains, columns, pattern)
139 def cell_rows(self, pattern: str = None, **subs) -> dict[str, list[Cell]]: 140 columns = [] 141 for domain, cols in self._domains_x(**subs).items(): 142 if pattern is None or re.search(pattern, domain, re.IGNORECASE): 143 columns.append((domain, cols)) 144 for y in range(self._hrows, self.rows): 145 values = defaultdict(list) 146 for domain, cols in columns: 147 values[domain].extend(self.cell(c, y) for c in cols) 148 yield ReDict(values)