| """ | |
| 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 matching node. | |
| 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) | |