Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/bs4/css.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

51 statements  

1"""Integration code for CSS selectors using `Soup Sieve <https://facelessuser.github.io/soupsieve/>`_ (pypi: ``soupsieve``). 

2 

3Acquire a `CSS` object through the `element.Tag.css` attribute of 

4the starting point of your CSS selector, or (if you want to run a 

5selector against the entire document) of the `BeautifulSoup` object 

6itself. 

7 

8The main advantage of doing this instead of using ``soupsieve`` 

9functions is that you don't need to keep passing the `element.Tag` to be 

10selected against, since the `CSS` object is permanently scoped to that 

11`element.Tag`. 

12 

13""" 

14 

15from __future__ import annotations 

16 

17from types import ModuleType 

18from typing import ( 

19 Any, 

20 cast, 

21 Iterable, 

22 Iterator, 

23 Optional, 

24 TYPE_CHECKING, 

25) 

26import warnings 

27from bs4._typing import _NamespaceMapping 

28 

29if TYPE_CHECKING: 

30 from soupsieve import SoupSieve 

31 from bs4 import element 

32 from bs4.element import ResultSet, Tag 

33 

34soupsieve: Optional[ModuleType] 

35try: 

36 import soupsieve 

37except ImportError: 

38 soupsieve = None 

39 warnings.warn( 

40 "The soupsieve package is not installed. CSS selectors cannot be used." 

41 ) 

42 

43 

44class CSS(object): 

45 """A proxy object against the ``soupsieve`` library, to simplify its 

46 CSS selector API. 

47 

48 You don't need to instantiate this class yourself; instead, use 

49 `element.Tag.css`. 

50 

51 :param tag: All CSS selectors run by this object will use this as 

52 their starting point. 

53 

54 :param api: An optional drop-in replacement for the ``soupsieve`` module, 

55 intended for use in unit tests. 

56 """ 

57 

58 def __init__(self, tag: element.Tag, api: Optional[ModuleType] = None): 

59 if api is None: 

60 api = soupsieve 

61 if api is None: 

62 raise NotImplementedError( 

63 "Cannot execute CSS selectors because the soupsieve package is not installed." 

64 ) 

65 self.api = api 

66 self.tag = tag 

67 

68 def escape(self, ident: str) -> str: 

69 """Escape a CSS identifier. 

70 

71 This is a simple wrapper around `soupsieve.escape() <https://facelessuser.github.io/soupsieve/api/#soupsieveescape>`_. See the 

72 documentation for that function for more information. 

73 """ 

74 if soupsieve is None: 

75 raise NotImplementedError( 

76 "Cannot escape CSS identifiers because the soupsieve package is not installed." 

77 ) 

78 return cast(str, self.api.escape(ident)) 

79 

80 def _ns( 

81 self, ns: Optional[_NamespaceMapping], select: str 

82 ) -> Optional[_NamespaceMapping]: 

83 """Normalize a dictionary of namespaces.""" 

84 if not isinstance(select, self.api.SoupSieve) and ns is None: 

85 # If the selector is a precompiled pattern, it already has 

86 # a namespace context compiled in, which cannot be 

87 # replaced. 

88 ns = self.tag._namespaces 

89 return ns 

90 

91 def _rs(self, results: Iterable[Tag]) -> ResultSet[Tag]: 

92 """Normalize a list of results to a py:class:`ResultSet`. 

93 

94 A py:class:`ResultSet` is more consistent with the rest of 

95 Beautiful Soup's API, and :py:meth:`ResultSet.__getattr__` has 

96 a helpful error message if you try to treat a list of results 

97 as a single result (a common mistake). 

98 """ 

99 # Import here to avoid circular import 

100 from bs4 import ResultSet 

101 

102 return ResultSet(None, results) 

103 

104 def compile( 

105 self, 

106 select: str, 

107 namespaces: Optional[_NamespaceMapping] = None, 

108 flags: int = 0, 

109 **kwargs: Any, 

110 ) -> SoupSieve: 

111 """Pre-compile a selector and return the compiled object. 

112 

113 :param selector: A CSS selector. 

114 

115 :param namespaces: A dictionary mapping namespace prefixes 

116 used in the CSS selector to namespace URIs. By default, 

117 Beautiful Soup will use the prefixes it encountered while 

118 parsing the document. 

119 

120 :param flags: Flags to be passed into Soup Sieve's 

121 `soupsieve.compile() <https://facelessuser.github.io/soupsieve/api/#soupsievecompile>`_ method. 

122 

123 :param kwargs: Keyword arguments to be passed into Soup Sieve's 

124 `soupsieve.compile() <https://facelessuser.github.io/soupsieve/api/#soupsievecompile>`_ method. 

125 

126 :return: A precompiled selector object. 

127 :rtype: soupsieve.SoupSieve 

128 """ 

