from scrapling.core._types import Any, Dict class SelectorsGeneration: """ Functions for generating selectors Trying to generate selectors like Firefox or maybe cleaner ones!? Ehm Inspiration: https://searchfox.org/mozilla-central/source/devtools/shared/inspector/css-logic.js#591 """ # Note: This is a mixin class meant to be used with Selector. # The methods access Selector attributes (._root, .parent, .attrib, .tag, etc.) # through self, which will be a Selector instance at runtime. def _general_selection(self: Any, selection: str = "css", full_path: bool = False) -> str: """Generate a selector for the current element. :return: A string of the generated selector. """ if self._is_text_node(self._root): return "" selectorPath = [] target = self css = selection.lower() == "css" while target is not None: if target.parent: if target.attrib.get("id"): # id is enough if css: part = f"#{target.attrib['id']}" elif full_path: part = f"*[@id='{target.attrib['id']}']" else: part = f"[@id='{target.attrib['id']}']" selectorPath.append(part) if not full_path: return " > ".join(reversed(selectorPath)) if css else "//*" + "/".join(reversed(selectorPath)) else: part = f"{target.tag}" # We won't use classes anymore because I some websites share exact classes between elements # classes = target.attrib.get('class', '').split() # if classes and css: # part += f".{'.'.join(classes)}" # else: counter: Dict[str, int] = {} for child in target.parent.children: counter.setdefault(child.tag, 0) counter[child.tag] += 1 if child._root == target._root: break if counter[target.tag] > 1: part += f":nth-of-type({counter[target.tag]})" if css else f"[{counter[target.tag]}]" selectorPath.append(part) target = target.parent if target is None or target.tag == "html": return " > ".join(reversed(selectorPath)) if css else "//" + "/".join(reversed(selectorPath)) else: break return " > ".join(reversed(selectorPath)) if css else "//" + "/".join(reversed(selectorPath)) @property def generate_css_selector(self: Any) -> str: """Generate a CSS selector for the current element :return: A string of the generated selector. """ return self._general_selection() @property def generate_full_css_selector(self: Any) -> str: """Generate a complete CSS selector for the current element :return: A string of the generated selector. """ return self._general_selection(full_path=True) @property def generate_xpath_selector(self: Any) -> str: """Generate an XPath selector for the current element :return: A string of the generated selector. """ return self._general_selection("xpath") @property def generate_full_xpath_selector(self: Any) -> str: """Generate a complete XPath selector for the current element :return: A string of the generated selector. """ return self._general_selection("xpath", full_path=True)