Coverage for phml\utilities\locate\find.py: 100%
79 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-05 15:06 -0500
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-05 15:06 -0500
1"""phml.utilities.locate.find
3Collection of utility methods to find one or many of a specific node.
4"""
6from phml.nodes import Node, Parent
7from phml.utilities.travel.travel import path, walk
8from phml.utilities.validate import Test, check
10__all__ = [
11 "ancestor",
12 "find",
13 "find_all",
14 "find_after",
15 "find_all_after",
16 "find_all_before",
17 "find_before",
18 "find_all_between",
19]
22def ancestor(*nodes: Node) -> Node | None:
23 """Get the common ancestor between two nodes.
25 Args:
26 *nodes (Node): A list of any number of nodes
27 to find the common ancestor form. Worst case it will
28 return the root.
30 Returns:
31 Optional[Node]: The node that is the common
32 ancestor or None if not found.
33 """
34 total_path: list | None = None
36 def filter_func(node, total_path) -> bool:
37 return node in total_path
39 for node in nodes:
40 if total_path is not None:
41 total_path = list(filter(lambda n: filter_func(n, total_path), path(node)))
42 else:
43 total_path = path(node)
45 total_path = total_path or []
46 return total_path[-1] if len(total_path) > 0 else None
49def find(start: Parent, condition: Test, strict: bool = True) -> Node | None:
50 """Walk the nodes children and return the desired node.
52 Returns the first node that matches the condition.
54 Args:
55 start (Parent): Starting node.
56 condition (Test): Condition to check against each node.
58 Returns:
59 Optional[Node]: Returns the found node or None if not found.
60 """
61 for node in walk(start):
62 if check(node, condition, strict=strict):
63 return node
65 return None
68def find_all(start: Parent, condition: Test, strict: bool = True) -> list[Node]:
69 """Find all nodes that match the condition.
71 Args:
72 start (Root | Element): Starting node.
73 condition (Test): Condition to apply to each node.
75 Returns:
76 list[Node]: List of found nodes. Empty if no nodes are found.
77 """
78 results = []
79 for node in walk(start):
80 if check(node, condition, strict=strict):
81 results.append(node)
82 return results
85def find_after(
86 start: Node,
87 condition: Test | None = None,
88 strict: bool = True,
89) -> Node | None:
90 """Get the first sibling node following the provided node that matches
91 the condition.
93 Args:
94 start (Node): Node to get sibling from.
95 condition (Test): Condition to check against each node.
97 Returns:
98 Optional[Node]: Returns the first sibling or None if there
99 are no siblings.
100 """
102 if start.parent is not None:
103 idx = start.parent.index(start)
104 if len(start.parent) - 1 > idx:
105 for node in start.parent[idx + 1 :]:
106 if condition is not None:
107 if check(node, condition, strict=strict):
108 return node
109 else:
110 return node
111 return None
114def find_all_after(
115 start: Node,
116 condition: Test | None = None,
117 strict: bool = True,
118) -> list[Node]:
119 """Get all sibling nodes that match the condition.
121 Args:
122 start (Node): Node to get siblings from.
123 condition (Test): Condition to check against each node.
125 Returns:
126 list[Node]: Returns the all siblings that match the
127 condition or an empty list if none were found.
128 """
129 if start.parent is None:
130 return []
132 idx = start.parent.index(start)
133 matches = []
135 if len(start.parent) - 1 > idx:
136 for node in start.parent[idx + 1 :]:
137 if condition is not None:
138 if check(node, condition, strict=strict):
139 matches.append(node)
140 else:
141 matches.append(node)
143 return matches
146def find_before(
147 start: Node,
148 condition: Test | None = None,
149 strict: bool = True,
150) -> Node | None:
151 """Find the first sibling node before the given node. If a condition is applied
152 then it will be the first sibling node that passes that condition.
154 Args:
155 start (Node): The node to find the previous sibling from.
156 condition (Optional[Test]): The test that is applied to each node.
158 Returns:
159 Optional[Node]: The first node before the given node
160 or None if no prior siblings.
161 """
163 if start.parent is not None:
164 idx = start.parent.index(start)
165 if idx > 0:
166 for node in start.parent[idx - 1 :: -1]:
167 if condition is not None:
168 if check(node, condition, strict=strict):
169 return node
170 else:
171 return node
172 return None
175def find_all_before(
176 start: Node,
177 condition: Test | None = None,
178 strict: bool = True,
179) -> list[Node]:
180 """Find all nodes that come before the given node.
182 Args:
183 start (Node): The node to find all previous siblings from.
184 condition (Optional[Test]): The condition to apply to each node.
186 Returns:
187 list[Node]: A list of nodes that come before the given node.
188 Empty list if no nodes were found.
189 """
190 if start.parent is None:
191 return []
193 idx = start.parent.index(start)
194 matches = []
196 if idx > 0:
197 for node in start.parent[:idx]:
198 if condition is not None:
199 if check(node, condition, strict=strict):
200 matches.append(node)
201 else:
202 matches.append(node)
203 return matches
206def find_all_between(
207 parent: Parent,
208 segment: tuple[int, int | None] = (0, None),
209 condition: Test | None = None,
210 strict: bool = True,
211) -> list[Node]:
212 """Find all sibling nodes in parent that meet the provided condition from start index
213 to end index.
215 Args:
216 parent (Parent): The parent element to get nodes from.
217 start (int, optional): The starting index, inclusive. Defaults to 0.
218 end (int, optional): The ending index, exclusive. Defaults to 0.
219 condition (Test, optional): Condition to apply to each node. Defaults to None.
221 Returns:
222 list[Node]: List of all matching nodes or an empty list if none were found.
223 """
224 _range = slice(segment[0], segment[1] or len(parent))
226 results = []
227 if _range.start < len(parent) and _range.stop <= len(parent):
228 for node in parent[_range]:
229 if condition is not None:
230 if check(node, condition, strict=strict):
231 results.append(node)
232 else:
233 results.append(node)
234 return results