You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

237 lines
7.0 KiB

"""Node Searching."""
from anytree.iterators import PreOrderIter
def findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None):
"""
Search nodes matching `filter_` but stop at `maxlevel` or `stop`.
Return tuple with matching nodes.
Args:
node: top node, start searching.
Keyword Args:
filter_: function called with every `node` as argument, `node` is returned if `True`.
stop: stop iteration at `node` if `stop` function returns `True` for `node`.
maxlevel (int): maximum decending in the node hierarchy.
mincount (int): minimum number of nodes.
maxcount (int): maximum number of nodes.
Example tree:
>>> from anytree import Node, RenderTree, AsciiStyle
>>> f = Node("f")
>>> b = Node("b", parent=f)
>>> a = Node("a", parent=b)
>>> d = Node("d", parent=b)
>>> c = Node("c", parent=d)
>>> e = Node("e", parent=d)
>>> g = Node("g", parent=f)
>>> i = Node("i", parent=g)
>>> h = Node("h", parent=i)
>>> print(RenderTree(f, style=AsciiStyle()).by_attr())
f
|-- b
| |-- a
| +-- d
| |-- c
| +-- e
+-- g
+-- i
+-- h
>>> findall(f, filter_=lambda node: node.name in ("a", "b"))
(Node('/f/b'), Node('/f/b/a'))
>>> findall(f, filter_=lambda node: d in node.path)
(Node('/f/b/d'), Node('/f/b/d/c'), Node('/f/b/d/e'))
The number of matches can be limited:
>>> findall(f, filter_=lambda node: d in node.path, mincount=4) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
anytree.search.CountError: Expecting at least 4 elements, but found 3. ... Node('/f/b/d/e'))
>>> findall(f, filter_=lambda node: d in node.path, maxcount=2) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
anytree.search.CountError: Expecting 2 elements at maximum, but found 3. ... Node('/f/b/d/e'))
"""
return _findall(node, filter_=filter_, stop=stop,
maxlevel=maxlevel, mincount=mincount, maxcount=maxcount)
def findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None):
"""
Search nodes with attribute `name` having `value` but stop at `maxlevel`.
Return tuple with matching nodes.
Args:
node: top node, start searching.
value: value which need to match
Keyword Args:
name (str): attribute name need to match
maxlevel (int): maximum decending in the node hierarchy.
mincount (int): minimum number of nodes.
maxcount (int): maximum number of nodes.
Example tree:
>>> from anytree import Node, RenderTree, AsciiStyle
>>> f = Node("f")
>>> b = Node("b", parent=f)
>>> a = Node("a", parent=b)
>>> d = Node("d", parent=b)
>>> c = Node("c", parent=d)
>>> e = Node("e", parent=d)
>>> g = Node("g", parent=f)
>>> i = Node("i", parent=g)
>>> h = Node("h", parent=i)
>>> print(RenderTree(f, style=AsciiStyle()).by_attr())
f
|-- b
| |-- a
| +-- d
| |-- c
| +-- e
+-- g
+-- i
+-- h
>>> findall_by_attr(f, "d")
(Node('/f/b/d'),)
"""
return _findall(node, filter_=lambda n: _filter_by_name(n, name, value),
maxlevel=maxlevel, mincount=mincount, maxcount=maxcount)
def find(node, filter_=None, stop=None, maxlevel=None):
"""
Search for *single* node matching `filter_` but stop at `maxlevel` or `stop`.
Return matching node.
Args:
node: top node, start searching.
Keyword Args:
filter_: function called with every `node` as argument, `node` is returned if `True`.
stop: stop iteration at `node` if `stop` function returns `True` for `node`.
maxlevel (int): maximum decending in the node hierarchy.
Example tree:
>>> from anytree import Node, RenderTree, AsciiStyle
>>> f = Node("f")
>>> b = Node("b", parent=f)
>>> a = Node("a", parent=b)
>>> d = Node("d", parent=b)
>>> c = Node("c", parent=d)
>>> e = Node("e", parent=d)
>>> g = Node("g", parent=f)
>>> i = Node("i", parent=g)
>>> h = Node("h", parent=i)
>>> print(RenderTree(f, style=AsciiStyle()).by_attr())
f
|-- b
| |-- a
| +-- d
| |-- c
| +-- e
+-- g
+-- i
+-- h
>>> find(f, lambda node: node.name == "d")
Node('/f/b/d')
>>> find(f, lambda node: node.name == "z")
>>> find(f, lambda node: b in node.path) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
anytree.search.CountError: Expecting 1 elements at maximum, but found 5. (Node('/f/b')... Node('/f/b/d/e'))
"""
return _find(node, filter_=filter_, stop=stop, maxlevel=maxlevel)
def find_by_attr(node, value, name="name", maxlevel=None):
"""
Search for *single* node with attribute `name` having `value` but stop at `maxlevel`.
Return tuple with matching nodes.
Args:
node: top node, start searching.
value: value which need to match
Keyword Args:
name (str): attribute name need to match
maxlevel (int): maximum decending in the node hierarchy.
Example tree:
>>> from anytree import Node, RenderTree, AsciiStyle
>>> f = Node("f")
>>> b = Node("b", parent=f)
>>> a = Node("a", parent=b)
>>> d = Node("d", parent=b)
>>> c = Node("c", parent=d, foo=4)
>>> e = Node("e", parent=d)
>>> g = Node("g", parent=f)
>>> i = Node("i", parent=g)
>>> h = Node("h", parent=i)
>>> print(RenderTree(f, style=AsciiStyle()).by_attr())
f
|-- b
| |-- a
| +-- d
| |-- c
| +-- e
+-- g
+-- i
+-- h
>>> find_by_attr(f, "d")
Node('/f/b/d')
>>> find_by_attr(f, name="foo", value=4)
Node('/f/b/d/c', foo=4)
>>> find_by_attr(f, name="foo", value=8)
"""
return _find(node, filter_=lambda n: _filter_by_name(n, name, value),
maxlevel=maxlevel)
def _find(node, filter_, stop=None, maxlevel=None):
items = _findall(node, filter_, stop=stop, maxlevel=maxlevel, maxcount=1)
return items[0] if items else None
def _findall(node, filter_, stop=None, maxlevel=None, mincount=None, maxcount=None):
result = tuple(PreOrderIter(node, filter_, stop, maxlevel))
resultlen = len(result)
if mincount is not None and resultlen < mincount:
msg = "Expecting at least %d elements, but found %d."
raise CountError(msg % (mincount, resultlen), result)
if maxcount is not None and resultlen > maxcount:
msg = "Expecting %d elements at maximum, but found %d."
raise CountError(msg % (maxcount, resultlen), result)
return result
def _filter_by_name(node, name, value):
try:
return getattr(node, name) == value
except AttributeError:
return False
class CountError(RuntimeError):
def __init__(self, msg, result):
"""Error raised on `mincount` or `maxcount` mismatch."""
if result:
msg += " " + repr(result)
super(CountError, self).__init__(msg)