aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/openpyxl/descriptors
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/descriptors')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py58
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py272
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py41
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py112
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py12
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py129
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py136
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py240
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py18
9 files changed, 1018 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py
new file mode 100644
index 00000000..df86a3c7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py
@@ -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)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py
new file mode 100644
index 00000000..f1e86ed3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py
@@ -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)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py
new file mode 100644
index 00000000..4b1839f5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py
@@ -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)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py
new file mode 100644
index 00000000..d8aa2028
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py
@@ -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))
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py
new file mode 100644
index 00000000..93cc9e41
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py
@@ -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
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py
new file mode 100644
index 00000000..bda63a2d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py
@@ -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)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py
new file mode 100644
index 00000000..d77116b2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py
@@ -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
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py
new file mode 100644
index 00000000..1bc9ef0d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py
@@ -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
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py
new file mode 100644
index 00000000..cadc1ef3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py
@@ -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)