Updated script that can be controled by Nodejs web app
This commit is contained in:
@ -0,0 +1,240 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from copy import copy
|
||||
from keyword import kwlist
|
||||
KEYWORDS = frozenset(kwlist)
|
||||
|
||||
from . import Descriptor
|
||||
from . import MetaSerialisable
|
||||
from .sequence import (
|
||||
Sequence,
|
||||
NestedSequence,
|
||||
MultiSequencePart,
|
||||
)
|
||||
from .namespace import namespaced
|
||||
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import (
|
||||
Element,
|
||||
localname,
|
||||
)
|
||||
|
||||
seq_types = (list, tuple)
|
||||
|
||||
class Serialisable(metaclass=MetaSerialisable):
|
||||
"""
|
||||
Objects can serialise to XML their attributes and child objects.
|
||||
The following class attributes are created by the metaclass at runtime:
|
||||
__attrs__ = attributes
|
||||
__nested__ = single-valued child treated as an attribute
|
||||
__elements__ = child elements
|
||||
"""
|
||||
|
||||
__attrs__ = None
|
||||
__nested__ = None
|
||||
__elements__ = None
|
||||
__namespaced__ = None
|
||||
|
||||
idx_base = 0
|
||||
|
||||
@property
|
||||
def tagname(self):
|
||||
raise(NotImplementedError)
|
||||
|
||||
namespace = None
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
"""
|
||||
Create object from XML
|
||||
"""
|
||||
# strip known namespaces from attributes
|
||||
attrib = dict(node.attrib)
|
||||
for key, ns in cls.__namespaced__:
|
||||
if ns in attrib:
|
||||
attrib[key] = attrib[ns]
|
||||
del attrib[ns]
|
||||
|
||||
# strip attributes with unknown namespaces
|
||||
for key in list(attrib):
|
||||
if key.startswith('{'):
|
||||
del attrib[key]
|
||||
elif key in KEYWORDS:
|
||||
attrib["_" + key] = attrib[key]
|
||||
del attrib[key]
|
||||
elif "-" in key:
|
||||
n = key.replace("-", "_")
|
||||
attrib[n] = attrib[key]
|
||||
del attrib[key]
|
||||
|
||||
if node.text and "attr_text" in cls.__attrs__:
|
||||
attrib["attr_text"] = node.text
|
||||
|
||||
for el in node:
|
||||
tag = localname(el)
|
||||
if tag in KEYWORDS:
|
||||
tag = "_" + tag
|
||||
desc = getattr(cls, tag, None)
|
||||
if desc is None or isinstance(desc, property):
|
||||
continue
|
||||
|
||||
if hasattr(desc, 'from_tree'):
|
||||
#descriptor manages conversion
|
||||
obj = desc.from_tree(el)
|
||||
else:
|
||||
if hasattr(desc.expected_type, "from_tree"):
|
||||
#complex type
|
||||
obj = desc.expected_type.from_tree(el)
|
||||
else:
|
||||
#primitive
|
||||
obj = el.text
|
||||
|
||||
if isinstance(desc, NestedSequence):
|
||||
attrib[tag] = obj
|
||||
elif isinstance(desc, Sequence):
|
||||
attrib.setdefault(tag, [])
|
||||
attrib[tag].append(obj)
|
||||
elif isinstance(desc, MultiSequencePart):
|
||||
attrib.setdefault(desc.store, [])
|
||||
attrib[desc.store].append(obj)
|
||||
else:
|
||||
attrib[tag] = obj
|
||||
|
||||
return cls(**attrib)
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, idx=None, namespace=None):
|
||||
|
||||
if tagname is None:
|
||||
tagname = self.tagname
|
||||
|
||||
# keywords have to be masked
|
||||
if tagname.startswith("_"):
|
||||
tagname = tagname[1:]
|
||||
|
||||
tagname = namespaced(self, tagname, namespace)
|
||||
namespace = getattr(self, "namespace", namespace)
|
||||
|
||||
attrs = dict(self)
|
||||
for key, ns in self.__namespaced__:
|
||||
if key in attrs:
|
||||
attrs[ns] = attrs[key]
|
||||
del attrs[key]
|
||||
|
||||
el = Element(tagname, attrs)
|
||||
if "attr_text" in self.__attrs__:
|
||||
el.text = safe_string(getattr(self, "attr_text"))
|
||||
|
||||
for child_tag in self.__elements__:
|
||||
desc = getattr(self.__class__, child_tag, None)
|
||||
obj = getattr(self, child_tag)
|
||||
if hasattr(desc, "namespace") and hasattr(obj, 'namespace'):
|
||||
obj.namespace = desc.namespace
|
||||
|
||||
if isinstance(obj, seq_types):
|
||||
if isinstance(desc, NestedSequence):
|
||||
# wrap sequence in container
|
||||
if not obj:
|
||||
continue
|
||||
nodes = [desc.to_tree(child_tag, obj, namespace)]
|
||||
elif isinstance(desc, Sequence):
|
||||
# sequence
|
||||
desc.idx_base = self.idx_base
|
||||
nodes = (desc.to_tree(child_tag, obj, namespace))
|
||||
else: # property
|
||||
nodes = (v.to_tree(child_tag, namespace) for v in obj)
|
||||
for node in nodes:
|
||||
el.append(node)
|
||||
else:
|
||||
if child_tag in self.__nested__:
|
||||
node = desc.to_tree(child_tag, obj, namespace)
|
||||
elif obj is None:
|
||||
continue
|
||||
else:
|
||||
node = obj.to_tree(child_tag)
|
||||
if node is not None:
|
||||
el.append(node)
|
||||
return el
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
for attr in self.__attrs__:
|
||||
value = getattr(self, attr)
|
||||
if attr.startswith("_"):
|
||||
attr = attr[1:]
|
||||
elif attr != "attr_text" and "_" in attr:
|
||||
desc = getattr(self.__class__, attr)
|
||||
if getattr(desc, "hyphenated", False):
|
||||
attr = attr.replace("_", "-")
|
||||
if attr != "attr_text" and value is not None:
|
||||
yield attr, safe_string(value)
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if not self.__class__ == other.__class__:
|
||||
return False
|
||||
elif not dict(self) == dict(other):
|
||||
return False
|
||||
for el in self.__elements__:
|
||||
if getattr(self, el) != getattr(other, el):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
s = u"<{0}.{1} object>\nParameters:".format(
|
||||
self.__module__,
|
||||
self.__class__.__name__
|
||||
)
|
||||
args = []
|
||||
for k in self.__attrs__ + self.__elements__:
|
||||
v = getattr(self, k)
|
||||
if isinstance(v, Descriptor):
|
||||
v = None
|
||||
args.append(u"{0}={1}".format(k, repr(v)))
|
||||
args = u", ".join(args)
|
||||
|
||||
return u"\n".join([s, args])
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
fields = []
|
||||
for attr in self.__attrs__ + self.__elements__:
|
||||
val = getattr(self, attr)
|
||||
if isinstance(val, list):
|
||||
val = tuple(val)
|
||||
fields.append(val)
|
||||
|
||||
return hash(tuple(fields))
|
||||
|
||||
|
||||
def __add__(self, other):
|
||||
if type(self) != type(other):
|
||||
raise TypeError("Cannot combine instances of different types")
|
||||
vals = {}
|
||||
for attr in self.__attrs__:
|
||||
vals[attr] = getattr(self, attr) or getattr(other, attr)
|
||||
for el in self.__elements__:
|
||||
a = getattr(self, el)
|
||||
b = getattr(other, el)
|
||||
if a and b:
|
||||
vals[el] = a + b
|
||||
else:
|
||||
vals[el] = a or b
|
||||
return self.__class__(**vals)
|
||||
|
||||
|
||||
def __copy__(self):
|
||||
# serialise to xml and back to avoid shallow copies
|
||||
xml = self.to_tree(tagname="dummy")
|
||||
cp = self.__class__.from_tree(xml)
|
||||
# copy any non-persisted attributed
|
||||
for k in self.__dict__:
|
||||
if k not in self.__attrs__ + self.__elements__:
|
||||
v = copy(getattr(self, k))
|
||||
setattr(cp, k, v)
|
||||
return cp
|
Reference in New Issue
Block a user