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

1"""phml.utilities.locate.find 

2 

3Collection of utility methods to find one or many of a specific node. 

4""" 

5 

6from phml.nodes import Node, Parent 

7from phml.utilities.travel.travel import path, walk 

8from phml.utilities.validate import Test, check 

9 

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] 

20 

21 

22def ancestor(*nodes: Node) -> Node | None: 

23 """Get the common ancestor between two nodes. 

24 

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. 

29 

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 

35 

36 def filter_func(node, total_path) -> bool: 

37 return node in total_path 

38 

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) 

44 

45 total_path = total_path or [] 

46 return total_path[-1] if len(total_path) > 0 else None 

47 

48 

49def find(start: Parent, condition: Test, strict: bool = True) -> Node | None: 

50 """Walk the nodes children and return the desired node. 

51 

52 Returns the first node that matches the condition. 

53 

54 Args: 

55 start (Parent): Starting node. 

56 condition (Test): Condition to check against each node. 

57 

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 

64 

65 return None 

66 

67 

68def find_all(start: Parent, condition: Test, strict: bool = True) -> list[Node]: 

69 """Find all nodes that match the condition. 

70 

71 Args: 

72 start (Root | Element): Starting node. 

73 condition (Test): Condition to apply to each node. 

74 

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 

83 

84 

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. 

92 

93 Args: 

94 start (Node): Node to get sibling from. 

95 condition (Test): Condition to check against each node. 

96 

97 Returns: 

98 Optional[Node]: Returns the first sibling or None if there 

99 are no siblings. 

100 """ 

101 

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 

112 

113 

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. 

120 

121 Args: 

122 start (Node): Node to get siblings from. 

123 condition (Test): Condition to check against each node. 

124 

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 [] 

131 

132 idx = start.parent.index(start) 

133 matches = [] 

134 

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) 

142 

143 return matches 

144 

145 

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. 

153 

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. 

157 

158 Returns: 

159 Optional[Node]: The first node before the given node 

160 or None if no prior siblings. 

161 """ 

162 

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 

173 

174 

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. 

181 

182 Args: 

183 start (Node): The node to find all previous siblings from. 

184 condition (Optional[Test]): The condition to apply to each node. 

185 

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 [] 

192 

193 idx = start.parent.index(start) 

194 matches = [] 

195 

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 

204 

205 

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. 

214 

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. 

220 

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)) 

225 

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