# Source code for openpyxl.descriptors.serialisable

    # 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