Source code for fastpyxl.workbook.child
# Copyright (c) 2010-2024 fastpyxl
import re
import warnings
from fastpyxl.worksheet.header_footer import HeaderFooter
"""
Base class for worksheets, chartsheets, etc. that can be added to workbooks
"""
INVALID_TITLE_REGEX = re.compile(r'[\\*?:/\[\]]')
[docs]
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):
parent = self._parent
assert parent is not None
return 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)
parent = self._parent
old_title = self.__title
if old_title is not None and old_title != value:
idx = getattr(parent, "_sheet_titles_lower", None)
if idx is not None:
vl = value.lower()
conflict = vl in idx
if self in parent._sheets and old_title:
if conflict and vl == old_title.lower():
conflict = False
if conflict:
value = avoid_duplicate_name(parent.sheetnames, value)
else:
value = avoid_duplicate_name(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
idx = getattr(parent, "_sheet_titles_lower", None)
if idx is not None and self in parent._sheets:
if old_title:
idx.discard(old_title.lower())
idx.add(value.lower())
parent._sheetnames_cache = None
parent._sheet_title_map = None
@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)