1# Copyright (c) 2010-2024 openpyxl
2
3from openpyxl.compat import safe_string
4from openpyxl.xml.functions import Element
5from openpyxl.utils.indexed_list import IndexedList
6
7from .base import Descriptor, Alias, _convert
8from .namespace import namespaced
9
10
11class Sequence(Descriptor):
12 """
13 A sequence (list or tuple) that may only contain objects of the declared
14 type
15 """
16
17 expected_type = type(None)
18 seq_types = (list, tuple)
19 idx_base = 0
20 unique = False
21 container = list
22
23
24 def __set__(self, instance, seq):
25 if not isinstance(seq, self.seq_types):
26 raise TypeError("Value must be a sequence")
27 seq = self.container(_convert(self.expected_type, value) for value in seq)
28 if self.unique:
29 seq = IndexedList(seq)
30
31 super().__set__(instance, seq)
32
33
34 def to_tree(self, tagname, obj, namespace=None):
35 """
36 Convert the sequence represented by the descriptor to an XML element
37 """
38 for idx, v in enumerate(obj, self.idx_base):
39 if hasattr(v, "to_tree"):
40 el = v.to_tree(tagname, idx)
41 else:
42 tagname = namespaced(obj, tagname, namespace)
43 el = Element(tagname)
44 el.text = safe_string(v)
45 yield el
46
47
48class UniqueSequence(Sequence):
49 """
50 Use a set to keep values unique
51 """
52 seq_types = (list, tuple, set)
53 container = set
54
55
56class ValueSequence(Sequence):
57 """
58 A sequence of primitive types that are stored as a single attribute.
59 "val" is the default attribute
60 """
61
62 attribute = "val"
63
64
65 def to_tree(self, tagname, obj, namespace=None):
66 tagname = namespaced(self, tagname, namespace)
67 for v in obj:
68 yield Element(tagname, {self.attribute:safe_string(v)})
69
70
71 def from_tree(self, node):
72
73 return node.get(self.attribute)
74
75
76class NestedSequence(Sequence):
77 """
78 Wrap a sequence in an containing object
79 """
80
81 count = False
82
83 def to_tree(self, tagname, obj, namespace=None):
84 tagname = namespaced(self, tagname, namespace)
85 container = Element(tagname)
86 if self.count:
87 container.set('count', str(len(obj)))
88 for v in obj:
89 container.append(v.to_tree())
90 return container
91
92
93 def from_tree(self, node):
94 return [self.expected_type.from_tree(el) for el in node]
95
96
97class MultiSequence(Sequence):
98 """
99 Sequences can contain objects with different tags
100 """
101
102 def __set__(self, instance, seq):
103 if not isinstance(seq, (tuple, list)):
104 raise ValueError("Value must be a sequence")
105 seq = list(seq)
106 Descriptor.__set__(self, instance, seq)
107
108
109 def to_tree(self, tagname, obj, namespace=None):
110 """
111 Convert the sequence represented by the descriptor to an XML element
112 """
113 for v in obj:
114 el = v.to_tree(namespace=namespace)
115 yield el
116
117
118class MultiSequencePart(Alias):
119 """
120 Allow a multisequence to be built up from parts
121
122 Excluded from the instance __elements__ or __attrs__ as is effectively an Alias
123 """
124
125 def __init__(self, expected_type, store):
126 self.expected_type = expected_type
127 self.store = store
128
129
130 def __set__(self, instance, value):
131 value = _convert(self.expected_type, value)
132 instance.__dict__[self.store].append(value)
133
134
135 def __get__(self, instance, cls):
136 return self