Coverage for phml\nodes\element.py: 100%
40 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 16:33 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 16:33 -0600
1from __future__ import annotations
3from typing import TYPE_CHECKING, Optional
5from .parent import Parent
7if TYPE_CHECKING:
8 from .root import Root
9 from .types import Properties
12class Element(Parent):
13 """Element (Parent) represents an Element ([DOM]).
15 A tagName field must be present. It represents the element's local name ([DOM]).
17 The properties field represents information associated with the element.
18 The value of the properties field implements the Properties interface.
20 If the tagName field is 'template', a content field can be present. The value
21 of the content field implements the Root interface.
23 If the tagName field is 'template', the element must be a leaf.
25 If the tagName field is 'noscript', its children should be represented as if
26 scripting is disabled ([HTML]).
29 For example, the following HTML:
31 ```html
32 <a href="https://alpha.com" class="bravo" download></a>
33 ```
35 Yields:
37 ```javascript
38 {
39 type: 'element',
40 tagName: 'a',
41 properties: {
42 href: 'https://alpha.com',
43 className: ['bravo'],
44 download: true
45 },
46 children: []
47 }
48 ```
49 """
51 def __init__(
52 self,
53 tag: str = "element",
54 properties: Optional[Properties] = None,
55 parent: Optional[Element | Root] = None,
56 startend: bool = False,
57 **kwargs,
58 ):
59 super().__init__(**kwargs)
60 self.properties = properties or {}
61 self.tag = tag
62 self.startend = startend
63 self.parent = parent
64 self.locals = {}
66 def __getitem__(self, index: str) -> str:
67 return self.properties[index]
69 def __setitem__(self, index: str, value: str):
70 if not isinstance(index, str) or not isinstance(value, (str, bool)):
71 raise TypeError("Index must be a str and value must be either str or bool.")
73 self.properties[index] = value
75 def __delitem__(self, index: str):
76 if index in self.properties:
77 self.properties.pop(index, None)
79 def __eq__(self, obj) -> bool:
80 return bool(
81 obj is not None
82 and isinstance(obj, Element)
83 and self.tag == obj.tag
84 and self.startend == obj.startend
85 and self.properties == obj.properties
86 and len(self.children) == len(obj.children)
87 and all(child == obj_child for child, obj_child in zip(self.children, obj.children))
88 )
90 def start_tag(self) -> str:
91 """Builds the open/start tag for the element.
93 Note:
94 It will return `/>` if the tag is self closing.
96 Returns:
97 str: Built element start tag.
98 """
99 opening = f"<{self.tag}"
101 attributes = []
102 for prop in self.properties:
103 if isinstance(self[prop], bool) or self[prop] in ["yes", "no"]:
104 if self[prop] == "yes" or self[prop]:
105 attributes.append(prop)
106 else:
107 attributes.append(f'{prop}="{self[prop]}"')
108 if len(attributes) > 0:
109 attributes = " " + " ".join(attributes)
110 else:
111 attributes = ""
113 closing = f"{' /' if self.startend else ''}>"
115 return opening + attributes + closing
117 def end_tag(self) -> str:
118 """Build the elements end tag.
120 Returns:
121 str: Built element end tag.
122 """
123 return f"</{self.tag}>" if not self.startend else None
125 def __repr__(self) -> str:
126 out = f"{self.type}(tag: {self.tag}, properties: {self.properties}, \
127startend: {self.startend}, children: {len(self.children)})"
128 return out