phml.utils.locate.find

phml.utils.locate.find

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

  1"""phml.utils.locate.find
  2
  3Collection of utility methods to find one or many of a specific node.
  4"""
  5
  6from typing import Optional
  7
  8from phml.nodes import AST, All_Nodes, Element, Root
  9from phml.utils.travel import path, walk
 10from phml.utils.validate import Test, test
 11
 12__all__ = [
 13    "ancestor",
 14    "find",
 15    "find_all",
 16    "find_after",
 17    "find_all_after",
 18    "find_all_before",
 19    "find_before",
 20    "find_all_between",
 21]
 22
 23
 24def ancestor(*nodes: All_Nodes) -> Optional[All_Nodes]:
 25    """Get the common ancestor between two nodes.
 26
 27    Args:
 28        *nodes (All_Nodes): A list of any number of nodes
 29        to find the common ancestor form. Worst case it will
 30        return the root.
 31
 32    Returns:
 33        Optional[All_Nodes]: The node that is the common
 34        ancestor or None if not found.
 35    """
 36    total_path: list = None
 37
 38    def filter_func(node, total_path) -> bool:
 39        return node in total_path
 40
 41    for node in nodes:
 42        if total_path is not None:
 43            total_path = list(filter(lambda n: filter_func(n, total_path), path(node)))
 44        else:
 45            total_path = path(node)
 46
 47    return total_path[-1] if len(total_path) > 0 else None
 48
 49
 50def find(start: Root | Element | AST, condition: Test) -> Optional[All_Nodes]:
 51    """Walk the nodes children and return the desired node.
 52
 53    Returns the first node that matches the condition.
 54
 55    Args:
 56        start (Root | Element): Starting node.
 57        condition (Test): Condition to check against each node.
 58
 59    Returns:
 60        Optional[All_Nodes]: Returns the found node or None if not found.
 61    """
 62    if isinstance(start, AST):
 63        start = start.tree
 64
 65    for node in walk(start):
 66        if test(node, condition):
 67            return node
 68
 69    return None
 70
 71
 72def find_all(start: Root | Element | AST, condition: Test) -> list[All_Nodes]:
 73    """Find all nodes that match the condition.
 74
 75    Args:
 76        start (Root | Element): Starting node.
 77        condition (Test): Condition to apply to each node.
 78
 79    Returns:
 80        list[All_Nodes]: List of found nodes. Empty if no nodes are found.
 81    """
 82    if isinstance(start, AST):
 83        start = start.tree
 84
 85    results = []
 86    for node in walk(start):
 87        if test(node, condition):
 88            results.append(node)
 89    return results
 90
 91
 92def find_after(
 93    start: Root | Element | AST,
 94    condition: Optional[Test] = None,
 95) -> Optional[All_Nodes]:
 96    """Get the first sibling node following the provided node that matches
 97    the condition.
 98
 99    Args:
100        start (All_Nodes): Node to get sibling from.
101        condition (Test): Condition to check against each node.
102
103    Returns:
104        Optional[All_Nodes]: Returns the first sibling or None if there
105        are no siblings.
106    """
107    if isinstance(start, AST):
108        start = start.tree
109
110    idx = start.parent.children.index(start)
111    if len(start.parent.children) - 1 > idx:
112        for node in start.parent.children[idx + 1 :]:
113            if condition is not None:
114                if test(node, condition):
115                    return node
116            else:
117                return node
118    return None
119
120
121def find_all_after(
122    start: Element,
123    condition: Optional[Test] = None,
124) -> list[All_Nodes]:
125    """Get all sibling nodes that match the condition.
126
127    Args:
128        start (All_Nodes): Node to get siblings from.
129        condition (Test): Condition to check against each node.
130
131    Returns:
132        list[All_Nodes]: Returns the all siblings that match the
133        condition or an empty list if none were found.
134    """
135    idx = start.parent.children.index(start)
136    matches = []
137
138    if len(start.parent.children) - 1 > idx:
139        for node in start.parent.children[idx + 1 :]:
140            if condition is not None:
141                if test(node, condition):
142                    matches.append(node)
143            else:
144                matches.append(node)
145
146    return matches
147
148
149def find_before(
150    start: Element,
151    condition: Optional[Test] = None,
152) -> Optional[All_Nodes]:
153    """Find the first sibling node before the given node. If a condition is applied
154    then it will be the first sibling node that passes that condition.
155
156    Args:
157        start (All_Nodes): The node to find the previous sibling from.
158        condition (Optional[Test]): The test that is applied to each node.
159
160    Returns:
161        Optional[All_Nodes]: The first node before the given node
162        or None if no prior siblings.
163    """
164    if isinstance(start, AST):
165        start = start.tree
166
167    idx = start.parent.children.index(start)
168    if idx > 0:
169        for node in start.parent.children[idx - 1 :: -1]:
170            if condition is not None:
171                if test(node, condition):
172                    return node
173            else:
174                return node
175    return None
176
177
178def find_all_before(
179    start: Element,
180    condition: Optional[Test] = None,
181) -> list[All_Nodes]:
182    """Find all nodes that come before the given node.
183
184    Args:
185        start (All_Nodes): The node to find all previous siblings from.
186        condition (Optional[Test]): The condition to apply to each node.
187
188    Returns:
189        list[All_Nodes]: A list of nodes that come before the given node.
190        Empty list if no nodes were found.
191    """
192    idx = start.parent.children.index(start)
193    matches = []
194
195    if idx > 0:
196        for node in start.parent.children[:idx]:
197            if condition is not None:
198                if test(node, condition):
199                    matches.append(node)
200            else:
201                matches.append(node)
202    return matches
203
204
205def find_all_between(
206    parent: Root | Element | AST,
207    start: Optional[int] = 0,
208    end: Optional[int] = 0,
209    condition: Optional[Test] = None,
210    _range: Optional[slice] = None,
211) -> list[All_Nodes]:
212    """Find all sibling nodes in parent that meet the provided condition from start index
213    to end index.
214
215    Args:
216        parent (Root | Element): 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        _range (slice, optional): Slice to apply to the parent nodes children instead of start and
221        end indecies. Defaults to None.
222
223    Returns:
224        list[All_Nodes]: List of all matching nodes or an empty list if none were found.
225    """
226    if isinstance(parent, AST):
227        parent = parent.tree
228
229    if _range is not None:
230        start = _range.start
231        end = _range.stop
232
233    results = []
234    if start in range(0, end) and end in range(start, len(parent.children)):
235        for node in parent.children[start:end]:
236            if condition is not None:
237                if test(node, condition):
238                    results.append(node)
239            else:
240                results.append(node)
241    return results
def ancestor( *nodes: phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal) -> Union[phml.nodes.root.Root, phml.nodes.element.Element, phml.nodes.text.Text, phml.nodes.comment.Comment, phml.nodes.doctype.DocType, phml.nodes.parent.Parent, phml.nodes.node.Node, phml.nodes.literal.Literal, NoneType]:
25def ancestor(*nodes: All_Nodes) -> Optional[All_Nodes]:
26    """Get the common ancestor between two nodes.
27
28    Args:
29        *nodes (All_Nodes): A list of any number of nodes
30        to find the common ancestor form. Worst case it will
31        return the root.
32
33    Returns:
34        Optional[All_Nodes]: The node that is the common
35        ancestor or None if not found.
36    """
37    total_path: list = None
38
39    def filter_func(node, total_path) -> bool:
40        return node in total_path
41
42    for node in nodes:
43        if total_path is not None:
44            total_path = list(filter(lambda n: filter_func(n, total_path), path(node)))
45        else:
46            total_path = path(node)
47
48    return total_path[-1] if len(total_path) > 0 else None

