193 lines
4.6 KiB
Python
193 lines
4.6 KiB
Python
from copy import copy
|
|
from dataclasses import dataclass
|
|
from enum import StrEnum
|
|
from typing import TYPE_CHECKING, Iterable, Optional, Self
|
|
|
|
from bs4 import BeautifulSoup, Tag
|
|
from ..clonable import Clonable
|
|
|
|
if TYPE_CHECKING:
|
|
from ..puppytype import Parsable
|
|
|
|
TemplateName = "template"
|
|
|
|
class ShadowRootMode(StrEnum):
|
|
Open = "open"
|
|
Closed = "closed"
|
|
|
|
class Node(Clonable):
|
|
@property
|
|
def value(self): pass
|
|
|
|
def from_path(path: str) -> Self:
|
|
with open(path, "r") as f:
|
|
soup = BeautifulSoup(f.read(), 'html.parser')
|
|
return Node.from_tag(soup)
|
|
|
|
def from_tag(tag: Tag | Iterable[Tag] | BeautifulSoup) -> Self:
|
|
if isinstance(tag, Tag):
|
|
return SingleNode(tag)
|
|
|
|
match len(tag):
|
|
case 0: return EmptyNode()
|
|
case 1: return SingleNode(tag[0])
|
|
case _: return NodeCollection(tag)
|
|
|
|
def attach_shadow(self, shadow_root_mode: ShadowRootMode, clonable: Optional[bool] = None, delegates_focus: Optional[bool] = None, serializable: Optional[bool] = None):
|
|
return ShadowRoot(
|
|
host=self.value,
|
|
mode=shadow_root_mode,
|
|
clonable=clonable,
|
|
delegates_focus=delegates_focus,
|
|
serializable=serializable
|
|
)
|
|
|
|
def find(self, *args, **kwargs):
|
|
return self.value.find(*args, **kwargs)
|
|
|
|
def find_all(self, *args, **kwargs):
|
|
return self.value.find_all(*args, **kwargs)
|
|
|
|
def append(self, *args, **kwargs):
|
|
return self.value.append(*args, **kwargs)
|
|
|
|
def insert(self, *args, **kwargs):
|
|
return self.value.insert(*args, **kwargs)
|
|
|
|
def get_attr(self, attr: str) -> Optional[str]:
|
|
return self.value.get(attr)
|
|
|
|
def has_attr(self, attr: str) -> bool:
|
|
return self.value.has_attr(attr)
|
|
|
|
def replace(self, node: "Node | Iterable[Node]"):
|
|
self.value.insert_before(node.value)
|
|
self.value.decompose()
|
|
|
|
def prettify(self) -> str:
|
|
return self.value.prettify()
|
|
|
|
class EmptyNode(Node):
|
|
@property
|
|
def value(self):
|
|
return None
|
|
|
|
@dataclass
|
|
class NodeCollection(Node):
|
|
nodes: Iterable[Tag]
|
|
|
|
def __iter__(self):
|
|
return self.nodes
|
|
|
|
@property
|
|
def value(self):
|
|
return self.nodes
|
|
|
|
def clone(self) -> Self:
|
|
return NodeCollection(copy(self.value))
|
|
|
|
@dataclass
|
|
class SingleNode(Node):
|
|
_value: Tag
|
|
|
|
@property
|
|
def value(self):
|
|
return self._value
|
|
|
|
@property
|
|
def parent(self):
|
|
return self._value.parent
|
|
|
|
def attach_shadow(self, shadow_root_mode: ShadowRootMode, clonable: Optional[bool] = None, delegates_focus: Optional[bool] = None, serializable: Optional[bool] = None):
|
|
return ShadowRoot(
|
|
host=self,
|
|
mode=shadow_root_mode,
|
|
clonable=clonable,
|
|
delegates_focus=delegates_focus,
|
|
serializable=serializable
|
|
)
|
|
|
|
def __getitem__(self, index):
|
|
return self._value[index]
|
|
|
|
def clone(self) -> Self:
|
|
return SingleNode(copy(self.value))
|
|
|
|
@dataclass
|
|
class Template(Node):
|
|
_value: Node
|
|
|
|
@property
|
|
def value(self):
|
|
return self._value
|
|
|
|
@property
|
|
def id(self):
|
|
return self._value["id"]
|
|
|
|
@property
|
|
def shadow_root_mode(self) -> ShadowRootMode | None:
|
|
return self._value.get("shadowrootmode")
|
|
|
|
@shadow_root_mode.setter
|
|
def shadow_root_mode(self, mode: ShadowRootMode):
|
|
self._value["shadowrootmode"] = mode.value
|
|
|
|
@property
|
|
def delegates_focus(self) -> Optional[bool]:
|
|
return self._value.get("delegatesfocus")
|
|
|
|
@delegates_focus.setter
|
|
def delegates_focus(self, value: bool):
|
|
self._value["delegatesfocus"] = value
|
|
|
|
@property
|
|
def clonable(self) -> Optional[bool]:
|
|
return self._value.get("clonable")
|
|
|
|
@clonable.setter
|
|
def clonable(self, value: bool):
|
|
self._value["clonable"] = value
|
|
|
|
@property
|
|
def serializable(self) -> Optional[bool]:
|
|
return self._value.get("serializable")
|
|
|
|
@serializable.setter
|
|
def serializable(self, value: bool):
|
|
self._value["serializable"] = value
|
|
|
|
def from_path(path: str) -> Self:
|
|
with open(path, "r") as f:
|
|
return Template(BeautifulSoup(f.read(), 'html.parser').find())
|
|
|
|
def from_element(element: "Parsable") -> Self:
|
|
if isinstance(element, Template):
|
|
return element
|
|
return Template(element)
|
|
|
|
def clone(self) -> Self:
|
|
return Template(copy(self._value))
|
|
|
|
@dataclass
|
|
class ShadowRoot(Clonable):
|
|
host: Node
|
|
mode: ShadowRootMode
|
|
|
|
clonable: Optional[bool] = None
|
|
delegates_focus: Optional[bool] = None
|
|
serializable: Optional[bool] = None
|
|
|
|
def append_child(self, template: Template):
|
|
template.shadow_root_mode = self.mode
|
|
|
|
if self.clonable is not None:
|
|
template.clonable = self.clonable
|
|
|
|
if self.delegates_focus is not None:
|
|
template.delegates_focus = self.delegates_focus
|
|
|
|
if self.serializable is not None:
|
|
template.serializable = self.serializable
|
|
|
|
self.host.value.insert(0, template.value)
|