123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- """
- Node Searching.
- .. note:: You can speed-up node searching, by installing https://pypi.org/project/fastcache/ and
- using :any:`cachedsearch`.
- """
- 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 descending 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 descending 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 descending 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 descending 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)
|