2023-01-12 01:04:47 +00:00
|
|
|
"""
|
|
|
|
Soup Sieve.
|
|
|
|
|
|
|
|
A CSS selector filter for BeautifulSoup4.
|
|
|
|
|
|
|
|
MIT License
|
|
|
|
|
|
|
|
Copyright (c) 2018 Isaac Muse
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
|
|
copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
SOFTWARE.
|
|
|
|
"""
|
2023-01-14 01:30:01 +00:00
|
|
|
from __future__ import annotations
|
2023-01-12 01:04:47 +00:00
|
|
|
from .__meta__ import __version__, __version_info__ # noqa: F401
|
|
|
|
from . import css_parser as cp
|
|
|
|
from . import css_match as cm
|
|
|
|
from . import css_types as ct
|
|
|
|
from .util import DEBUG, SelectorSyntaxError # noqa: F401
|
2023-01-14 01:30:01 +00:00
|
|
|
import bs4 # type: ignore[import]
|
2023-05-28 12:58:26 +00:00
|
|
|
from typing import Any, Iterator, Iterable
|
2023-01-12 01:04:47 +00:00
|
|
|
|
|
|
|
__all__ = (
|
|
|
|
'DEBUG', 'SelectorSyntaxError', 'SoupSieve',
|
|
|
|
'closest', 'compile', 'filter', 'iselect',
|
|
|
|
'match', 'select', 'select_one'
|
|
|
|
)
|
|
|
|
|
|
|
|
SoupSieve = cm.SoupSieve
|
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def compile( # noqa: A001
|
|
|
|
pattern: str,
|
2023-05-28 12:58:26 +00:00
|
|
|
namespaces: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
flags: int = 0,
|
|
|
|
*,
|
2023-05-28 12:58:26 +00:00
|
|
|
custom: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
**kwargs: Any
|
|
|
|
) -> cm.SoupSieve:
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Compile CSS pattern."""
|
|
|
|
|
|
|
|
if isinstance(pattern, SoupSieve):
|
|
|
|
if flags:
|
|
|
|
raise ValueError("Cannot process 'flags' argument on a compiled selector list")
|
|
|
|
elif namespaces is not None:
|
|
|
|
raise ValueError("Cannot process 'namespaces' argument on a compiled selector list")
|
|
|
|
elif custom is not None:
|
|
|
|
raise ValueError("Cannot process 'custom' argument on a compiled selector list")
|
|
|
|
return pattern
|
|
|
|
|
2023-05-28 12:58:26 +00:00
|
|
|
return cp._cached_css_compile(
|
|
|
|
pattern,
|
|
|
|
ct.Namespaces(namespaces) if namespaces is not None else namespaces,
|
|
|
|
ct.CustomSelectors(custom) if custom is not None else custom,
|
|
|
|
flags
|
|
|
|
)
|
2023-01-12 01:04:47 +00:00
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def purge() -> None:
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Purge cached patterns."""
|
|
|
|
|
|
|
|
cp._purge_cache()
|
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def closest(
|
|
|
|
select: str,
|
|
|
|
tag: 'bs4.Tag',
|
2023-05-28 12:58:26 +00:00
|
|
|
namespaces: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
flags: int = 0,
|
|
|
|
*,
|
2023-05-28 12:58:26 +00:00
|
|
|
custom: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
**kwargs: Any
|
|
|
|
) -> 'bs4.Tag':
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Match closest ancestor."""
|
|
|
|
|
|
|
|
return compile(select, namespaces, flags, **kwargs).closest(tag)
|
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def match(
|
|
|
|
select: str,
|
|
|
|
tag: 'bs4.Tag',
|
2023-05-28 12:58:26 +00:00
|
|
|
namespaces: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
flags: int = 0,
|
|
|
|
*,
|
2023-05-28 12:58:26 +00:00
|
|
|
custom: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
**kwargs: Any
|
|
|
|
) -> bool:
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Match node."""
|
|
|
|
|
|
|
|
return compile(select, namespaces, flags, **kwargs).match(tag)
|
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def filter( # noqa: A001
|
|
|
|
select: str,
|
|
|
|
iterable: Iterable['bs4.Tag'],
|
2023-05-28 12:58:26 +00:00
|
|
|
namespaces: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
flags: int = 0,
|
|
|
|
*,
|
2023-05-28 12:58:26 +00:00
|
|
|
custom: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
**kwargs: Any
|
|
|
|
) -> list['bs4.Tag']:
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Filter list of nodes."""
|
|
|
|
|
|
|
|
return compile(select, namespaces, flags, **kwargs).filter(iterable)
|
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def select_one(
|
|
|
|
select: str,
|
|
|
|
tag: 'bs4.Tag',
|
2023-05-28 12:58:26 +00:00
|
|
|
namespaces: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
flags: int = 0,
|
|
|
|
*,
|
2023-05-28 12:58:26 +00:00
|
|
|
custom: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
**kwargs: Any
|
|
|
|
) -> 'bs4.Tag':
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Select a single tag."""
|
|
|
|
|
|
|
|
return compile(select, namespaces, flags, **kwargs).select_one(tag)
|
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def select(
|
|
|
|
select: str,
|
|
|
|
tag: 'bs4.Tag',
|
2023-05-28 12:58:26 +00:00
|
|
|
namespaces: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
limit: int = 0,
|
|
|
|
flags: int = 0,
|
|
|
|
*,
|
2023-05-28 12:58:26 +00:00
|
|
|
custom: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
**kwargs: Any
|
|
|
|
) -> list['bs4.Tag']:
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Select the specified tags."""
|
|
|
|
|
|
|
|
return compile(select, namespaces, flags, **kwargs).select(tag, limit)
|
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def iselect(
|
|
|
|
select: str,
|
|
|
|
tag: 'bs4.Tag',
|
2023-05-28 12:58:26 +00:00
|
|
|
namespaces: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
limit: int = 0,
|
|
|
|
flags: int = 0,
|
|
|
|
*,
|
2023-05-28 12:58:26 +00:00
|
|
|
custom: dict[str, str] | None = None,
|
2023-01-14 01:30:01 +00:00
|
|
|
**kwargs: Any
|
|
|
|
) -> Iterator['bs4.Tag']:
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Iterate the specified tags."""
|
|
|
|
|
|
|
|
for el in compile(select, namespaces, flags, **kwargs).iselect(tag, limit):
|
|
|
|
yield el
|
|
|
|
|
|
|
|
|
2023-01-14 01:30:01 +00:00
|
|
|
def escape(ident: str) -> str:
|
2023-01-12 01:04:47 +00:00
|
|
|
"""Escape identifier."""
|
|
|
|
|
|
|
|
return cp.escape(ident)
|