from __future__ import annotations
import warnings
from typing import Any
import soupsavvy.exceptions as exc
from soupsavvy.interfaces import IElement, SelectionApi
[docs]
class LXMLXpathApi(SelectionApi):
"""Interface for `lxml` xpath API for html elements."""
[docs]
def __init__(self, selector: Any) -> None:
from lxml.etree import XPath, XPathSyntaxError
if not isinstance(selector, XPath):
try:
selector = XPath(selector)
except XPathSyntaxError as e:
raise exc.InvalidXPathSelector(
f"Parsing XPath '{selector}' failed"
) from e
super().__init__(selector)
[docs]
def select(self, element: IElement) -> list[IElement]:
from lxml.etree import _Element as HtmlElement
selected = self.selector(element.node)
if not isinstance(selected, list) or (
selected and all(not isinstance(match, HtmlElement) for match in selected)
):
warnings.warn(
"XPath expression does not target elements, "
f"no results will be found: {selected}",
UserWarning,
)
return []
return [element.from_node(node) for node in selected]
[docs]
class SeleniumXPathApi(SelectionApi):
"""Interface for `selenium` xpath API for web elements."""
[docs]
def select(self, element: IElement) -> list[IElement]:
from selenium.common.exceptions import InvalidSelectorException
from selenium.webdriver.common.by import By
try:
selected = element.node.find_elements(By.XPATH, self.selector)
except InvalidSelectorException as e:
raise exc.InvalidXPathSelector(
f"CSS selector constructed from provided parameters "
f"is not valid: {self.selector}"
) from e
return [element.from_node(node) for node in selected]
[docs]
class PlaywrightXPathApi(SelectionApi):
"""Interface for `playwright` xpath API for web elements."""
[docs]
def select(self, element: IElement) -> list[IElement]:
from playwright.sync_api import Error as PlaywrightError
try:
selected = element.node.query_selector_all(f"xpath={self.selector}")
except PlaywrightError as e:
raise exc.InvalidXPathSelector(
f"XPath selector constructed from provided parameters "
f"is not valid: {self.selector}"
) from e
return [element.from_node(node) for node in selected]