Get the common ancestor between two nodes.

Args
  • *nodes (All_Nodes): A list of any number of nodes
  • to find the common ancestor form. Worst case it will
  • return the root.
Returns

Optional[All_Nodes]: The node that is the common ancestor or None if not found.

def find( start: phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.AST.AST, condition: Union[NoneType, str, list, dict, Callable]) -> Union[phml.nodes.root.Root, phml.nodes.element.Element, phml.nodes.text.Text, phml.nodes.comment.Comment, phml.nodes.doctype.DocType, phml.nodes.parent.Parent, phml.nodes.node.Node, phml.nodes.literal.Literal, NoneType]:
51def find(start: Root | Element | AST, condition: Test) -> Optional[All_Nodes]:
52    """Walk the nodes children and return the desired node.
53
54    Returns the first node that matches the condition.
55
56    Args:
57        start (Root | Element): Starting node.
58        condition (Test): Condition to check against each node.
59
60    Returns:
61        Optional[All_Nodes]: Returns the found node or None if not found.
62    """
63    if isinstance(start, AST):
64        start = start.tree
65
66    for node in walk(start):
67        if test(node, condition):
68            return node
69
70    return None

Walk the nodes children and return the desired node.

Returns the first node that matches the condition.

Args
  • start (Root | Element): Starting node.
  • condition (Test): Condition to check against each node.
