Coverage for lib/lottie/parsers/sif/sif/core.py: 89%

114 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-20 16:17 +0100

1from xml.dom import minidom 

2import enum 

3from uuid import uuid4 

4 

5from lottie.nvector import NVector 

6from lottie.parsers.sif.xml.utils import xml_text, str_to_bool 

7from lottie.parsers.sif.xml.utils import xml_child_elements, value_from_xml_string, xml_make_text, value_to_xml_string 

8from lottie.parsers.sif.sif.frame_time import FrameTime 

9 

10 

11class ObjectRegistry: 

12 def __init__(self): 

13 self.registry = {} 

14 

15 def register_as(self, object, key): 

16 self.registry[key] = object 

17 

18 def register(self, object): 

19 guid = getattr(object, "guid", None) 

20 if guid is None: 20 ↛ 21line 20 didn't jump to line 21, because the condition on line 20 was never true

21 guid = self.guid() 

22 object.guid = guid 

23 self.registry[guid] = object 

24 

25 @classmethod 

26 def guid(cls): 

27 return str(uuid4()).replace("-", "").upper() 

28 

29 def get_object(self, guid): 

30 return self.registry[guid] 

31 

32 

33def noop(x): 

34 return x 

35 

36 

37class TypeDescriptor: 

38 _type_tag_names = { 

39 "bone_object": "bone" 

40 } 

41 

42 def __init__(self, typename, default=None, type_wrapper=noop): 

43 self.typename = typename 

44 self.type_wrapper = type_wrapper 

45 self.default_value = default 

46 

47 def value_to_xml_element(self, value, dom: minidom.Document): 

48 element = dom.createElement(self.tag_name) 

49 

50 if self.typename == "vector": 

51 element.appendChild(xml_make_text(dom, "x", str(value.x))) 

52 element.appendChild(xml_make_text(dom, "y", str(value.y))) 

53 if hasattr(value, "guid"): 

54 element.setAttribute("guid", value.guid) 

55 elif self.typename == "color": 

56 element.appendChild(xml_make_text(dom, "r", str(value[0]))) 

57 element.appendChild(xml_make_text(dom, "g", str(value[1]))) 

58 element.appendChild(xml_make_text(dom, "b", str(value[2]))) 

59 element.appendChild(xml_make_text(dom, "a", str(value[3]))) 

60 if hasattr(value, "guid"): 60 ↛ 61line 60 didn't jump to line 61, because the condition on line 60 was never true

61 element.setAttribute("guid", value.guid) 

62 elif self.typename == "gradient": 

63 for point in value: 

64 element.appendChild(point.to_dom(dom)) 

65 elif self.typename == "bool": 

66 element.setAttribute("value", "true" if value else "false") 

67 elif self.typename == "bone_object": 67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true

68 element.setAttribute("guid", value.guid) 

69 element.setAttribute("type", self.typename) 

70 elif self.typename == "string": 

71 element.appendChild(dom.createTextNode(value)) 

72 else: 

73 if isinstance(value, enum.Enum): 

74 value = value.value 

75 element.setAttribute("value", str(value)) 

76 

77 return element 

78 

79 @property 

80 def tag_name(self): 

81 return self._type_tag_names.get(self.typename, self.typename) 

82 

83 def value_from_xml_element(self, xml: minidom.Element, registry: ObjectRegistry): 

84 if xml.tagName != self.tag_name: 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true

85 raise ValueError("Wrong value type (%s instead of %s)" % (xml.tagName, self.tag_name)) 

86 

87 guid = xml.getAttribute("guid") 

88 if guid and guid in registry.registry: 

89 value = registry.registry[guid] 

90 elif self.typename == "vector": 

91 value = NVector( 

92 float(xml_text(xml.getElementsByTagName("x")[0])), 

93 float(xml_text(xml.getElementsByTagName("y")[0])) 

94 ) 

95 if xml.getAttribute("guid"): 

96 value.guid = xml.getAttribute("guid") 

97 registry.register(value) 

98 elif self.typename == "color": 

99 value = NVector( 

100 float(xml_text(xml.getElementsByTagName("r")[0])), 

101 float(xml_text(xml.getElementsByTagName("g")[0])), 

102 float(xml_text(xml.getElementsByTagName("b")[0])), 

103 float(xml_text(xml.getElementsByTagName("a")[0])) 

104 ) 

105 elif self.typename == "gradient": 

106 value = [ 

107 GradientPoint.from_dom(sub, registry) 

108 for sub in xml_child_elements(xml, GradientPoint.type.typename) 

109 ] 

110 elif self.typename == "real" or self.typename == "angle": 

111 value = float(xml.getAttribute("value")) 

112 elif self.typename == "integer": 

113 value = int(xml.getAttribute("value")) 

114 elif self.typename == "time": 

115 value = FrameTime.parse_string(xml.getAttribute("value"), registry) 

116 elif self.typename == "bool": 

117 value = str_to_bool(xml.getAttribute("value")) 

118 elif self.typename == "string": 118 ↛ 120line 118 didn't jump to line 120, because the condition on line 118 was never false

119 return xml_text(xml) 

120 elif self.typename == "bone_object": 

121 # Already done above but this forces the guid to be present 

122 return registry.get_object(xml.getAttribute("guid")) 

123 else: 

124 raise ValueError("Unsupported type %s" % self.typename) 

125 

126 return self.type_wrapper(value) 

127 

128 

129class GradientPoint: 

130 type = TypeDescriptor("color") 

131 

132 def __init__(self, pos: float, color: NVector): 

133 self.pos = pos 

134 self.color = color 

135 

136 def to_dom(self, dom: minidom.Document): 

137 element = self.type.value_to_xml_element(self.color, dom) 

138 element.setAttribute("pos", value_to_xml_string(self.pos, float)) 

139 return element 

140 

141 @classmethod 

142 def from_dom(cls, xml: minidom.Element, registry: ObjectRegistry): 

143 return GradientPoint( 

144 value_from_xml_string(xml.getAttribute("pos"), float, registry), 

145 cls.type.value_from_xml_element(xml, registry) 

146 ) 

147 

148 def __repr__(self): 

149 return "<GradientPoint %s %s>" % (self.pos, self.color) 

150 

151 

152class SifNodeMeta(type): 

153 def __new__(cls, name, bases, attr): 

154 props = [] 

155 for base in bases: 

156 if type(base) == cls: 

157 props += base._nodes 

158 attr["_nodes"] = props + attr.get("_nodes", []) 

159 if "_tag" not in attr: 

160 attr["_tag"] = name.lower() 

161 attr["_nodemap"] = { 

162 node.att_name: node 

163 for node in attr["_nodes"] 

164 } 

165 return super().__new__(cls, name, bases, attr)