1"""
2Soup Sieve.
3
4A CSS selector filter for BeautifulSoup4.
5
6MIT License
7
8Copyright (c) 2018 Isaac Muse
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26SOFTWARE.
27"""
28from __future__ import annotations
29from .__meta__ import __version__, __version_info__ # noqa: F401
30from . import css_parser as cp
31from . import css_match as cm
32from . import css_types as ct
33from .util import DEBUG, SelectorSyntaxError # noqa: F401
34import bs4
35from typing import Any, Iterator, Iterable
36
37__all__ = (
38 'DEBUG', 'SelectorSyntaxError', 'SoupSieve',
39 'closest', 'compile', 'filter', 'iselect',
40 'match', 'select', 'select_one'
41)
42
43SoupSieve = cm.SoupSieve
44
45
46def compile( # noqa: A001
47 pattern: str,
48 namespaces: dict[str, str] | None = None,
49 flags: int = 0,
50 *,
51 custom: dict[str, str] | None = None,
52 **kwargs: Any
53) -> cm.SoupSieve:
54 """Compile CSS pattern."""
55
56 if isinstance(pattern, SoupSieve):
57 if flags:
58 raise ValueError("Cannot process 'flags' argument on a compiled selector list")
59 elif namespaces is not None:
60 raise ValueError("Cannot process 'namespaces' argument on a compiled selector list")
61 elif custom is not None:
62 raise ValueError("Cannot process 'custom' argument on a compiled selector list")
63 return pattern
64
65 return cp._cached_css_compile(
66 pattern,
67 ct.Namespaces(namespaces) if namespaces is not None else namespaces,
68 ct.CustomSelectors(custom) if custom is not None else custom,
69 flags
70 )
71
72
73def purge() -> None:
74 """Purge cached patterns."""
75
76 cp._purge_cache()
77
78
79def closest(
80 select: str,
81 tag: bs4.Tag,
82 namespaces: dict[str, str] | None = None,
83 flags: int = 0,
84 *,
85 custom: dict[str, str] | None = None,
86 **kwargs: Any
87) -> bs4.Tag | None:
88 """Match closest ancestor."""
89
90 return compile(select, namespaces, flags, **kwargs).closest(tag)
91
92
93def match(
94 select: str,
95 tag: bs4.Tag,
96 namespaces: dict[str, str] | None = None,
97 flags: int = 0,
98 *,
99 custom: dict[str, str] | None = None,
100 **kwargs: Any
101) -> bool:
102 """Match node."""
103
104 return compile(select, namespaces, flags, **kwargs).match(tag)
105
106
107def filter( # noqa: A001
108 select: str,
109 iterable: Iterable[bs4.Tag],
110 namespaces: dict[str, str] | None = None,
111 flags: int = 0,
112 *,
113 custom: dict[str, str] | None = None,
114 **kwargs: Any
115) -> list[bs4.Tag]:
116 """Filter list of nodes."""
117
118 return compile(select, namespaces, flags, **kwargs).filter(iterable)
119
120
121def select_one(
122 select: str,
123 tag: bs4.Tag,
124 namespaces: dict[str, str] | None = None,
125 flags: int = 0,
126 *,
127 custom: dict[str, str] | None = None,
128 **kwargs: Any
129) -> bs4.Tag | None:
130 """Select a single tag."""
131
132 return compile(select, namespaces, flags, **kwargs).select_one(tag)
133
134
135def select(
136 select: str,
137 tag: bs4.Tag,
138 namespaces: dict[str, str] | None = None,
139 limit: int = 0,
140 flags: int = 0,
141 *,
142 custom: dict[str, str] | None = None,
143 **kwargs: Any
144) -> list[bs4.Tag]:
145 """Select the specified tags."""
146
147 return compile(select, namespaces, flags, **kwargs).select(tag, limit)
148
149
150def iselect(
151 select: str,
152 tag: bs4.Tag,
153 namespaces: dict[str, str] | None = None,
154 limit: int = 0,
155 flags: int = 0,
156 *,
157 custom: dict[str, str] | None = None,
158 **kwargs: Any
159) -> Iterator[bs4.Tag]:
160 """Iterate the specified tags."""
161
162 yield from compile(select, namespaces, flags, **kwargs).iselect(tag, limit)
163
164
165def escape(ident: str) -> str:
166 """Escape identifier."""
167
168 return cp.escape(ident)