{"record":{"id":"7589e38dbfcaf63f","repo":"D4Vinci/Scrapling","slug":"expected-a-single-string-or-ident-for-attr-go","errorCode":null,"errorMessage":"Expected a single string or ident for ::attr(), got {function.arguments!r}","messagePattern":"Expected a single string or ident for ::attr\\(\\), got (.+?)","errorType":"exception","errorClass":"ExpressionError","httpStatus":null,"severity":"error","filePath":"scrapling/core/translator.py","lineNumber":113,"sourceCode":"        if isinstance(pseudo_element, FunctionalPseudoElement):\n            method_name = f\"xpath_{pseudo_element.name.replace('-', '_')}_functional_pseudo_element\"\n            method = getattr(self, method_name, None)\n            if not method:  # pragma: no cover\n                raise ExpressionError(f\"The functional pseudo-element ::{pseudo_element.name}() is unknown\")\n            xpath = method(xpath, pseudo_element)\n        else:\n            method_name = f\"xpath_{pseudo_element.replace('-', '_')}_simple_pseudo_element\"\n            method = getattr(self, method_name, None)\n            if not method:  # pragma: no cover\n                raise ExpressionError(f\"The pseudo-element ::{pseudo_element} is unknown\")\n            xpath = method(xpath)\n        return xpath\n\n    @staticmethod\n    def xpath_attr_functional_pseudo_element(xpath: OriginalXPathExpr, function: FunctionalPseudoElement) -> XPathExpr:\n        \"\"\"Support selecting attribute values using ::attr() pseudo-element\"\"\"\n        if function.argument_types() not in ([\"STRING\"], [\"IDENT\"]):  # pragma: no cover\n            raise ExpressionError(f\"Expected a single string or ident for ::attr(), got {function.arguments!r}\")\n        return XPathExpr.from_xpath(xpath, attribute=function.arguments[0].value)\n\n    @staticmethod\n    def xpath_text_simple_pseudo_element(xpath: OriginalXPathExpr) -> XPathExpr:\n        \"\"\"Support selecting text nodes using ::text pseudo-element\"\"\"\n        return XPathExpr.from_xpath(xpath, textnode=True)\n\n\nclass HTMLTranslator(TranslatorMixin, OriginalHTMLTranslator):\n    def css_to_xpath(self, css: str, prefix: str = \"descendant-or-self::\") -> str:\n        return super().css_to_xpath(css, prefix)\n\n\ntranslator = HTMLTranslator()\n# Using a function instead of the translator directly to avoid Pyright override error\n\n\n@lru_cache(maxsize=256)","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/core/translator.py#L95-L131","documentation":"Raised by `xpath_attr_functional_pseudo_element` in scrapling/core/translator.py:113 when translating an `::attr(...)` pseudo-element. After cssselect parses the selector, the handler validates that `::attr` received exactly one argument of type STRING (quoted) or IDENT (bare name). Zero arguments, multiple arguments, or non-string tokens (e.g. `::attr(5)`, `::attr([href])`) fail the `argument_types()` check and raise `ExpressionError` with the parsed arguments shown.","triggerScenarios":"`selector.css('a::attr()')` (empty), `selector.css('a::attr(href, title)')` (two args), `selector.css('a::attr(123)')` (numeric token) — any `::attr(...)` whose argument list is not exactly one STRING or IDENT.","commonSituations":"Typos and unbalanced parentheses when hand-building selector strings; attempting to fetch multiple attributes in one pseudo-element; quoting mistakes like `::attr('href)` that make cssselect parse unexpected tokens; dynamically generated selectors from templates with missing values (`f\"a::attr({attr_name})\"` with `attr_name=''`).","solutions":["Use exactly one attribute name: `a::attr(href)` or `a::attr('href')`.","To get several attributes, issue separate queries (`css('a::attr(href)')` and `css('a::attr(title)')`) or select elements and read `element.attrib`.","If the selector is built dynamically, validate/interpolate the attribute name before building the string.","Simplify the selector until it parses to isolate which argument list is malformed."],"exampleFix":"# before\nvals = page.css('a::attr(href, title)')  # ExpressionError\n\n# after\nhrefs = page.css('a::attr(href)')\ntitles = page.css('a::attr(title)')\n# or\nfor a in page.css('a'):\n    print(a.attrib.get('href'), a.attrib.get('title'))","handlingStrategy":"validation","validationCode":"import re\n\nATTR_RE = re.compile(r'::attr\\(\\s*(?:\\\"[^\\\"]+\\\"|'[^']+'|[A-Za-z_:-][\\w:.-]*)\\s*\\)')\n\ndef attr_usage_ok(css: str) -> bool:\n    \"\"\"Every ::attr() must have exactly one string/ident argument.\"\"\"\n    for m in re.finditer(r'::attr\\([^)]*\\)', css):\n        if not ATTR_RE.fullmatch(m.group(0)):\n            return False\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    v = page.css(f'a::attr({name})')\nexcept Exception as e:\n    if '::attr()' in str(e):\n        raise ValueError(f'bad attribute name {name!r} in selector') from e\n    raise","preventionTips":["Always pass one attribute name: ::attr(href) or ::attr('href').","Sanitize dynamically interpolated attribute names (non-empty, valid ident).","Query attributes separately instead of comma-listing inside ::attr()."],"tags":["css-selectors","attr-pseudo-element","expression-error","scrapling"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}