Initial Commit

This commit is contained in:
OmniLottie
2026-03-01 21:36:54 +08:00
commit a386c803e1
199 changed files with 42253 additions and 0 deletions
View File
+160
View File
@@ -0,0 +1,160 @@
from xml.dom import minidom
import copy
import enum
from .core_nodes import XmlDescriptor, XmlSimpleElement, ValueReference
from .utils import *
from lottie.nvector import NVector
from lottie.parsers.sif.ast_impl.base import SifAstNode, SifValue
from lottie.parsers.sif.sif.core import ObjectRegistry, TypeDescriptor, noop
class XmlAnimatable(XmlDescriptor):
def __init__(self, name, typename, default=None, type_wrapper=noop):
super().__init__(name)
self.type = TypeDescriptor(typename, default, type_wrapper)
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry, param: TypeDescriptor = None):
cn = xml_first_element_child(parent, self.name)
if cn:
value = SifAstNode.from_dom(xml_first_element_child(cn), self.type_for(param), registry)
else:
value = self.default()
setattr(obj, self.att_name, value)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document, type: TypeDescriptor = None):
value = getattr(obj, self.att_name)
if isinstance(value, SifValue) and isinstance(value.value, ValueReference):
parent.setAttribute(self.name, ":" + value.value.id)
return
param = parent.appendChild(dom.createElement(self.name))
param.appendChild(value.to_dom(dom, self.type_for(type)))
return param
def from_python(self, value):
if not isinstance(value, SifAstNode):
raise ValueError("%s isn't a valid value for %s" % (value, self.name))
return value
def default(self):
return SifValue(copy.deepcopy(self.type.default_value))
def type_for(self, param: TypeDescriptor):
if param is not None and self.type.typename == "_recurse":
return param
return self.type
class XmlParam(XmlDescriptor):
def __init__(self, name, typename, default=None, type_wrapper=noop, static=False):
super().__init__(name)
self.type = TypeDescriptor(typename, default, type_wrapper)
self.static = static
def _def(self):
from ..api import Def
return Def
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
for cn in xml_child_elements(parent, "param"):
if cn.getAttribute("name") == self.name:
use = cn.getAttribute("use")
if use:
value = registry.get_object(use)
else:
value_node = xml_first_element_child(cn)
if self.static:
value = self.type.value_from_xml_element(value_node, registry)
else:
value = SifAstNode.from_dom(value_node, self.type, registry)
break
else:
value = self.default()
setattr(obj, self.att_name, value)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
param = parent.appendChild(dom.createElement("param"))
param.setAttribute("name", self.name)
value = getattr(obj, self.att_name)
if isinstance(value, self._def()):
param.setAttribute("use", ":" + value.id)
else:
if self.static:
elem = self.type.value_to_xml_element(value, dom)
else:
elem = value.to_dom(dom, self.type)
param.appendChild(elem)
return param
def from_python(self, value):
if self.static:
return self.type.type_wrapper(value)
if not isinstance(value, (SifAstNode, self._def())):
raise ValueError("%s isn't a valid value for %s" % (value, self.name))
return value
def default(self):
if self.static:
return copy.deepcopy(self.type.default_value)
return SifValue(copy.deepcopy(self.type.default_value))
class XmlDynamicListParam(XmlDescriptor):
_tag = "dynamic_list"
def __init__(self, name, typename, att_name=None):
super().__init__(name)
self.type = TypeDescriptor(typename)
if att_name is not None:
self.att_name = att_name
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
param = parent.appendChild(dom.createElement("param"))
param.setAttribute("name", self.name)
dyl = param.appendChild(dom.createElement(self._tag))
dyl.setAttribute("type", self.type.typename)
values = getattr(obj, self.att_name)
for val in values:
entry = dyl.appendChild(dom.createElement("entry"))
entry.appendChild(self._value_to_dom(val, dom))
def _value_to_dom(self, val, dom: minidom.Document):
return val.to_dom(dom, self.type)
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
values = []
for cn in xml_child_elements(parent, "param"):
if cn.getAttribute("name") == self.name:
list = xml_first_element_child(cn)
if list.getAttribute("type") != self.type.typename:
raise ValueError(
"Wrong type for %s: got %s instead of %s" %
(self.name, self.type.typename, list.getAttribute("type"))
)
for entry in xml_child_elements(list, "entry"):
values.append(self._value_from_dom(xml_first_element_child(entry), registry))
break
setattr(obj, self.att_name, values)
def _value_from_dom(self, element, registry):
return SifAstNode.from_dom(element, self.type, registry)
def from_python(self, value):
return value
def default(self):
return []
class XmlStaticListParam(XmlDynamicListParam):
_tag = "static_list"
def _value_to_dom(self, val, dom: minidom.Document):
return self.type.value_to_xml_element(val, dom)
def _value_from_dom(self, element: minidom.Element, registry: ObjectRegistry):
return self.type.value_from_xml_element(element, registry)
+142
View File
@@ -0,0 +1,142 @@
from xml.dom import minidom
import copy
from uuid import uuid4
from .utils import *
from lottie.parsers.sif.sif.core import ObjectRegistry
class XmlDescriptor:
def __init__(self, name):
self.name = name
self.att_name = name.replace("-", "_").replace("[", "_").replace("]", "").replace(".", "_")
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
raise NotImplementedError
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
raise NotImplementedError
def from_python(self, value):
raise NotImplementedError
def initialize_object(self, dict, obj):
if self.att_name in dict:
setattr(obj, self.att_name, self.from_python(dict[self.att_name]))
else:
setattr(obj, self.att_name, self.default())
def clean(self, value):
return self.from_python(value)
def default(self):
return None
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name)
class TypedXmlDescriptor(XmlDescriptor):
def __init__(self, name, type=str, default_value=None, att_name=None):
super().__init__(name)
self.type = type
self.default_value = default_value
if att_name is not None:
self.att_name = att_name
def from_python(self, value):
if value is None and self.default_value is None:
return None
if not value_isinstance(value, self.type):
return self.type(value)
return value
def default(self):
return copy.deepcopy(self.default_value)
class ValueReference:
def __init__(self, id, value=None):
self.value = value
self.id = id
@classmethod
def from_registry(cls, id, registry: ObjectRegistry):
return cls(id, registry.get_object(id))
class XmlAttribute(TypedXmlDescriptor):
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
xml_str = parent.getAttribute(self.name)
if xml_str:
if xml_str.startswith(":") and xml_str[1:] in registry.registry:
value = ValueReference.from_registry(xml_str[1:], registry)
else:
value = value_from_xml_string(xml_str, self.type, registry)
setattr(obj, self.att_name, value)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
value = getattr(obj, self.att_name)
if value is not None:
if isinstance(value, ValueReference):
xml_str = ":" + value.id
else:
xml_str = value_to_xml_string(value, self.type)
parent.setAttribute(self.name, xml_str)
class XmlFixedAttribute(XmlDescriptor):
def __init__(self, name, value, type=str):
super().__init__(name)
self.value = value
self.type = type
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
parent.setAttribute(self.name, value_to_xml_string(self.value, self.type))
def from_python(self, value):
if value != self.value:
raise ValueError("Value of %s should be %s, got %s" % (self.name, self.value, value))
return value
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
xml_str = parent.getAttribute(self.name)
setattr(obj, self.att_name, value_from_xml_string(xml_str, self.type, registry))
def default(self):
return self.value
class XmlSimpleElement(TypedXmlDescriptor):
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
cn = xml_first_element_child(parent, self.name, allow_none=True)
if cn:
value = value_from_xml_string(xml_text(cn), self.type, registry)
else:
value = self.default_value
setattr(obj, self.att_name, value)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
value = getattr(obj, self.att_name)
if value is not None:
parent.appendChild(xml_make_text(dom, self.name, value_to_xml_string(value, self.type)))
class XmlMeta(TypedXmlDescriptor):
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
for cn in xml_child_elements(parent, "meta"):
if cn.getAttribute("name") == self.name:
value = value_from_xml_string(cn.getAttribute("content"), self.type, registry)
break
else:
value = self.default_value
setattr(obj, self.att_name, value)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
value = getattr(obj, self.att_name)
if value is not None:
meta = parent.appendChild(dom.createElement("meta"))
meta.setAttribute("name", self.name)
meta.setAttribute("content", value_to_xml_string(value, self.type))
+85
View File
@@ -0,0 +1,85 @@
from xml.dom import minidom
from distutils.util import strtobool
from lottie.nvector import NVector
from lottie.parsers.sif.sif.frame_time import FrameTime
class _tag:
def __init__(self, type):
self.type = type
def __call__(self, v):
return self.type(v)
bool_str = _tag(bool)
def str_to_bool(strval):
return bool(strtobool(strval))
def value_from_xml_string(xml_str, type, registry):
if type in (bool_str, bool):
return str_to_bool(xml_str)
elif type is NVector:
return NVector(*map(float, xml_str.split()))
if type is FrameTime:
return FrameTime.parse_string(xml_str, registry)
return type(xml_str)
def value_to_xml_string(value, type):
if type is bool:
return "1" if value else "0"
if type is bool_str:
return "true" if value else "false"
elif type is NVector:
return " ".join(map(str, value))
return str(value)
def value_isinstance(value, type):
if isinstance(type, _tag):
type = type.type
return isinstance(value, type)
def xml_text(node):
return "".join(
x.nodeValue
for x in node.childNodes
if x.nodeType in {minidom.Node.TEXT_NODE, minidom.Node.CDATA_SECTION_NODE}
)
def xml_make_text(dom: minidom.Document, tag_name, text):
e = dom.createElement(tag_name)
e.appendChild(dom.createTextNode(text))
return e
def xml_element_matches(ch: minidom.Node, tagname=None):
if ch.nodeType != minidom.Node.ELEMENT_NODE:
return False
if tagname is not None and ch.tagName != tagname:
return False
return True
def xml_child_elements(xml: minidom.Node, tagname=None):
for ch in xml.childNodes:
if xml_element_matches(ch, tagname):
yield ch
def xml_first_element_child(xml: minidom.Node, tagname=None, allow_none=False):
for ch in xml_child_elements(xml, tagname):
return ch
if allow_none:
return None
raise ValueError("No %s in %s" % (tagname or "child element", getattr(xml, "tagName", "node")))
+217
View File
@@ -0,0 +1,217 @@
from .utils import *
from .core_nodes import XmlDescriptor, ObjectRegistry
class XmlParamSif(XmlDescriptor):
def __init__(self, name, child_node, default_ctor=None):
super().__init__(name)
self.child_node = child_node
self.default_ctor = default_ctor or child_node
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
for cn in xml_child_elements(parent, "param"):
if cn.getAttribute("name") == self.name:
value = self.child_node.from_dom(xml_first_element_child(cn), registry)
break
else:
value = self.default()
setattr(obj, self.att_name, value)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
param = parent.appendChild(dom.createElement("param"))
param.setAttribute("name", self.name)
param.appendChild(getattr(obj, self.att_name).to_dom(dom))
return param
def clean(self, value):
if not isinstance(value, self.child_node):
raise ValueError("%s isn't a valid value for %s" % (value, self.name))
return value
def default(self):
return self.default_ctor()
class SifNodeList:
def __init__(self, type):
self._items = []
self._type = type
def __len__(self):
return len(self._items)
def __iter__(self):
return iter(self._items)
def __getitem__(self, name):
return self._items[name]
def __getslice__(self, i, j):
return self._items[i:j]
def __setitem__(self, key, value: "Layer"):
self.validate(value)
self._items[key] = value
def append(self, value: "Layer"):
self.validate(value)
self._items.append(value)
def __str__(self):
return str(self._items)
def __repr__(self):
return "<SifNodeList %s>" % self._items
def validate(self, value):
if not isinstance(value, self._type):
raise ValueError("Not a valid object: %s" % value)
class XmlSifElement(XmlDescriptor):
def __init__(self, name, child_node, nested=True):
super().__init__(name)
self.child_node = child_node
self.nested = nested
def default(self):
return self.child_node()
def from_python(self, value):
if not isinstance(value, self.child_node):
raise ValueError("Invalid value for %s: %s" % (self.name, value))
return value
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
cn = xml_first_element_child(parent, self.name, allow_none=True)
if cn:
if self.nested:
element = xml_first_element_child(cn)
else:
element = cn
value = self.child_node.from_dom(element, registry)
else:
value = self.default()
setattr(obj, self.att_name, value)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
value = getattr(obj, self.att_name)
if self.nested:
node = dom.createElement(self.name)
parent.appendChild(node)
else:
node = parent
node.appendChild(value.to_dom(dom))
class XmlList(XmlDescriptor):
def __init__(self, child_node, name=None, wrapper_tag=None, tags=None):
super().__init__(wrapper_tag or child_node._tag)
self.child_node = child_node
self.att_name = self.att_name + "s" if name is None else name
self.wrapper_tag = wrapper_tag
if tags is None:
self.tags = {self.name}
else:
self.tags = tags
def default(self):
return SifNodeList(self.child_node)
def clean(self, value):
return value
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
values = self.default()
for cn in xml_child_elements(parent):
if cn.tagName in self.tags:
value_node = cn
if self.wrapper_tag:
value_node = xml_first_element_child(cn)
values.append(self.child_node.from_dom(value_node, registry))
setattr(obj, self.att_name, values)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
for value in getattr(obj, self.att_name):
value_node = value.to_dom(dom)
if self.wrapper_tag:
wrapper = dom.createElement(self.wrapper_tag)
wrapper.appendChild(value_node)
value_node = wrapper
parent.appendChild(value_node)
class XmlWrapper(XmlDescriptor):
def __init__(self, name, wrapped: XmlDescriptor):
super().__init__(name)
self.wrapped = wrapped
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
wrapper = parent.appendChild(dom.createElement(self.name))
self.wrapped.to_xml(obj, wrapper, dom)
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
wrapper = xml_first_element_child(parent, self.name, True)
if wrapper:
return self.wrapped.from_xml(obj, wrapper, registry)
return self.default()
def from_python(self, value):
return self.wrapped.from_python(value)
def initialize_object(self, dict, obj):
return self.wrapped.initialize_object(dict, obj)
def clean(self, value):
return self.wrapped.clean(value)
def default(self):
return self.wrapped.default()
class XmlWrapperParam(XmlWrapper):
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
wrapper = parent.appendChild(dom.createElement("param"))
wrapper.setAttribute("name", self.name)
self.wrapped.to_xml(obj, wrapper, dom)
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
for wrapper in xml_child_elements(parent, "param"):
if wrapper.getAttribute("name") == self.name:
return self.wrapped.from_xml(obj, wrapper, registry)
return self.default()
class XmlBoneReference(XmlDescriptor):
def __init__(self, name):
super().__init__(name)
def to_xml(self, obj, parent: minidom.Element, dom: minidom.Document):
value = getattr(obj, self.name, None)
if not value:
return
node = parent.appendChild(dom.createElement(self.name))
value_node = node.appendChild(dom.createElement("bone_valuenode"))
value_node.setAttribute("type", value.type)
value_node.setAttribute("guid", value.guid)
return node
def from_xml(self, obj, parent: minidom.Element, registry: ObjectRegistry):
node = xml_first_element_child(parent, self.name, True)
value = None
if node:
value_node = xml_first_element_child(node, "bone_valuenode", True)
if value_node:
value = registry.get_object(value_node.getAttribute("guid"))
if value.type != value_node.getAttribute("type"):
raise ValueError("Bone type %s is not %s" % (value.type, value_node.getAttribute("type")))
setattr(obj, self.att_name, value)
def from_python(self, value):
return value