129 return self.api.compile(select, self._ns(namespaces, select), flags, **kwargs) 

130 

131 def select_one( 

132 self, 

133 select: str, 

134 namespaces: Optional[_NamespaceMapping] = None, 

135 flags: int = 0, 

136 **kwargs: Any, 

137 ) -> element.Tag | None: 

138 """Perform a CSS selection operation on the current Tag and return the 

139 first result, if any. 

140 

141 This uses the Soup Sieve library. For more information, see 

142 that library's documentation for the `soupsieve.select_one() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect_one>`_ method. 

143 

144 :param selector: A CSS selector. 

145 

146 :param namespaces: A dictionary mapping namespace prefixes 

147 used in the CSS selector to namespace URIs. By default, 

148 Beautiful Soup will use the prefixes it encountered while 

149 parsing the document. 

150 

151 :param flags: Flags to be passed into Soup Sieve's 

152 `soupsieve.select_one() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect_one>`_ method. 

153 

154 :param kwargs: Keyword arguments to be passed into Soup Sieve's 

155 `soupsieve.select_one() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect_one>`_ method. 

156 """ 

157 return self.api.select_one( 

158 select, self.tag, self._ns(namespaces, select), flags, **kwargs 

159 ) 

160 

161 def select( 

162 self, 

163 select: str, 

164 namespaces: Optional[_NamespaceMapping] = None, 

165 limit: int = 0, 

166 flags: int = 0, 

167 **kwargs: Any, 

168 ) -> ResultSet[element.Tag]: 

169 """Perform a CSS selection operation on the current `element.Tag`. 

170 

171 This uses the Soup Sieve library. For more information, see 

172 that library's documentation for the `soupsieve.select() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect>`_ method. 

173 

174 :param selector: A CSS selector. 

175 

176 :param namespaces: A dictionary mapping namespace prefixes 

177 used in the CSS selector to namespace URIs. By default, 

178 Beautiful Soup will pass in the prefixes it encountered while 

179 parsing the document. 

180 

181 :param limit: After finding this number of results, stop looking. 

182 

183 :param flags: Flags to be passed into Soup Sieve's 

184 `soupsieve.select() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect>`_ method. 

185 

186 :param kwargs: Keyword arguments to be passed into Soup Sieve's 

187 `soupsieve.select() <https://facelessuser.github.io/soupsieve/api/#soupsieveselect>`_ method. 

188 """ 

189 if limit is None: 

190 limit = 0 

191 

192 return self._rs( 

193 self.api.select( 

194 select, self.tag, self._ns(namespaces, select), limit, flags, **kwargs 

195 ) 

196 ) 

197 

198 def iselect( 

199 self, 

200 select: str, 

201 namespaces: Optional[_NamespaceMapping] = None, 

202 limit: int = 0, 

203 flags: int = 0, 

204 **kwargs: Any, 

205 ) -> Iterator[element.Tag]: 

206 """Perform a CSS selection operation on the current `element.Tag`. 

207 

208 This uses the Soup Sieve library. For more information, see 

209 that library's documentation for the `soupsieve.iselect() 

210 <https://facelessuser.github.io/soupsieve/api/#soupsieveiselect>`_ 

211 method. It is the same as select(), but it returns a generator 

212 instead of a list. 

213 

214 :param selector: A string containing a CSS selector. 

215 

216 :param namespaces: A dictionary mapping namespace prefixes 

217 used in the CSS selector to namespace URIs. By default, 

218 Beautiful Soup will pass in the prefixes it encountered while 

219 parsing the document. 

220 

221 :param limit: After finding this number of results, stop looking. 

222 

223 :param flags: Flags to be passed into Soup Sieve's 

224 `soupsieve.iselect() <https://facelessuser.github.io/soupsieve/api/#soupsieveiselect>`_ method. 

225 

226 :param kwargs: Keyword arguments to be passed into Soup Sieve's 

227 `soupsieve.iselect() <https://facelessuser.github.io/soupsieve/api/#soupsieveiselect>`_ method. 

228 """ 

