aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/bs4/filter.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/bs4/filter.py')
-rw-r--r--.venv/lib/python3.12/site-packages/bs4/filter.py755
1 files changed, 755 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/bs4/filter.py b/.venv/lib/python3.12/site-packages/bs4/filter.py
new file mode 100644
index 00000000..e3ce2e2f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/bs4/filter.py
@@ -0,0 +1,755 @@
+from __future__ import annotations
+from collections import defaultdict
+import re
+from typing import (
+ Any,
+ Callable,
+ cast,
+ Dict,
+ Iterator,
+ Iterable,
+ List,
+ Optional,
+ Sequence,
+ Type,
+ Union,
+)
+import warnings
+
+from bs4._deprecation import _deprecated
+from bs4.element import (
+ AttributeDict,
+ NavigableString,
+ PageElement,
+ ResultSet,
+ Tag,
+)
+from bs4._typing import (
+ _AtMostOneElement,
+ _AttributeValue,
+ _OneElement,
+ _PageElementMatchFunction,
+ _QueryResults,
+ _RawAttributeValues,
+ _RegularExpressionProtocol,
+ _StrainableAttribute,
+ _StrainableElement,
+ _StrainableString,
+ _StringMatchFunction,
+ _TagMatchFunction,
+)
+
+
+class ElementFilter(object):
+ """`ElementFilter` encapsulates the logic necessary to decide:
+
+ 1. whether a `PageElement` (a `Tag` or a `NavigableString`) matches a
+ user-specified query.
+
+ 2. whether a given sequence of markup found during initial parsing
+ should be turned into a `PageElement` at all, or simply discarded.
+
+ The base class is the simplest `ElementFilter`. By default, it
+ matches everything and allows all markup to become `PageElement`
+ objects. You can make it more selective by passing in a
+ user-defined match function, or defining a subclass.
+
+ Most users of Beautiful Soup will never need to use
+ `ElementFilter`, or its more capable subclass
+ `SoupStrainer`. Instead, they will use methods like
+ :py:meth:`Tag.find`, which will convert their arguments into
+ `SoupStrainer` objects and run them against the tree.
+
+ However, if you find yourself wanting to treat the arguments to
+ Beautiful Soup's find_*() methods as first-class objects, those
+ objects will be `SoupStrainer` objects. You can create them
+ yourself and then make use of functions like
+ `ElementFilter.filter()`.
+ """
+
+ match_function: Optional[_PageElementMatchFunction]
+
+ def __init__(self, match_function: Optional[_PageElementMatchFunction] = None):
+ """Pass in a match function to easily customize the behavior of
+ `ElementFilter.match` without needing to subclass.
+
+ :param match_function: A function that takes a `PageElement`
+ and returns `True` if that `PageElement` matches some criteria.
+ """
+ self.match_function = match_function
+
+ @property
+ def includes_everything(self) -> bool:
+ """Does this `ElementFilter` obviously include everything? If so,
+ the filter process can be made much faster.
+
+ The `ElementFilter` might turn out to include everything even
+ if this returns `False`, but it won't include everything in an
+ obvious way.
+
+ The base `ElementFilter` implementation includes things based on
+ the match function, so includes_everything is only true if
+ there is no match function.
+ """
+ return not self.match_function
+
+ @property
+ def excludes_everything(self) -> bool:
+ """Does this `ElementFilter` obviously exclude everything? If
+ so, Beautiful Soup will issue a warning if you try to use it
+ when parsing a document.
+
+ The `ElementFilter` might turn out to exclude everything even
+ if this returns `False`, but it won't exclude everything in an
+ obvious way.
+
+ The base `ElementFilter` implementation excludes things based
+ on a match function we can't inspect, so excludes_everything
+ is always false.
+ """
+ return False
+
+ def match(self, element: PageElement, _known_rules:bool=False) -> bool:
+ """Does the given PageElement match the rules set down by this
+ ElementFilter?
+
+ The base implementation delegates to the function passed in to
+ the constructor.
+
+ :param _known_rules: Defined for compatibility with
+ SoupStrainer._match(). Used more for consistency than because
+ we need the performance optimization.
+ """
+ if not _known_rules and self.includes_everything:
+ return True
+ if not self.match_function:
+ return True
+ return self.match_function(element)
+
+ def filter(self, generator: Iterator[PageElement]) -> Iterator[_OneElement]:
+ """The most generic search method offered by Beautiful Soup.
+
+ Acts like Python's built-in `filter`, using
+ `ElementFilter.match` as the filtering function.
+ """
+ # If there are no rules at all, don't bother filtering. Let
+ # anything through.
+ if self.includes_everything:
+ for i in generator:
+ yield i
+ while True:
+ try:
+ i = next(generator)
+ except StopIteration:
+ break
+ if i:
+ if self.match(i, _known_rules=True):
+ yield cast("_OneElement", i)
+
+ def find(self, generator: Iterator[PageElement]) -> _AtMostOneElement:
+ """A lower-level equivalent of :py:meth:`Tag.find`.
+
+ You can pass in your own generator for iterating over
+ `PageElement` objects. The first one that matches this
+ `ElementFilter` will be returned.
+
+ :param generator: A way of iterating over `PageElement`
+ objects.
+ """
+ for match in self.filter(generator):
+ return match
+ return None
+
+ def find_all(
+ self, generator: Iterator[PageElement], limit: Optional[int] = None
+ ) -> _QueryResults:
+ """A lower-level equivalent of :py:meth:`Tag.find_all`.
+
+ You can pass in your own generator for iterating over
+ `PageElement` objects. Only elements that match this
+ `ElementFilter` will be returned in the :py:class:`ResultSet`.
+
+ :param generator: A way of iterating over `PageElement`
+ objects.
+
+ :param limit: Stop looking after finding this many results.
+ """
+ results: _QueryResults = ResultSet(self)
+ for match in self.filter(generator):
+ results.append(match)
+ if limit is not None and len(results) >= limit:
+ break
+ return results
+
+ def allow_tag_creation(
+ self, nsprefix: Optional[str], name: str, attrs: Optional[_RawAttributeValues]
+ ) -> bool:
+ """Based on the name and attributes of a tag, see whether this
+ `ElementFilter` will allow a `Tag` object to even be created.
+
+ By default, all tags are parsed. To change this, subclass
+ `ElementFilter`.
+
+ :param name: The name of the prospective tag.
+ :param attrs: The attributes of the prospective tag.
+ """
+ return True
+
+ def allow_string_creation(self, string: str) -> bool:
+ """Based on the content of a string, see whether this
+ `ElementFilter` will allow a `NavigableString` object based on
+ this string to be added to the parse tree.
+
+ By default, all strings are processed into `NavigableString`
+ objects. To change this, subclass `ElementFilter`.
+
+ :param str: The string under consideration.
+ """
+ return True
+
+
+class MatchRule(object):
+ """Each MatchRule encapsulates the logic behind a single argument
+ passed in to one of the Beautiful Soup find* methods.
+ """
+
+ string: Optional[str]
+ pattern: Optional[_RegularExpressionProtocol]
+ present: Optional[bool]
+ exclude_everything: Optional[bool]
+ # TODO-TYPING: All MatchRule objects also have an attribute
+ # ``function``, but the type of the function depends on the
+ # subclass.
+
+ def __init__(
+ self,
+ string: Optional[Union[str, bytes]] = None,
+ pattern: Optional[_RegularExpressionProtocol] = None,
+ function: Optional[Callable] = None,
+ present: Optional[bool] = None,
+ exclude_everything: Optional[bool] = None
+ ):
+ if isinstance(string, bytes):
+ string = string.decode("utf8")
+ self.string = string
+ if isinstance(pattern, bytes):
+ self.pattern = re.compile(pattern.decode("utf8"))
+ elif isinstance(pattern, str):
+ self.pattern = re.compile(pattern)
+ else:
+ self.pattern = pattern
+ self.function = function
+ self.present = present
+ self.exclude_everything = exclude_everything
+
+ values = [
+ x
+ for x in (self.string, self.pattern, self.function, self.present, self.exclude_everything)
+ if x is not None
+ ]
+ if len(values) == 0:
+ raise ValueError(
+ "Either string, pattern, function, present, or exclude_everything must be provided."
+ )
+ if len(values) > 1:
+ raise ValueError(
+ "At most one of string, pattern, function, present, and exclude_everything must be provided."
+ )
+
+ def _base_match(self, string: Optional[str]) -> Optional[bool]:
+ """Run the 'cheap' portion of a match, trying to get an answer without
+ calling a potentially expensive custom function.
+
+ :return: True or False if we have a (positive or negative)
+ match; None if we need to keep trying.
+ """
+ # self.exclude_everything matches nothing.
+ if self.exclude_everything:
+ return False
+
+ # self.present==True matches everything except None.
+ if self.present is True:
+ return string is not None
+
+ # self.present==False matches _only_ None.
+ if self.present is False:
+ return string is None
+
+ # self.string does an exact string match.
+ if self.string is not None:
+ # print(f"{self.string} ?= {string}")
+ return self.string == string
+
+ # self.pattern does a regular expression search.
+ if self.pattern is not None:
+ # print(f"{self.pattern} ?~ {string}")
+ if string is None:
+ return False
+ return self.pattern.search(string) is not None
+
+ return None
+
+ def matches_string(self, string: Optional[str]) -> bool:
+ _base_result = self._base_match(string)
+ if _base_result is not None:
+ # No need to invoke the test function.
+ return _base_result
+ if self.function is not None and not self.function(string):
+ # print(f"{self.function}({string}) == False")
+ return False
+ return True
+
+ def __repr__(self) -> str:
+ cls = type(self).__name__
+ return f"<{cls} string={self.string} pattern={self.pattern} function={self.function} present={self.present}>"
+
+ def __eq__(self, other: Any) -> bool:
+ return (
+ isinstance(other, MatchRule)
+ and self.string == other.string
+ and self.pattern == other.pattern
+ and self.function == other.function
+ and self.present == other.present
+ )
+
+
+class TagNameMatchRule(MatchRule):
+ """A MatchRule implementing the rules for matches against tag name."""
+
+ function: Optional[_TagMatchFunction]
+
+ def matches_tag(self, tag: Tag) -> bool:
+ base_value = self._base_match(tag.name)
+ if base_value is not None:
+ return base_value
+
+ # The only remaining possibility is that the match is determined
+ # by a function call. Call the function.
+ function = cast(_TagMatchFunction, self.function)
+ if function(tag):
+ return True
+ return False
+
+
+class AttributeValueMatchRule(MatchRule):
+ """A MatchRule implementing the rules for matches against attribute value."""
+
+ function: Optional[_StringMatchFunction]
+
+
+class StringMatchRule(MatchRule):
+ """A MatchRule implementing the rules for matches against a NavigableString."""
+
+ function: Optional[_StringMatchFunction]
+
+
+class SoupStrainer(ElementFilter):
+ """The `ElementFilter` subclass used internally by Beautiful Soup.
+
+ A `SoupStrainer` encapsulates the logic necessary to perform the
+ kind of matches supported by methods such as
+ :py:meth:`Tag.find`. `SoupStrainer` objects are primarily created
+ internally, but you can create one yourself and pass it in as
+ ``parse_only`` to the `BeautifulSoup` constructor, to parse a
+ subset of a large document.
+
+ Internally, `SoupStrainer` objects work by converting the
+ constructor arguments into `MatchRule` objects. Incoming
+ tags/markup are matched against those rules.
+
+ :param name: One or more restrictions on the tags found in a document.
+
+ :param attrs: A dictionary that maps attribute names to
+ restrictions on tags that use those attributes.
+
+ :param string: One or more restrictions on the strings found in a
+ document.
+
+ :param kwargs: A dictionary that maps attribute names to restrictions
+ on tags that use those attributes. These restrictions are additive to
+ any specified in ``attrs``.
+
+ """
+
+ name_rules: List[TagNameMatchRule]
+ attribute_rules: Dict[str, List[AttributeValueMatchRule]]
+ string_rules: List[StringMatchRule]
+
+ def __init__(
+ self,
+ name: Optional[_StrainableElement] = None,
+ attrs: Dict[str, _StrainableAttribute] = {},
+ string: Optional[_StrainableString] = None,
+ **kwargs: _StrainableAttribute,
+ ):
+ if string is None and "text" in kwargs:
+ string = cast(Optional[_StrainableString], kwargs.pop("text"))
+ warnings.warn(
+ "As of version 4.11.0, the 'text' argument to the SoupStrainer constructor is deprecated. Use 'string' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ if name is None and not attrs and not string and not kwargs:
+ # Special case for backwards compatibility. Instantiating
+ # a SoupStrainer with no arguments whatsoever gets you one
+ # that matches all Tags, and only Tags.
+ self.name_rules = [TagNameMatchRule(present=True)]
+ else:
+ self.name_rules = cast(
+ List[TagNameMatchRule], list(self._make_match_rules(name, TagNameMatchRule))
+ )
+ self.attribute_rules = defaultdict(list)
+
+ if not isinstance(attrs, dict):
+ # Passing something other than a dictionary as attrs is
+ # sugar for matching that thing against the 'class'
+ # attribute.
+ attrs = {"class": attrs}
+
+ for attrdict in attrs, kwargs:
+ for attr, value in attrdict.items():
+ if attr == "class_" and attrdict is kwargs:
+ # If you pass in 'class_' as part of kwargs, it's
+ # because class is a Python reserved word. If you
+ # pass it in as part of the attrs dict, it's
+ # because you really are looking for an attribute
+ # called 'class_'.
+ attr = "class"
+
+ if value is None:
+ value = False
+ for rule_obj in self._make_match_rules(value, AttributeValueMatchRule):
+ self.attribute_rules[attr].append(
+ cast(AttributeValueMatchRule, rule_obj)
+ )
+
+ self.string_rules = cast(
+ List[StringMatchRule], list(self._make_match_rules(string, StringMatchRule))
+ )
+
+ #: DEPRECATED 4.13.0: You shouldn't need to check this under
+ #: any name (.string or .text), and if you do, you're probably
+ #: not taking into account all of the types of values this
+ #: variable might have. Look at the .string_rules list instead.
+ self.__string = string
+
+ @property
+ def includes_everything(self) -> bool:
+ """Check whether the provided rules will obviously include
+ everything. (They might include everything even if this returns `False`,
+ but not in an obvious way.)
+ """
+ return not self.name_rules and not self.string_rules and not self.attribute_rules
+
+ @property
+ def excludes_everything(self) -> bool:
+ """Check whether the provided rules will obviously exclude
+ everything. (They might exclude everything even if this returns `False`,
+ but not in an obvious way.)
+ """
+ if (self.string_rules and (self.name_rules or self.attribute_rules)):
+ # This is self-contradictory, so the rules exclude everything.
+ return True
+
+ # If there's a rule that ended up treated as an "exclude everything"
+ # rule due to creating a logical inconsistency, then the rules
+ # exclude everything.
+ if any(x.exclude_everything for x in self.string_rules):
+ return True
+ if any(x.exclude_everything for x in self.name_rules):
+ return True
+ for ruleset in self.attribute_rules.values():
+ if any(x.exclude_everything for x in ruleset):
+ return True
+ return False
+
+ @property
+ def string(self) -> Optional[_StrainableString]:
+ ":meta private:"
+ warnings.warn(
+ "Access to deprecated property string. (Look at .string_rules instead) -- Deprecated since version 4.13.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.__string
+
+ @property
+ def text(self) -> Optional[_StrainableString]:
+ ":meta private:"
+ warnings.warn(
+ "Access to deprecated property text. (Look at .string_rules instead) -- Deprecated since version 4.13.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.__string
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} name={self.name_rules} attrs={self.attribute_rules} string={self.string_rules}>"
+
+ @classmethod
+ def _make_match_rules(
+ cls,
+ obj: Optional[Union[_StrainableElement, _StrainableAttribute]],
+ rule_class: Type[MatchRule],
+ ) -> Iterator[MatchRule]:
+ """Convert a vaguely-specific 'object' into one or more well-defined
+ `MatchRule` objects.
+
+ :param obj: Some kind of object that corresponds to one or more
+ matching rules.
+ :param rule_class: Create instances of this `MatchRule` subclass.
+ """
+ if obj is None:
+ return
+ if isinstance(obj, (str, bytes)):
+ yield rule_class(string=obj)
+ elif isinstance(obj, bool):
+ yield rule_class(present=obj)
+ elif callable(obj):
+ yield rule_class(function=obj)
+ elif isinstance(obj, _RegularExpressionProtocol):
+ yield rule_class(pattern=obj)
+ elif hasattr(obj, "__iter__"):
+ if not obj:
+ # The attribute is being matched against the null set,
+ # which means it should exclude everything.
+ yield rule_class(exclude_everything=True)
+ for o in obj:
+ if not isinstance(o, (bytes, str)) and hasattr(o, "__iter__"):
+ # This is almost certainly the user's
+ # mistake. This list contains another list, which
+ # opens up the possibility of infinite
+ # self-reference. In the interests of avoiding
+ # infinite recursion, we'll treat this as an
+ # impossible match and issue a rule that excludes
+ # everything, rather than looking inside.
+ warnings.warn(
+ f"Ignoring nested list {o} to avoid the possibility of infinite recursion.",
+ stacklevel=5,
+ )
+ yield rule_class(exclude_everything=True)
+ continue
+ for x in cls._make_match_rules(o, rule_class):
+ yield x
+ else:
+ yield rule_class(string=str(obj))
+
+ def matches_tag(self, tag: Tag) -> bool:
+ """Do the rules of this `SoupStrainer` trigger a match against the
+ given `Tag`?
+
+ If the `SoupStrainer` has any `TagNameMatchRule`, at least one
+ must match the `Tag` or its `Tag.name`.
+
+ If there are any `AttributeValueMatchRule` for a given
+ attribute, at least one of them must match the attribute
+ value.
+
+ If there are any `StringMatchRule`, at least one must match,
+ but a `SoupStrainer` that *only* contains `StringMatchRule`
+ cannot match a `Tag`, only a `NavigableString`.
+ """
+ # If there are no rules at all, let anything through.
+ #if self.includes_everything:
+ # return True
+
+ # String rules cannot not match a Tag on their own.
+ if not self.name_rules and not self.attribute_rules:
+ return False
+
+ # Optimization for a very common case where the user is
+ # searching for a tag with one specific name, and we're
+ # looking at a tag with a different name.
+ if (
+ not tag.prefix
+ and len(self.name_rules) == 1
+ and self.name_rules[0].string is not None
+ and tag.name != self.name_rules[0].string
+ ):
+ return False
+
+ # If there are name rules, at least one must match. It can
+ # match either the Tag object itself or the prefixed name of
+ # the tag.
+ prefixed_name = None
+ if tag.prefix:
+ prefixed_name = f"{tag.prefix}:{tag.name}"
+ if self.name_rules:
+ name_matches = False
+ for rule in self.name_rules:
+ # attrs = " ".join(
+ # [f"{k}={v}" for k, v in sorted(tag.attrs.items())]
+ # )
+ # print(f"Testing <{tag.name} {attrs}>{tag.string}</{tag.name}> against {rule}")
+ if rule.matches_tag(tag) or (
+ prefixed_name is not None and rule.matches_string(prefixed_name)
+ ):
+ name_matches = True
+ break
+
+ if not name_matches:
+ return False
+
+ # If there are attribute rules for a given attribute, at least
+ # one of them must match. If there are rules for multiple
+ # attributes, each attribute must have at least one match.
+ for attr, rules in self.attribute_rules.items():
+ attr_value = tag.get(attr, None)
+ this_attr_match = self._attribute_match(attr_value, rules)
+ if not this_attr_match:
+ return False
+
+ # If there are string rules, at least one must match.
+ if self.string_rules:
+ _str = tag.string
+ if _str is None:
+ return False
+ if not self.matches_any_string_rule(_str):
+ return False
+ return True
+
+ def _attribute_match(
+ self,
+ attr_value: Optional[_AttributeValue],
+ rules: Iterable[AttributeValueMatchRule],
+ ) -> bool:
+ attr_values: Sequence[Optional[str]]
+ if isinstance(attr_value, list):
+ attr_values = attr_value
+ else:
+ attr_values = [cast(str, attr_value)]
+
+ def _match_attribute_value_helper(attr_values: Sequence[Optional[str]]) -> bool:
+ for rule in rules:
+ for attr_value in attr_values:
+ if rule.matches_string(attr_value):
+ return True
+ return False
+
+ this_attr_match = _match_attribute_value_helper(attr_values)
+ if not this_attr_match and len(attr_values) > 1:
+ # This cast converts Optional[str] to plain str.
+ #
+ # We know if there's more than one value, there can't be
+ # any None in the list, because Beautiful Soup never uses
+ # None as a value of a multi-valued attribute, and if None
+ # is passed in as attr_value, it's turned into a list with
+ # a single element (thus len(attr_values) > 1 fails).
+ attr_values = cast(Sequence[str], attr_values)
+
+ # Try again but treat the attribute value
+ # as a single string.
+ joined_attr_value = " ".join(attr_values)
+ this_attr_match = _match_attribute_value_helper([joined_attr_value])
+ return this_attr_match
+
+ def allow_tag_creation(
+ self, nsprefix: Optional[str], name: str, attrs: Optional[_RawAttributeValues]
+ ) -> bool:
+ """Based on the name and attributes of a tag, see whether this
+ `SoupStrainer` will allow a `Tag` object to even be created.
+
+ :param name: The name of the prospective tag.
+ :param attrs: The attributes of the prospective tag.
+ """
+ if self.string_rules:
+ # A SoupStrainer that has string rules can't be used to
+ # manage tag creation, because the string rule can't be
+ # evaluated until after the tag and all of its contents
+ # have been parsed.
+ return False
+ prefixed_name = None
+ if nsprefix:
+ prefixed_name = f"{nsprefix}:{name}"
+ if self.name_rules:
+ # At least one name rule must match.
+ name_match = False
+ for rule in self.name_rules:
+ for x in name, prefixed_name:
+ if x is not None:
+ if rule.matches_string(x):
+ name_match = True
+ break
+ if not name_match:
+ return False
+
+ # For each attribute that has rules, at least one rule must
+ # match.
+ if attrs is None:
+ attrs = AttributeDict()
+ for attr, rules in self.attribute_rules.items():
+ attr_value = attrs.get(attr)
+ if not self._attribute_match(attr_value, rules):
+ return False
+
+ return True
+
+ def allow_string_creation(self, string: str) -> bool:
+ """Based on the content of a markup string, see whether this
+ `SoupStrainer` will allow it to be instantiated as a
+ `NavigableString` object, or whether it should be ignored.
+ """
+ if self.name_rules or self.attribute_rules:
+ # A SoupStrainer that has name or attribute rules won't
+ # match any strings; it's designed to match tags with
+ # certain properties.
+ return False
+ if not self.string_rules:
+ # A SoupStrainer with no string rules will match
+ # all strings.
+ return True
+ if not self.matches_any_string_rule(string):
+ return False
+ return True
+
+ def matches_any_string_rule(self, string: str) -> bool:
+ """See whether the content of a string matches any of
+ this `SoupStrainer`'s string rules.
+ """
+ if not self.string_rules:
+ return True
+ for string_rule in self.string_rules:
+ if string_rule.matches_string(string):
+ return True
+ return False
+
+ def match(self, element: PageElement, _known_rules: bool=False) -> bool:
+ """Does the given `PageElement` match the rules set down by this
+ `SoupStrainer`?
+
+ The find_* methods rely heavily on this method to find matches.
+
+ :param element: A `PageElement`.
+ :param _known_rules: Set to true in the common case where
+ we already checked and found at least one rule in this SoupStrainer
+ that might exclude a PageElement. Without this, we need
+ to check .includes_everything every time, just to be safe.
+ :return: `True` if the element matches this `SoupStrainer`'s rules; `False` otherwise.
+ """
+ # If there are no rules at all, let anything through.
+ if not _known_rules and self.includes_everything:
+ return True
+ if isinstance(element, Tag):
+ return self.matches_tag(element)
+ assert isinstance(element, NavigableString)
+ if not (self.name_rules or self.attribute_rules):
+ # A NavigableString can only match a SoupStrainer that
+ # does not define any name or attribute rules.
+ # Then it comes down to the string rules.
+ return self.matches_any_string_rule(element)
+ return False
+
+ @_deprecated("allow_tag_creation", "4.13.0")
+ def search_tag(self, name: str, attrs: Optional[_RawAttributeValues]) -> bool:
+ """A less elegant version of `allow_tag_creation`. Deprecated as of 4.13.0"""
+ ":meta private:"
+ return self.allow_tag_creation(None, name, attrs)
+
+ @_deprecated("match", "4.13.0")
+ def search(self, element: PageElement) -> Optional[PageElement]:
+ """A less elegant version of match(). Deprecated as of 4.13.0.
+
+ :meta private:
+ """
+ return element if self.match(element) else None