Returns

Optional[All_Nodes]: Returns the found node or None if not found.

def find_all( start: phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.AST.AST, condition: Union[NoneType, str, list, dict, Callable]) -> list[phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal]:
73def find_all(start: Root | Element | AST, condition: Test) -> list[All_Nodes]:
74    """Find all nodes that match the condition.
75
76    Args:
77        start (Root | Element): Starting node.
78        condition (Test): Condition to apply to each node.
79
80    Returns:
81        list[All_Nodes]: List of found nodes. Empty if no nodes are found.
82    """
83    if isinstance(start, AST):
84        start = start.tree
85
86    results = []
87    for node in walk(start):
88        if test(node, condition):
89            results.append(node)
90    return results

Find all nodes that match the condition.

Args
  • start (Root | Element): Starting node.
  • condition (Test): Condition to apply to each node.
Returns

list[All_Nodes]: List of found nodes. Empty if no nodes are found.

def find_after( start: phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.AST.AST, condition: Union[NoneType, str, list, dict, Callable] = None) -> Union[phml.nodes.root.Root, phml.nodes.element.Element, phml.nodes.text.Text, phml.nodes.comment.Comment, phml.nodes.doctype.DocType, phml.nodes.parent.Parent, phml.nodes.node.Node, phml.nodes.literal.Literal, NoneType]:
 93def find_after(
 94    start: Root | Element | AST,
 95    condition: Optional[Test] = None,
 96) -> Optional[All_Nodes]:
 97    """Get the first sibling node following the provided node that matches
 98    the condition.
 99
100    Args:
101        start (All_Nodes): Node to get sibling from.
102        condition (Test): Condition to check against each node.
103
104    Returns:
105        Optional[All_Nodes]: Returns the first sibling or None if there
106        are no siblings.
107    """
108    if isinstance(start, AST):
109        start = start.tree
110
111    idx = start.parent.children.index(start)
112    if len(start.parent.children) - 1 > idx:
113        for node in start.parent.children[idx + 1 :]:
114            if condition is not None:
115                if test(node, condition):
116                    return node
117            else:
118                return node
119    return None

Get the first sibling node following the provided node that matches the condition.

Args
  • start (All_Nodes): Node to get sibling from.
  • condition (Test): Condition to check against each node.
Returns

Optional[All_Nodes]: Returns the first sibling or None if there are no siblings.

def find_all_after( start: phml.nodes.element.Element, condition: Union[NoneType, str, list, dict, Callable] = None) -> list[phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal]:
122def find_all_after(
123    start: Element,
124    condition: Optional[Test] = None,
125) -> list[All_Nodes]:
126    """Get all sibling nodes that match the condition.
127
128    Args:
129        start (All_Nodes): Node to get siblings from.
130        condition (Test): Condition to check against each node.
131
132    Returns:
133        list[All_Nodes]: Returns the all siblings that match the
134        condition or an empty list if none were found.
135    """
136    idx = start.parent.children.index(start)
137    matches = []
138
139    if len(start.parent.children) - 1 > idx:
140        for node in start.parent.children[idx + 1 :]:
141            if condition is not None:
142                if test(node, condition):
143                    matches.append(node)
144            else:
145                matches.append(node)
146
147    return matches

Get all sibling nodes that match the condition.

Args
  • start (All_Nodes): Node to get siblings from.
  • condition (Test): Condition to check against each node.
Returns

list[All_Nodes]: Returns the all siblings that match the condition or an empty list if none were found.