229 return self.api.iselect( 

230 select, self.tag, self._ns(namespaces, select), limit, flags, **kwargs 

231 ) 

232 

233 def closest( 

234 self, 

235 select: str, 

236 namespaces: Optional[_NamespaceMapping] = None, 

237 flags: int = 0, 

238 **kwargs: Any, 

239 ) -> Optional[element.Tag]: 

240 """Find the `element.Tag` closest to this one that matches the given selector. 

241 

242 This uses the Soup Sieve library. For more information, see 

243 that library's documentation for the `soupsieve.closest() 

244 <https://facelessuser.github.io/soupsieve/api/#soupsieveclosest>`_ 

245 method. 

246 

247 :param selector: A string containing a CSS selector. 

248 

249 :param namespaces: A dictionary mapping namespace prefixes 

250 used in the CSS selector to namespace URIs. By default, 

251 Beautiful Soup will pass in the prefixes it encountered while 

252 parsing the document. 

253 

254 :param flags: Flags to be passed into Soup Sieve's 

255 `soupsieve.closest() <https://facelessuser.github.io/soupsieve/api/#soupsieveclosest>`_ method. 

256 

257 :param kwargs: Keyword arguments to be passed into Soup Sieve's 

258 `soupsieve.closest() <https://facelessuser.github.io/soupsieve/api/#soupsieveclosest>`_ method. 

259 

260 """ 

261 return self.api.closest( 

262 select, self.tag, self._ns(namespaces, select), flags, **kwargs 

263 ) 

264 

265 def match( 

266 self, 

267 select: str, 

268 namespaces: Optional[_NamespaceMapping] = None, 

269 flags: int = 0, 

270 **kwargs: Any, 

271 ) -> bool: 

272 """Check whether or not this `element.Tag` matches the given CSS selector. 

273 

274 This uses the Soup Sieve library. For more information, see 

275 that library's documentation for the `soupsieve.match() 

276 <https://facelessuser.github.io/soupsieve/api/#soupsievematch>`_ 

277 method. 

278 

279 :param: a CSS selector. 

280 

281 :param namespaces: A dictionary mapping namespace prefixes 

282 used in the CSS selector to namespace URIs. By default, 

283 Beautiful Soup will pass in the prefixes it encountered while 

284 parsing the document. 

285 

286 :param flags: Flags to be passed into Soup Sieve's 

287 `soupsieve.match() 

288 <https://facelessuser.github.io/soupsieve/api/#soupsievematch>`_ 

289 method. 

290 

291 :param kwargs: Keyword arguments to be passed into SoupSieve's 

292 `soupsieve.match() 

293 <https://facelessuser.github.io/soupsieve/api/#soupsievematch>`_ 

294 method. 

295 """ 

296 return cast( 

297 bool, 

298 self.api.match( 

299 select, self.tag, self._ns(namespaces, select), flags, **kwargs 

300 ), 

301 ) 

302 

303 def filter( 

304 self, 

305 select: str, 

306 namespaces: Optional[_NamespaceMapping] = None, 

307 flags: int = 0, 

308 **kwargs: Any, 

309 ) -> ResultSet[element.Tag]: 

310 """Filter this `element.Tag`'s direct children based on the given CSS selector. 

311 

312 This uses the Soup Sieve library. It works the same way as 

313 passing a `element.Tag` into that library's `soupsieve.filter() 

314 <https://facelessuser.github.io/soupsieve/api/#soupsievefilter>`_ 

315 method. For more information, see the documentation for 

316 `soupsieve.filter() 

317 <https://facelessuser.github.io/soupsieve/api/#soupsievefilter>`_. 

318 

319 :param namespaces: A dictionary mapping namespace prefixes 

320 used in the CSS selector to namespace URIs. By default, 

321 Beautiful Soup will pass in the prefixes it encountered while 

322 parsing the document. 

323 

324 :param flags: Flags to be passed into Soup Sieve's 

325 `soupsieve.filter() 

326 <https://facelessuser.github.io/soupsieve/api/#soupsievefilter>`_ 

327 method. 

328 

329 :param kwargs: Keyword arguments to be passed into SoupSieve's 

330 `soupsieve.filter() 

331 <https://facelessuser.github.io/soupsieve/api/#soupsievefilter>`_ 

332 method. 

333 """ 

334 return self._rs( 

335 self.api.filter( 

336 select, self.tag, self._ns(namespaces, select), flags, **kwargs 

337 ) 

338 )