1# Copyright (c) 2010-2024 openpyxl
2
3## Incomplete!
4from openpyxl.descriptors.serialisable import Serialisable
5from openpyxl.descriptors import (
6 Typed,
7 Integer,
8 Set,
9 String,
10 Bool,
11)
12from openpyxl.descriptors.excel import Guid, ExtensionList
13from openpyxl.descriptors.sequence import NestedSequence
14
15from openpyxl.utils.indexed_list import IndexedList
16from openpyxl.xml.constants import SHEET_MAIN_NS
17
18from openpyxl.cell.text import Text
19from .author import AuthorList
20from .comments import Comment
21from .shape_writer import ShapeWriter
22
23
24class Properties(Serialisable):
25
26 locked = Bool(allow_none=True)
27 defaultSize = Bool(allow_none=True)
28 _print = Bool(allow_none=True)
29 disabled = Bool(allow_none=True)
30 uiObject = Bool(allow_none=True)
31 autoFill = Bool(allow_none=True)
32 autoLine = Bool(allow_none=True)
33 altText = String(allow_none=True)
34 textHAlign = Set(values=(['left', 'center', 'right', 'justify', 'distributed']))
35 textVAlign = Set(values=(['top', 'center', 'bottom', 'justify', 'distributed']))
36 lockText = Bool(allow_none=True)
37 justLastX = Bool(allow_none=True)
38 autoScale = Bool(allow_none=True)
39 rowHidden = Bool(allow_none=True)
40 colHidden = Bool(allow_none=True)
41 # anchor = Typed(expected_type=ObjectAnchor, )
42
43 __elements__ = ('anchor',)
44
45 def __init__(self,
46 locked=None,
47 defaultSize=None,
48 _print=None,
49 disabled=None,
50 uiObject=None,
51 autoFill=None,
52 autoLine=None,
53 altText=None,
54 textHAlign=None,
55 textVAlign=None,
56 lockText=None,
57 justLastX=None,
58 autoScale=None,
59 rowHidden=None,
60 colHidden=None,
61 anchor=None,
62 ):
63 self.locked = locked
64 self.defaultSize = defaultSize
65 self._print = _print
66 self.disabled = disabled
67 self.uiObject = uiObject
68 self.autoFill = autoFill
69 self.autoLine = autoLine
70 self.altText = altText
71 self.textHAlign = textHAlign
72 self.textVAlign = textVAlign
73 self.lockText = lockText
74 self.justLastX = justLastX
75 self.autoScale = autoScale
76 self.rowHidden = rowHidden
77 self.colHidden = colHidden
78 self.anchor = anchor
79
80
81class CommentRecord(Serialisable):
82
83 tagname = "comment"
84
85 ref = String()
86 authorId = Integer()
87 guid = Guid(allow_none=True)
88 shapeId = Integer(allow_none=True)
89 text = Typed(expected_type=Text)
90 commentPr = Typed(expected_type=Properties, allow_none=True)
91 author = String(allow_none=True)
92
93 __elements__ = ('text', 'commentPr')
94 __attrs__ = ('ref', 'authorId', 'guid', 'shapeId')
95
96 def __init__(self,
97 ref="",
98 authorId=0,
99 guid=None,
100 shapeId=0,
101 text=None,
102 commentPr=None,
103 author=None,
104 height=79,
105 width=144
106 ):
107 self.ref = ref
108 self.authorId = authorId
109 self.guid = guid
110 self.shapeId = shapeId
111 if text is None:
112 text = Text()
113 self.text = text
114 self.commentPr = commentPr
115 self.author = author
116 self.height = height
117 self.width = width
118
119
120 @classmethod
121 def from_cell(cls, cell):
122 """
123 Class method to convert cell comment
124 """
125 comment = cell._comment
126 ref = cell.coordinate
127 self = cls(ref=ref, author=comment.author)
128 self.text.t = comment.content
129 self.height = comment.height
130 self.width = comment.width
131 return self
132
133
134 @property
135 def content(self):
136 """
137 Remove all inline formatting and stuff
138 """
139 return self.text.content
140
141
142class CommentSheet(Serialisable):
143
144 tagname = "comments"
145
146 authors = Typed(expected_type=AuthorList)
147 commentList = NestedSequence(expected_type=CommentRecord, count=0)
148 extLst = Typed(expected_type=ExtensionList, allow_none=True)
149
150 _id = None
151 _path = "/xl/comments/comment{0}.xml"
152 mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
153 _rel_type = "comments"
154 _rel_id = None
155
156 __elements__ = ('authors', 'commentList')
157
158 def __init__(self,
159 authors=None,
160 commentList=None,
161 extLst=None,
162 ):
163 self.authors = authors
164 self.commentList = commentList
165
166
167 def to_tree(self):
168 tree = super().to_tree()
169 tree.set("xmlns", SHEET_MAIN_NS)
170 return tree
171
172
173 @property
174 def comments(self):
175 """
176 Return a dictionary of comments keyed by coord
177 """
178 authors = self.authors.author
179
180 for c in self.commentList:
181 yield c.ref, Comment(c.content, authors[c.authorId], c.height, c.width)
182
183
184 @classmethod
185 def from_comments(cls, comments):
186 """
187 Create a comment sheet from a list of comments for a particular worksheet
188 """
189 authors = IndexedList()
190
191 # dedupe authors and get indexes
192 for comment in comments:
193 comment.authorId = authors.add(comment.author)
194
195 return cls(authors=AuthorList(authors), commentList=comments)
196
197
198 def write_shapes(self, vml=None):
199 """
200 Create the VML for comments
201 """
202 sw = ShapeWriter(self.comments)
203 return sw.write(vml)
204
205
206 @property
207 def path(self):
208 """
209 Return path within the archive
210 """
211 return self._path.format(self._id)