1"""
2Smart-string xpath results, mirroring lxml's ``_ElementUnicodeResult``.
3
4When :meth:`turbohtml.Node.xpath` is called with ``smart_strings=True``, an
5attribute or ``text()`` value comes back as an :class:`XPathString`: a ``str``
6that also remembers the element it was selected from, so ``result.getparent()``
7walks back into the tree and ``result.is_attribute`` / ``result.attrname``
8identify where it came from. The C core constructs these; importing this module
9registers the type with it.
10"""
11
12from __future__ import annotations
13
14from typing import TYPE_CHECKING
15
16from turbohtml._html import Element, _register_xpath_string # Element stays importable so autodoc resolves it
17
18if TYPE_CHECKING:
19 from typing import Self
20
21
22# isinstance(result, str) must hold for callers, and the C core builds the value
23# through str.__new__, so this stays a real str subclass (FURB189 suppressed).
24class XPathString(str): # ruff:ignore[subclass-builtin]
25 """
26 A string xpath result that remembers the element it came from.
27
28 :param value: the string value itself, the attribute or ``text()`` result.
29 :param parent: the element the value was selected from, returned by :meth:`getparent`.
30 :param is_attribute: whether the value came from an attribute rather than element text.
31 :param attrname: the attribute name the value came from, or ``None`` for a text value.
32 """
33
34 __slots__ = ("_parent", "attrname", "is_attribute", "is_tail", "is_text")
35
36 _parent: Element
37 is_attribute: bool
38 """Whether the value was selected from an attribute."""
39 is_text: bool
40 """Whether the value was selected from element text."""
41 is_tail: bool
42 """Whether the value is tail text; always False, kept for lxml compatibility."""
43 attrname: str | None
44 """The attribute name the value came from, or None for a text value."""
45
46 def __new__(cls, value: str, parent: Element, is_attribute: bool, attrname: str | None) -> Self: # ruff:ignore[boolean-type-hint-positional-argument]
47 self = str.__new__(cls, value)
48 self._parent = parent
49 self.is_attribute = is_attribute
50 self.is_text = not is_attribute
51 self.is_tail = False
52 self.attrname = attrname
53 return self
54
55 def getparent(self) -> Element:
56 """Return the element this attribute or text value was selected from."""
57 return self._parent
58
59
60_register_xpath_string(XPathString)