1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
# Copyright (c) 2010-2024 openpyxl
import re
import warnings
from openpyxl.worksheet.header_footer import HeaderFooter
"""
Base class for worksheets, chartsheets, etc. that can be added to workbooks
"""
INVALID_TITLE_REGEX = re.compile(r'[\\*?:/\[\]]')
def avoid_duplicate_name(names, value):
"""
Naive check to see whether name already exists.
If name does exist suggest a name using an incrementer
Duplicates are case insensitive
"""
# Check for an absolute match in which case we need to find an alternative
match = [n for n in names if n.lower() == value.lower()]
if match:
names = u",".join(names)
sheet_title_regex = re.compile(f'(?P<title>{re.escape(value)})(?P<count>\\d*),?', re.I)
matches = sheet_title_regex.findall(names)
if matches:
# use name, but append with the next highest integer
counts = [int(idx) for (t, idx) in matches if idx.isdigit()]
highest = 0
if counts:
highest = max(counts)
value = u"{0}{1}".format(value, highest + 1)
return value
class _WorkbookChild:
__title = ""
_id = None
_path = "{0}"
_parent = None
_default_title = "Sheet"
def __init__(self, parent=None, title=None):
self._parent = parent
self.title = title or self._default_title
self.HeaderFooter = HeaderFooter()
def __repr__(self):
return '<{0} "{1}">'.format(self.__class__.__name__, self.title)
@property
def parent(self):
return self._parent
@property
def encoding(self):
return self._parent.encoding
@property
def title(self):
return self.__title
@title.setter
def title(self, value):
"""
Set a sheet title, ensuring it is valid.
Limited to 31 characters, no special characters.
Duplicate titles will be incremented numerically
"""
if not self._parent:
return
if not value:
raise ValueError("Title must have at least one character")
if hasattr(value, "decode"):
if not isinstance(value, str):
try:
value = value.decode("ascii")
except UnicodeDecodeError:
raise ValueError("Worksheet titles must be str")
m = INVALID_TITLE_REGEX.search(value)
if m:
msg = "Invalid character {0} found in sheet title".format(m.group(0))
raise ValueError(msg)
if self.title is not None and self.title != value:
value = avoid_duplicate_name(self.parent.sheetnames, value)
if len(value) > 31:
warnings.warn("Title is more than 31 characters. Some applications may not be able to read the file")
self.__title = value
@property
def oddHeader(self):
return self.HeaderFooter.oddHeader
@oddHeader.setter
def oddHeader(self, value):
self.HeaderFooter.oddHeader = value
@property
def oddFooter(self):
return self.HeaderFooter.oddFooter
@oddFooter.setter
def oddFooter(self, value):
self.HeaderFooter.oddFooter = value
@property
def evenHeader(self):
return self.HeaderFooter.evenHeader
@evenHeader.setter
def evenHeader(self, value):
self.HeaderFooter.evenHeader = value
@property
def evenFooter(self):
return self.HeaderFooter.evenFooter
@evenFooter.setter
def evenFooter(self, value):
self.HeaderFooter.evenFooter = value
@property
def firstHeader(self):
return self.HeaderFooter.firstHeader
@firstHeader.setter
def firstHeader(self, value):
self.HeaderFooter.firstHeader = value
@property
def firstFooter(self):
return self.HeaderFooter.firstFooter
@firstFooter.setter
def firstFooter(self, value):
self.HeaderFooter.firstFooter = value
@property
def path(self):
return self._path.format(self._id)
|