Updated script that can be controled by Nodejs web app
This commit is contained in:
@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from .base import *
|
||||
from .sequence import Sequence
|
||||
|
||||
|
||||
class MetaStrict(type):
|
||||
|
||||
def __new__(cls, clsname, bases, methods):
|
||||
for k, v in methods.items():
|
||||
if isinstance(v, Descriptor):
|
||||
v.name = k
|
||||
return type.__new__(cls, clsname, bases, methods)
|
||||
|
||||
|
||||
class Strict(metaclass=MetaStrict):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MetaSerialisable(type):
|
||||
|
||||
def __new__(cls, clsname, bases, methods):
|
||||
attrs = []
|
||||
nested = []
|
||||
elements = []
|
||||
namespaced = []
|
||||
for k, v in methods.items():
|
||||
if isinstance(v, Descriptor):
|
||||
ns= getattr(v, 'namespace', None)
|
||||
if ns:
|
||||
namespaced.append((k, "{%s}%s" % (ns, k)))
|
||||
if getattr(v, 'nested', False):
|
||||
nested.append(k)
|
||||
elements.append(k)
|
||||
elif isinstance(v, Sequence):
|
||||
elements.append(k)
|
||||
elif isinstance(v, Typed):
|
||||
if hasattr(v.expected_type, 'to_tree'):
|
||||
elements.append(k)
|
||||
elif isinstance(v.expected_type, tuple):
|
||||
if any((hasattr(el, "to_tree") for el in v.expected_type)):
|
||||
# don't bind elements as attrs
|
||||
continue
|
||||
else:
|
||||
attrs.append(k)
|
||||
else:
|
||||
if not isinstance(v, Alias):
|
||||
attrs.append(k)
|
||||
|
||||
if methods.get('__attrs__') is None:
|
||||
methods['__attrs__'] = tuple(attrs)
|
||||
methods['__namespaced__'] = tuple(namespaced)
|
||||
if methods.get('__nested__') is None:
|
||||
methods['__nested__'] = tuple(sorted(nested))
|
||||
if methods.get('__elements__') is None:
|
||||
methods['__elements__'] = tuple(sorted(elements))
|
||||
return MetaStrict.__new__(cls, clsname, bases, methods)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
272
lib/python3.13/site-packages/openpyxl/descriptors/base.py
Normal file
272
lib/python3.13/site-packages/openpyxl/descriptors/base.py
Normal file
@ -0,0 +1,272 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
"""
|
||||
Based on Python Cookbook 3rd Edition, 8.13
|
||||
http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from openpyxl import DEBUG
|
||||
from openpyxl.utils.datetime import from_ISO8601
|
||||
|
||||
from .namespace import namespaced
|
||||
|
||||
class Descriptor:
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
self.name = name
|
||||
for k, v in kw.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
instance.__dict__[self.name] = value
|
||||
|
||||
|
||||
class Typed(Descriptor):
|
||||
"""Values must of a particular type"""
|
||||
|
||||
expected_type = type(None)
|
||||
allow_none = False
|
||||
nested = False
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
self.__doc__ = f"Values must be of type {self.expected_type}"
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if not isinstance(value, self.expected_type):
|
||||
if (not self.allow_none
|
||||
or (self.allow_none and value is not None)):
|
||||
msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but value is {type(value)}"
|
||||
if DEBUG:
|
||||
msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but {value} is {type(value)}"
|
||||
raise TypeError(msg)
|
||||
super().__set__(instance, value)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__doc__
|
||||
|
||||
|
||||
def _convert(expected_type, value):
|
||||
"""
|
||||
Check value is of or can be converted to expected type.
|
||||
"""
|
||||
if not isinstance(value, expected_type):
|
||||
try:
|
||||
value = expected_type(value)
|
||||
except:
|
||||
raise TypeError('expected ' + str(expected_type))
|
||||
return value
|
||||
|
||||
|
||||
class Convertible(Typed):
|
||||
"""Values must be convertible to a particular type"""
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if ((self.allow_none and value is not None)
|
||||
or not self.allow_none):
|
||||
value = _convert(self.expected_type, value)
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Max(Convertible):
|
||||
"""Values must be less than a `max` value"""
|
||||
|
||||
expected_type = float
|
||||
allow_none = False
|
||||
|
||||
def __init__(self, **kw):
|
||||
if 'max' not in kw and not hasattr(self, 'max'):
|
||||
raise TypeError('missing max value')
|
||||
super().__init__(**kw)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if ((self.allow_none and value is not None)
|
||||
or not self.allow_none):
|
||||
value = _convert(self.expected_type, value)
|
||||
if value > self.max:
|
||||
raise ValueError('Max value is {0}'.format(self.max))
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Min(Convertible):
|
||||
"""Values must be greater than a `min` value"""
|
||||
|
||||
expected_type = float
|
||||
allow_none = False
|
||||
|
||||
def __init__(self, **kw):
|
||||
if 'min' not in kw and not hasattr(self, 'min'):
|
||||
raise TypeError('missing min value')
|
||||
super().__init__(**kw)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if ((self.allow_none and value is not None)
|
||||
or not self.allow_none):
|
||||
value = _convert(self.expected_type, value)
|
||||
if value < self.min:
|
||||
raise ValueError('Min value is {0}'.format(self.min))
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class MinMax(Min, Max):
|
||||
"""Values must be greater than `min` value and less than a `max` one"""
|
||||
pass
|
||||
|
||||
|
||||
class Set(Descriptor):
|
||||
"""Value can only be from a set of know values"""
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
if not 'values' in kw:
|
||||
raise TypeError("missing set of values")
|
||||
kw['values'] = set(kw['values'])
|
||||
super().__init__(name, **kw)
|
||||
self.__doc__ = "Value must be one of {0}".format(self.values)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value not in self.values:
|
||||
raise ValueError(self.__doc__)
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class NoneSet(Set):
|
||||
|
||||
"""'none' will be treated as None"""
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
super().__init__(name, **kw)
|
||||
self.values.add(None)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value == 'none':
|
||||
value = None
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Integer(Convertible):
|
||||
|
||||
expected_type = int
|
||||
|
||||
|
||||
class Float(Convertible):
|
||||
|
||||
expected_type = float
|
||||
|
||||
|
||||
class Bool(Convertible):
|
||||
|
||||
expected_type = bool
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if isinstance(value, str):
|
||||
if value in ('false', 'f', '0'):
|
||||
value = False
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class String(Typed):
|
||||
|
||||
expected_type = str
|
||||
|
||||
|
||||
class Text(String, Convertible):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ASCII(Typed):
|
||||
|
||||
expected_type = bytes
|
||||
|
||||
|
||||
class Tuple(Typed):
|
||||
|
||||
expected_type = tuple
|
||||
|
||||
|
||||
class Length(Descriptor):
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
if "length" not in kw:
|
||||
raise TypeError("value length must be supplied")
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if len(value) != self.length:
|
||||
raise ValueError("Value must be length {0}".format(self.length))
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Default(Typed):
|
||||
"""
|
||||
When called returns an instance of the expected type.
|
||||
Additional default values can be passed in to the descriptor
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
if "defaults" not in kw:
|
||||
kw['defaults'] = {}
|
||||
super().__init__(**kw)
|
||||
|
||||
def __call__(self):
|
||||
return self.expected_type()
|
||||
|
||||
|
||||
class Alias(Descriptor):
|
||||
"""
|
||||
Aliases can be used when either the desired attribute name is not allowed
|
||||
or confusing in Python (eg. "type") or a more descriptive name is desired
|
||||
(eg. "underline" for "u")
|
||||
"""
|
||||
|
||||
def __init__(self, alias):
|
||||
self.alias = alias
|
||||
|
||||
def __set__(self, instance, value):
|
||||
setattr(instance, self.alias, value)
|
||||
|
||||
def __get__(self, instance, cls):
|
||||
return getattr(instance, self.alias)
|
||||
|
||||
|
||||
class MatchPattern(Descriptor):
|
||||
"""Values must match a regex pattern """
|
||||
allow_none = False
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
if 'pattern' not in kw and not hasattr(self, 'pattern'):
|
||||
raise TypeError('missing pattern value')
|
||||
|
||||
super().__init__(name, **kw)
|
||||
self.test_pattern = re.compile(self.pattern, re.VERBOSE)
|
||||
|
||||
|
||||
def __set__(self, instance, value):
|
||||
|
||||
if value is None and not self.allow_none:
|
||||
raise ValueError("Value must not be none")
|
||||
|
||||
if ((self.allow_none and value is not None)
|
||||
or not self.allow_none):
|
||||
if not self.test_pattern.match(value):
|
||||
raise ValueError('Value does not match pattern {0}'.format(self.pattern))
|
||||
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class DateTime(Typed):
|
||||
|
||||
expected_type = datetime.datetime
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value is not None and isinstance(value, str):
|
||||
try:
|
||||
value = from_ISO8601(value)
|
||||
except ValueError:
|
||||
raise ValueError("Value must be ISO datetime format")
|
||||
super().__set__(instance, value)
|
@ -0,0 +1,41 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Utility list for top level containers that contain one type of element
|
||||
|
||||
Provides the necessary API to read and write XML
|
||||
"""
|
||||
|
||||
from openpyxl.xml.functions import Element
|
||||
|
||||
|
||||
class ElementList(list):
|
||||
|
||||
|
||||
@property
|
||||
def tagname(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@property
|
||||
def expected_type(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, tree):
|
||||
l = [cls.expected_type.from_tree(el) for el in tree]
|
||||
return cls(l)
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
container = Element(self.tagname)
|
||||
for el in self:
|
||||
container.append(el.to_tree())
|
||||
return container
|
||||
|
||||
|
||||
def append(self, value):
|
||||
if not isinstance(value, self.expected_type):
|
||||
raise TypeError(f"Value must of type {self.expected_type} {type(value)} provided")
|
||||
super().append(value)
|
112
lib/python3.13/site-packages/openpyxl/descriptors/excel.py
Normal file
112
lib/python3.13/site-packages/openpyxl/descriptors/excel.py
Normal file
@ -0,0 +1,112 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Excel specific descriptors
|
||||
"""
|
||||
|
||||
from openpyxl.xml.constants import REL_NS
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import Element
|
||||
|
||||
from . import (
|
||||
MatchPattern,
|
||||
MinMax,
|
||||
Integer,
|
||||
String,
|
||||
Sequence,
|
||||
)
|
||||
from .serialisable import Serialisable
|
||||
|
||||
|
||||
class HexBinary(MatchPattern):
|
||||
|
||||
pattern = "[0-9a-fA-F]+$"
|
||||
|
||||
|
||||
class UniversalMeasure(MatchPattern):
|
||||
|
||||
pattern = r"[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"
|
||||
|
||||
|
||||
class TextPoint(MinMax):
|
||||
"""
|
||||
Size in hundredths of points.
|
||||
In theory other units of measurement can be used but these are unbounded
|
||||
"""
|
||||
expected_type = int
|
||||
|
||||
min = -400000
|
||||
max = 400000
|
||||
|
||||
|
||||
Coordinate = Integer
|
||||
|
||||
|
||||
class Percentage(MinMax):
|
||||
|
||||
pattern = r"((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%" # strict
|
||||
min = -1000000
|
||||
max = 1000000
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if isinstance(value, str) and "%" in value:
|
||||
value = value.replace("%", "")
|
||||
value = int(float(value) * 1000)
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Extension(Serialisable):
|
||||
|
||||
uri = String()
|
||||
|
||||
def __init__(self,
|
||||
uri=None,
|
||||
):
|
||||
self.uri = uri
|
||||
|
||||
|
||||
class ExtensionList(Serialisable):
|
||||
|
||||
ext = Sequence(expected_type=Extension)
|
||||
|
||||
def __init__(self,
|
||||
ext=(),
|
||||
):
|
||||
self.ext = ext
|
||||
|
||||
|
||||
class Relation(String):
|
||||
|
||||
namespace = REL_NS
|
||||
allow_none = True
|
||||
|
||||
|
||||
class Base64Binary(MatchPattern):
|
||||
# http://www.w3.org/TR/xmlschema11-2/#nt-Base64Binary
|
||||
pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$"
|
||||
|
||||
|
||||
class Guid(MatchPattern):
|
||||
# https://msdn.microsoft.com/en-us/library/dd946381(v=office.12).aspx
|
||||
pattern = r"{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"
|
||||
|
||||
|
||||
class CellRange(MatchPattern):
|
||||
|
||||
pattern = r"^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$"
|
||||
allow_none = True
|
||||
|
||||
def __set__(self, instance, value):
|
||||
|
||||
if value is not None:
|
||||
value = value.upper()
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
def _explicit_none(tagname, value, namespace=None):
|
||||
"""
|
||||
Override serialisation because explicit none required
|
||||
"""
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
return Element(tagname, val=safe_string(value))
|
@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
def namespaced(obj, tagname, namespace=None):
|
||||
"""
|
||||
Utility to create a namespaced tag for an object
|
||||
"""
|
||||
|
||||
namespace = getattr(obj, "namespace", None) or namespace
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
return tagname
|
129
lib/python3.13/site-packages/openpyxl/descriptors/nested.py
Normal file
129
lib/python3.13/site-packages/openpyxl/descriptors/nested.py
Normal file
@ -0,0 +1,129 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Generic serialisable classes
|
||||
"""
|
||||
from .base import (
|
||||
Convertible,
|
||||
Bool,
|
||||
Descriptor,
|
||||
NoneSet,
|
||||
MinMax,
|
||||
Set,
|
||||
Float,
|
||||
Integer,
|
||||
String,
|
||||
)
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import Element, localname, whitespace
|
||||
|
||||
|
||||
class Nested(Descriptor):
|
||||
|
||||
nested = True
|
||||
attribute = "val"
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if hasattr(value, "tag"):
|
||||
tag = localname(value)
|
||||
if tag != self.name:
|
||||
raise ValueError("Tag does not match attribute")
|
||||
|
||||
value = self.from_tree(value)
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
return node.get(self.attribute)
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, value=None, namespace=None):
|
||||
namespace = getattr(self, "namespace", namespace)
|
||||
if value is not None:
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
value = safe_string(value)
|
||||
return Element(tagname, {self.attribute:value})
|
||||
|
||||
|
||||
class NestedValue(Nested, Convertible):
|
||||
"""
|
||||
Nested tag storing the value on the 'val' attribute
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NestedText(NestedValue):
|
||||
"""
|
||||
Represents any nested tag with the value as the contents of the tag
|
||||
"""
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
return node.text
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, value=None, namespace=None):
|
||||
namespace = getattr(self, "namespace", namespace)
|
||||
if value is not None:
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
el = Element(tagname)
|
||||
el.text = safe_string(value)
|
||||
whitespace(el)
|
||||
return el
|
||||
|
||||
|
||||
class NestedFloat(NestedValue, Float):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedInteger(NestedValue, Integer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedString(NestedValue, String):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedBool(NestedValue, Bool):
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
return node.get("val", True)
|
||||
|
||||
|
||||
class NestedNoneSet(Nested, NoneSet):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedSet(Nested, Set):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedMinMax(Nested, MinMax):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EmptyTag(Nested, Bool):
|
||||
|
||||
"""
|
||||
Boolean if a tag exists or not.
|
||||
"""
|
||||
|
||||
def from_tree(self, node):
|
||||
return True
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, value=None, namespace=None):
|
||||
if value:
|
||||
namespace = getattr(self, "namespace", namespace)
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
return Element(tagname)
|
136
lib/python3.13/site-packages/openpyxl/descriptors/sequence.py
Normal file
136
lib/python3.13/site-packages/openpyxl/descriptors/sequence.py
Normal file
@ -0,0 +1,136 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import Element
|
||||
from openpyxl.utils.indexed_list import IndexedList
|
||||
|
||||
from .base import Descriptor, Alias, _convert
|
||||
from .namespace import namespaced
|
||||
|
||||
|
||||
class Sequence(Descriptor):
|
||||
"""
|
||||
A sequence (list or tuple) that may only contain objects of the declared
|
||||
type
|
||||
"""
|
||||
|
||||
expected_type = type(None)
|
||||
seq_types = (list, tuple)
|
||||
idx_base = 0
|
||||
unique = False
|
||||
container = list
|
||||
|
||||
|
||||
def __set__(self, instance, seq):
|
||||
if not isinstance(seq, self.seq_types):
|
||||
raise TypeError("Value must be a sequence")
|
||||
seq = self.container(_convert(self.expected_type, value) for value in seq)
|
||||
if self.unique:
|
||||
seq = IndexedList(seq)
|
||||
|
||||
super().__set__(instance, seq)
|
||||
|
||||
|
||||
def to_tree(self, tagname, obj, namespace=None):
|
||||
"""
|
||||
Convert the sequence represented by the descriptor to an XML element
|
||||
"""
|
||||
for idx, v in enumerate(obj, self.idx_base):
|
||||
if hasattr(v, "to_tree"):
|
||||
el = v.to_tree(tagname, idx)
|
||||
else:
|
||||
tagname = namespaced(obj, tagname, namespace)
|
||||
el = Element(tagname)
|
||||
el.text = safe_string(v)
|
||||
yield el
|
||||
|
||||
|
||||
class UniqueSequence(Sequence):
|
||||
"""
|
||||
Use a set to keep values unique
|
||||
"""
|
||||
seq_types = (list, tuple, set)
|
||||
container = set
|
||||
|
||||
|
||||
class ValueSequence(Sequence):
|
||||
"""
|
||||
A sequence of primitive types that are stored as a single attribute.
|
||||
"val" is the default attribute
|
||||
"""
|
||||
|
||||
attribute = "val"
|
||||
|
||||
|
||||
def to_tree(self, tagname, obj, namespace=None):
|
||||
tagname = namespaced(self, tagname, namespace)
|
||||
for v in obj:
|
||||
yield Element(tagname, {self.attribute:safe_string(v)})
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
|
||||
return node.get(self.attribute)
|
||||
|
||||
|
||||
class NestedSequence(Sequence):
|
||||
"""
|
||||
Wrap a sequence in an containing object
|
||||
"""
|
||||
|
||||
count = False
|
||||
|
||||
def to_tree(self, tagname, obj, namespace=None):
|
||||
tagname = namespaced(self, tagname, namespace)
|
||||
container = Element(tagname)
|
||||
if self.count:
|
||||
container.set('count', str(len(obj)))
|
||||
for v in obj:
|
||||
container.append(v.to_tree())
|
||||
return container
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
return [self.expected_type.from_tree(el) for el in node]
|
||||
|
||||
|
||||
class MultiSequence(Sequence):
|
||||
"""
|
||||
Sequences can contain objects with different tags
|
||||
"""
|
||||
|
||||
def __set__(self, instance, seq):
|
||||
if not isinstance(seq, (tuple, list)):
|
||||
raise ValueError("Value must be a sequence")
|
||||
seq = list(seq)
|
||||
Descriptor.__set__(self, instance, seq)
|
||||
|
||||
|
||||
def to_tree(self, tagname, obj, namespace=None):
|
||||
"""
|
||||
Convert the sequence represented by the descriptor to an XML element
|
||||
"""
|
||||
for v in obj:
|
||||
el = v.to_tree(namespace=namespace)
|
||||
yield el
|
||||
|
||||
|
||||
class MultiSequencePart(Alias):
|
||||
"""
|
||||
Allow a multisequence to be built up from parts
|
||||
|
||||
Excluded from the instance __elements__ or __attrs__ as is effectively an Alias
|
||||
"""
|
||||
|
||||
def __init__(self, expected_type, store):
|
||||
self.expected_type = expected_type
|
||||
self.store = store
|
||||
|
||||
|
||||
def __set__(self, instance, value):
|
||||
value = _convert(self.expected_type, value)
|
||||
instance.__dict__[self.store].append(value)
|
||||
|
||||
|
||||
def __get__(self, instance, cls):
|
||||
return self
|
@ -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
|
18
lib/python3.13/site-packages/openpyxl/descriptors/slots.py
Normal file
18
lib/python3.13/site-packages/openpyxl/descriptors/slots.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Metaclass for mixing slots and descriptors
|
||||
# From "Programming in Python 3" by Mark Summerfield Ch.8 p. 383
|
||||
|
||||
class AutoSlotProperties(type):
|
||||
|
||||
def __new__(mcl, classname, bases, dictionary):
|
||||
slots = list(dictionary.get("__slots__", []))
|
||||
for getter_name in [key for key in dictionary if key.startswith("get_")]:
|
||||
name = getter_name
|
||||
slots.append("__" + name)
|
||||
getter = dictionary.pop(getter_name)
|
||||
setter = dictionary.get(setter_name, None)
|
||||
if (setter is not None
|
||||
and isinstance(setter, collections.Callable)):
|
||||
del dictionary[setter_name]
|
||||
dictionary[name] = property(getter. setter)
|
||||
dictionary["__slots__"] = tuple(slots)
|
||||
return super().__new__(mcl, classname, bases, dictionary)
|
Reference in New Issue
Block a user