def find_all_before( start: phml.nodes.element.Element, condition: Union[NoneType, str, list, dict, Callable] = None) -> list[phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal]:
179def find_all_before(
180    start: Element,
181    condition: Optional[Test] = None,
182) -> list[All_Nodes]:
183    """Find all nodes that come before the given node.
184
185    Args:
186        start (All_Nodes): The node to find all previous siblings from.
187        condition (Optional[Test]): The condition to apply to each node.
188
189    Returns:
190        list[All_Nodes]: A list of nodes that come before the given node.
191        Empty list if no nodes were found.
192    """
193    idx = start.parent.children.index(start)
194    matches = []
195
196    if idx > 0:
197        for node in start.parent.children[:idx]:
198            if condition is not None:
199                if test(node, condition):
200                    matches.append(node)
201            else:
202                matches.append(node)
203    return matches

Find all nodes that come before the given node.

Args
  • start (All_Nodes): The node to find all previous siblings from.
  • condition (Optional[Test]): The condition to apply to each node.
Returns

list[All_Nodes]: A list of nodes that come before the given node. Empty list if no nodes were found.

def find_before( start: phml.nodes.element.Element, condition: Union[NoneType, str, list, dict, Callable] = None) -> Union[phml.nodes.root.Root, phml.nodes.element.Element, phml.nodes.text.Text, phml.nodes.comment.Comment, phml.nodes.doctype.DocType, phml.nodes.parent.Parent, phml.nodes.node.Node, phml.nodes.literal.Literal, NoneType]:
150def find_before(
151    start: Element,
152    condition: Optional[Test] = None,
153) -> Optional[All_Nodes]:
154    """Find the first sibling node before the given node. If a condition is applied
155    then it will be the first sibling node that passes that condition.
156
157    Args:
158        start (All_Nodes): The node to find the previous sibling from.
159        condition (Optional[Test]): The test that is applied to each node.
160
161    Returns:
162        Optional[All_Nodes]: The first node before the given node
163        or None if no prior siblings.
164    """
165    if isinstance(start, AST):
166        start = start.tree
167
168    idx = start.parent.children.index(start)
169    if idx > 0:
170        for node in start.parent.children[idx - 1 :: -1]:
171            if condition is not None:
172                if test(node, condition):
173                    return node
174            else:
175                return node
176    return None

Find the first sibling node before the given node. If a condition is applied then it will be the first sibling node that passes that condition.

Args
  • start (All_Nodes): The node to find the previous sibling from.
  • condition (Optional[Test]): The test that is applied to each node.
Returns

Optional[All_Nodes]: The first node before the given node or None if no prior siblings.

def find_all_between( parent: phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.AST.AST, start: Optional[int] = 0, end: Optional[int] = 0, condition: Union[NoneType, str, list, dict, Callable] = None, _range: Optional[slice] = None) -> list[phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal]:
206def find_all_between(
207    parent: Root | Element | AST,
208    start: Optional[int] = 0,
209    end: Optional[int] = 0,
210    condition: Optional[Test] = None,
211    _range: Optional[slice] = None,
212) -> list[All_Nodes]:
213    """Find all sibling nodes in parent that meet the provided condition from start index
214    to end index.
215
216    Args:
217        parent (Root | Element): The parent element to get nodes from.
218        start (int, optional): The starting index, inclusive. Defaults to 0.
219        end (int, optional): The ending index, exclusive. Defaults to 0.
220        condition (Test, optional): Condition to apply to each node. Defaults to None.
221        _range (slice, optional): Slice to apply to the parent nodes children instead of start and
222        end indecies. Defaults to None.
223
224    Returns:
225        list[All_Nodes]: List of all matching nodes or an empty list if none were found.
226    """
227    if isinstance(parent, AST):
228        parent = parent.tree
229
230    if _range is not None:
231        start = _range.start
232        end = _range.stop
233
234    results = []
235    if start in range(0, end) and end in range(start, len(parent.children)):
236        for node in parent.children[start:end]:
237            if condition is not None:
238                if test(node, condition):
239                    results.append(node)
240            else:
241                results.append(node)
242    return results

Find all sibling nodes in parent that meet the provided condition from start index to end index.

Args
  • parent (Root | Element): The parent element to get nodes from.
  • start (int, optional): The starting index, inclusive. Defaults to 0.
  • end (int, optional): The ending index, exclusive. Defaults to 0.
  • condition (Test, optional): Condition to apply to each node. Defaults to None.
  • _range (slice, optional): Slice to apply to the parent nodes children instead of start and
  • end indecies. Defaults to None.
Returns

list[All_Nodes]: List of all matching nodes or an empty list if none were found.