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

1from __future__ import annotations 

2 

3from typing import TYPE_CHECKING, Optional 

4 

5from .parent import Parent 

6 

7if TYPE_CHECKING: 

8 from .root import Root 

9 from .types import Properties 

10 

11 

12class Element(Parent): 

13 """Element (Parent) represents an Element ([DOM]). 

14 

15 A tagName field must be present. It represents the element's local name ([DOM]). 

16 

17 The properties field represents information associated with the element. 

18 The value of the properties field implements the Properties interface. 

19 

20 If the tagName field is 'template', a content field can be present. The value 

21 of the content field implements the Root interface. 

22 

23 If the tagName field is 'template', the element must be a leaf. 

24 

25 If the tagName field is 'noscript', its children should be represented as if 

26 scripting is disabled ([HTML]). 

27 

28 

29 For example, the following HTML: 

30 

31 ```html 

32 <a href="https://alpha.com" class="bravo" download></a> 

33 ``` 

34 

35 Yields: 

36 

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 """ 

50 

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 = {} 

65 

66 def __getitem__(self, index: str) -> str: 

67 return self.properties[index] 

68 

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.") 

72 

73 self.properties[index] = value 

74 

75 def __delitem__(self, index: str): 

76 if index in self.properties: 

77 self.properties.pop(index, None) 

78 

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 ) 

89 

90 def start_tag(self) -> str: 

91 """Builds the open/start tag for the element. 

92 

93 Note: 

94 It will return `/>` if the tag is self closing. 

95 

96 Returns: 

97 str: Built element start tag. 

98 """ 

99 opening = f"<{self.tag}" 

100 

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 = "" 

112 

113 closing = f"{' /' if self.startend else ''}>" 

114 

115 return opening + attributes + closing 

116 

117 def end_tag(self) -> str: 

118 """Build the elements end tag. 

119 

120 Returns: 

121 str: Built element end tag. 

122 """ 

123 return f"</{self.tag}>" if not self.startend else None 

124 

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