-
-
Notifications
You must be signed in to change notification settings - Fork 329
Expand file tree
/
Copy pathselect.py
More file actions
106 lines (80 loc) · 2.71 KB
/
select.py
File metadata and controls
106 lines (80 loc) · 2.71 KB
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
from __future__ import annotations
from collections.abc import Callable, Iterator, Sequence
from dataclasses import dataclass
from reactpy.types import VdomJson
Selector = Callable[[VdomJson, "ElementInfo"], bool]
def id_equals(id: str) -> Selector:
return lambda element, _: element.get("attributes", {}).get("id") == id
def class_equals(class_name: str) -> Selector:
return (
lambda element, _: class_name
in element.get("attributes", {}).get("class", "").split()
)
def text_equals(text: str) -> Selector:
return lambda element, _: _element_text(element) == text
def _element_text(element: VdomJson) -> str:
if isinstance(element, str):
return element
return "".join(_element_text(child) for child in element.get("children", []))
def element_exists(element: VdomJson, selector: Selector) -> bool:
return next(find_elements(element, selector), None) is not None
def find_element(
element: VdomJson,
selector: Selector,
*,
first: bool = False,
) -> tuple[VdomJson, ElementInfo]:
"""Find an element by a selector.
Parameters:
element:
The tree to search.
selector:
A function that returns True if the element matches.
first:
If True, return the first element found. If False, raise an error if
multiple elements are found.
Returns:
Element info, or None if not found.
"""
find_iter = find_elements(element, selector)
found = next(find_iter, None)
if found is None:
raise ValueError("Element not found")
if not first:
try:
next(find_iter)
raise ValueError("Multiple elements found")
except StopIteration:
pass
return found
def find_elements(
element: VdomJson, selector: Selector
) -> Iterator[tuple[VdomJson, ElementInfo]]:
"""Find an element by a selector.
Parameters:
element:
The tree to search.
selector:
A function that returns True if the element matches.
Returns:
Element info, or None if not found.
"""
return _find_elements(element, selector, (), ())
def _find_elements(
element: VdomJson,
selector: Selector,
parents: Sequence[VdomJson],
path: Sequence[int],
) -> tuple[VdomJson, ElementInfo] | None:
info = ElementInfo(parents, path)
if selector(element, info):
yield element, info
for index, child in enumerate(element.get("children", [])):
if isinstance(child, dict):
yield from _find_elements(
child, selector, (*parents, element), (*path, index)
)
@dataclass
class ElementInfo:
parents: Sequence[VdomJson]
path: Sequence[int]