Updated script that can be controled by Nodejs web app
This commit is contained in:
@ -0,0 +1 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
190
lib/python3.13/site-packages/openpyxl/worksheet/_read_only.py
Normal file
190
lib/python3.13/site-packages/openpyxl/worksheet/_read_only.py
Normal file
@ -0,0 +1,190 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
""" Read worksheets on-demand
|
||||
"""
|
||||
|
||||
from .worksheet import Worksheet
|
||||
from openpyxl.cell.read_only import ReadOnlyCell, EMPTY_CELL
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from ._reader import WorkSheetParser
|
||||
from openpyxl.workbook.defined_name import DefinedNameDict
|
||||
|
||||
|
||||
def read_dimension(source):
|
||||
parser = WorkSheetParser(source, [])
|
||||
return parser.parse_dimensions()
|
||||
|
||||
|
||||
class ReadOnlyWorksheet:
|
||||
|
||||
_min_column = 1
|
||||
_min_row = 1
|
||||
_max_column = _max_row = None
|
||||
|
||||
# from Standard Worksheet
|
||||
# Methods from Worksheet
|
||||
cell = Worksheet.cell
|
||||
iter_rows = Worksheet.iter_rows
|
||||
values = Worksheet.values
|
||||
rows = Worksheet.rows
|
||||
__getitem__ = Worksheet.__getitem__
|
||||
__iter__ = Worksheet.__iter__
|
||||
|
||||
|
||||
def __init__(self, parent_workbook, title, worksheet_path, shared_strings):
|
||||
self.parent = parent_workbook
|
||||
self.title = title
|
||||
self.sheet_state = 'visible'
|
||||
self._current_row = None
|
||||
self._worksheet_path = worksheet_path
|
||||
self._shared_strings = shared_strings
|
||||
self._get_size()
|
||||
self.defined_names = DefinedNameDict()
|
||||
|
||||
|
||||
def _get_size(self):
|
||||
src = self._get_source()
|
||||
parser = WorkSheetParser(src, [])
|
||||
dimensions = parser.parse_dimensions()
|
||||
src.close()
|
||||
if dimensions is not None:
|
||||
self._min_column, self._min_row, self._max_column, self._max_row = dimensions
|
||||
|
||||
|
||||
def _get_source(self):
|
||||
"""Parse xml source on demand, must close after use"""
|
||||
return self.parent._archive.open(self._worksheet_path)
|
||||
|
||||
|
||||
def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False):
|
||||
"""
|
||||
The source worksheet file may have columns or rows missing.
|
||||
Missing cells will be created.
|
||||
"""
|
||||
filler = EMPTY_CELL
|
||||
if values_only:
|
||||
filler = None
|
||||
|
||||
max_col = max_col or self.max_column
|
||||
max_row = max_row or self.max_row
|
||||
empty_row = []
|
||||
if max_col is not None:
|
||||
empty_row = (filler,) * (max_col + 1 - min_col)
|
||||
|
||||
counter = min_row
|
||||
idx = 1
|
||||
with self._get_source() as src:
|
||||
parser = WorkSheetParser(src,
|
||||
self._shared_strings,
|
||||
data_only=self.parent.data_only,
|
||||
epoch=self.parent.epoch,
|
||||
date_formats=self.parent._date_formats,
|
||||
timedelta_formats=self.parent._timedelta_formats)
|
||||
|
||||
for idx, row in parser.parse():
|
||||
if max_row is not None and idx > max_row:
|
||||
break
|
||||
|
||||
# some rows are missing
|
||||
for _ in range(counter, idx):
|
||||
counter += 1
|
||||
yield empty_row
|
||||
|
||||
# return cells from a row
|
||||
if counter <= idx:
|
||||
row = self._get_row(row, min_col, max_col, values_only)
|
||||
counter += 1
|
||||
yield row
|
||||
|
||||
if max_row is not None and max_row < idx:
|
||||
for _ in range(counter, max_row+1):
|
||||
yield empty_row
|
||||
|
||||
|
||||
def _get_row(self, row, min_col=1, max_col=None, values_only=False):
|
||||
"""
|
||||
Make sure a row contains always the same number of cells or values
|
||||
"""
|
||||
if not row and not max_col: # in case someone wants to force rows where there aren't any
|
||||
return ()
|
||||
|
||||
max_col = max_col or row[-1]['column']
|
||||
row_width = max_col + 1 - min_col
|
||||
|
||||
new_row = [EMPTY_CELL] * row_width
|
||||
if values_only:
|
||||
new_row = [None] * row_width
|
||||
|
||||
for cell in row:
|
||||
counter = cell['column']
|
||||
if min_col <= counter <= max_col:
|
||||
idx = counter - min_col # position in list of cells returned
|
||||
new_row[idx] = cell['value']
|
||||
if not values_only:
|
||||
new_row[idx] = ReadOnlyCell(self, **cell)
|
||||
|
||||
return tuple(new_row)
|
||||
|
||||
|
||||
def _get_cell(self, row, column):
|
||||
"""Cells are returned by a generator which can be empty"""
|
||||
for row in self._cells_by_row(column, row, column, row):
|
||||
if row:
|
||||
return row[0]
|
||||
return EMPTY_CELL
|
||||
|
||||
|
||||
def calculate_dimension(self, force=False):
|
||||
if not all([self.max_column, self.max_row]):
|
||||
if force:
|
||||
self._calculate_dimension()
|
||||
else:
|
||||
raise ValueError("Worksheet is unsized, use calculate_dimension(force=True)")
|
||||
return f"{get_column_letter(self.min_column)}{self.min_row}:{get_column_letter(self.max_column)}{self.max_row}"
|
||||
|
||||
|
||||
def _calculate_dimension(self):
|
||||
"""
|
||||
Loop through all the cells to get the size of a worksheet.
|
||||
Do this only if it is explicitly requested.
|
||||
"""
|
||||
|
||||
max_col = 0
|
||||
for r in self.rows:
|
||||
if not r:
|
||||
continue
|
||||
cell = r[-1]
|
||||
max_col = max(max_col, cell.column)
|
||||
|
||||
self._max_row = cell.row
|
||||
self._max_column = max_col
|
||||
|
||||
|
||||
def reset_dimensions(self):
|
||||
"""
|
||||
Remove worksheet dimensions if these are incorrect in the worksheet source.
|
||||
NB. This probably indicates a bug in the library or application that created
|
||||
the workbook.
|
||||
"""
|
||||
self._max_row = self._max_column = None
|
||||
|
||||
|
||||
@property
|
||||
def min_row(self):
|
||||
return self._min_row
|
||||
|
||||
|
||||
@property
|
||||
def max_row(self):
|
||||
return self._max_row
|
||||
|
||||
|
||||
@property
|
||||
def min_column(self):
|
||||
return self._min_column
|
||||
|
||||
|
||||
@property
|
||||
def max_column(self):
|
||||
return self._max_column
|
472
lib/python3.13/site-packages/openpyxl/worksheet/_reader.py
Normal file
472
lib/python3.13/site-packages/openpyxl/worksheet/_reader.py
Normal file
@ -0,0 +1,472 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""Reader for a single worksheet."""
|
||||
from copy import copy
|
||||
from warnings import warn
|
||||
|
||||
# compatibility imports
|
||||
from openpyxl.xml.functions import iterparse
|
||||
|
||||
# package imports
|
||||
from openpyxl.cell import Cell, MergedCell
|
||||
from openpyxl.cell.text import Text
|
||||
from openpyxl.worksheet.dimensions import (
|
||||
ColumnDimension,
|
||||
RowDimension,
|
||||
SheetFormatProperties,
|
||||
)
|
||||
|
||||
from openpyxl.xml.constants import (
|
||||
SHEET_MAIN_NS,
|
||||
EXT_TYPES,
|
||||
)
|
||||
from openpyxl.formatting.formatting import ConditionalFormatting
|
||||
from openpyxl.formula.translate import Translator
|
||||
from openpyxl.utils import (
|
||||
get_column_letter,
|
||||
coordinate_to_tuple,
|
||||
)
|
||||
from openpyxl.utils.datetime import from_excel, from_ISO8601, WINDOWS_EPOCH
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.cell.rich_text import CellRichText
|
||||
|
||||
from .formula import DataTableFormula, ArrayFormula
|
||||
from .filters import AutoFilter
|
||||
from .header_footer import HeaderFooter
|
||||
from .hyperlink import HyperlinkList
|
||||
from .merge import MergeCells
|
||||
from .page import PageMargins, PrintOptions, PrintPageSetup
|
||||
from .pagebreak import RowBreak, ColBreak
|
||||
from .protection import SheetProtection
|
||||
from .scenario import ScenarioList
|
||||
from .views import SheetViewList
|
||||
from .datavalidation import DataValidationList
|
||||
from .table import TablePartList
|
||||
from .properties import WorksheetProperties
|
||||
from .dimensions import SheetDimension
|
||||
from .related import Related
|
||||
|
||||
|
||||
CELL_TAG = '{%s}c' % SHEET_MAIN_NS
|
||||
VALUE_TAG = '{%s}v' % SHEET_MAIN_NS
|
||||
FORMULA_TAG = '{%s}f' % SHEET_MAIN_NS
|
||||
MERGE_TAG = '{%s}mergeCells' % SHEET_MAIN_NS
|
||||
INLINE_STRING = "{%s}is" % SHEET_MAIN_NS
|
||||
COL_TAG = '{%s}col' % SHEET_MAIN_NS
|
||||
ROW_TAG = '{%s}row' % SHEET_MAIN_NS
|
||||
CF_TAG = '{%s}conditionalFormatting' % SHEET_MAIN_NS
|
||||
LEGACY_TAG = '{%s}legacyDrawing' % SHEET_MAIN_NS
|
||||
PROT_TAG = '{%s}sheetProtection' % SHEET_MAIN_NS
|
||||
EXT_TAG = "{%s}extLst" % SHEET_MAIN_NS
|
||||
HYPERLINK_TAG = "{%s}hyperlinks" % SHEET_MAIN_NS
|
||||
TABLE_TAG = "{%s}tableParts" % SHEET_MAIN_NS
|
||||
PRINT_TAG = '{%s}printOptions' % SHEET_MAIN_NS
|
||||
MARGINS_TAG = '{%s}pageMargins' % SHEET_MAIN_NS
|
||||
PAGE_TAG = '{%s}pageSetup' % SHEET_MAIN_NS
|
||||
HEADER_TAG = '{%s}headerFooter' % SHEET_MAIN_NS
|
||||
FILTER_TAG = '{%s}autoFilter' % SHEET_MAIN_NS
|
||||
VALIDATION_TAG = '{%s}dataValidations' % SHEET_MAIN_NS
|
||||
PROPERTIES_TAG = '{%s}sheetPr' % SHEET_MAIN_NS
|
||||
VIEWS_TAG = '{%s}sheetViews' % SHEET_MAIN_NS
|
||||
FORMAT_TAG = '{%s}sheetFormatPr' % SHEET_MAIN_NS
|
||||
ROW_BREAK_TAG = '{%s}rowBreaks' % SHEET_MAIN_NS
|
||||
COL_BREAK_TAG = '{%s}colBreaks' % SHEET_MAIN_NS
|
||||
SCENARIOS_TAG = '{%s}scenarios' % SHEET_MAIN_NS
|
||||
DATA_TAG = '{%s}sheetData' % SHEET_MAIN_NS
|
||||
DIMENSION_TAG = '{%s}dimension' % SHEET_MAIN_NS
|
||||
CUSTOM_VIEWS_TAG = '{%s}customSheetViews' % SHEET_MAIN_NS
|
||||
|
||||
|
||||
def _cast_number(value):
|
||||
"Convert numbers as string to an int or float"
|
||||
if "." in value or "E" in value or "e" in value:
|
||||
return float(value)
|
||||
return int(value)
|
||||
|
||||
|
||||
def parse_richtext_string(element):
|
||||
"""
|
||||
Parse inline string and preserve rich text formatting
|
||||
"""
|
||||
value = CellRichText.from_tree(element) or ""
|
||||
if len(value) == 1 and isinstance(value[0], str):
|
||||
value = value[0]
|
||||
return value
|
||||
|
||||
|
||||
class WorkSheetParser:
|
||||
|
||||
def __init__(self, src, shared_strings, data_only=False,
|
||||
epoch=WINDOWS_EPOCH, date_formats=set(),
|
||||
timedelta_formats=set(), rich_text=False):
|
||||
self.min_row = self.min_col = None
|
||||
self.epoch = epoch
|
||||
self.source = src
|
||||
self.shared_strings = shared_strings
|
||||
self.data_only = data_only
|
||||
self.shared_formulae = {}
|
||||
self.row_counter = self.col_counter = 0
|
||||
self.tables = TablePartList()
|
||||
self.date_formats = date_formats
|
||||
self.timedelta_formats = timedelta_formats
|
||||
self.row_dimensions = {}
|
||||
self.column_dimensions = {}
|
||||
self.number_formats = []
|
||||
self.keep_vba = False
|
||||
self.hyperlinks = HyperlinkList()
|
||||
self.formatting = []
|
||||
self.legacy_drawing = None
|
||||
self.merged_cells = None
|
||||
self.row_breaks = RowBreak()
|
||||
self.col_breaks = ColBreak()
|
||||
self.rich_text = rich_text
|
||||
|
||||
|
||||
def parse(self):
|
||||
dispatcher = {
|
||||
COL_TAG: self.parse_column_dimensions,
|
||||
PROT_TAG: self.parse_sheet_protection,
|
||||
EXT_TAG: self.parse_extensions,
|
||||
CF_TAG: self.parse_formatting,
|
||||
LEGACY_TAG: self.parse_legacy,
|
||||
ROW_BREAK_TAG: self.parse_row_breaks,
|
||||
COL_BREAK_TAG: self.parse_col_breaks,
|
||||
CUSTOM_VIEWS_TAG: self.parse_custom_views,
|
||||
}
|
||||
|
||||
properties = {
|
||||
PRINT_TAG: ('print_options', PrintOptions),
|
||||
MARGINS_TAG: ('page_margins', PageMargins),
|
||||
PAGE_TAG: ('page_setup', PrintPageSetup),
|
||||
HEADER_TAG: ('HeaderFooter', HeaderFooter),
|
||||
FILTER_TAG: ('auto_filter', AutoFilter),
|
||||
VALIDATION_TAG: ('data_validations', DataValidationList),
|
||||
PROPERTIES_TAG: ('sheet_properties', WorksheetProperties),
|
||||
VIEWS_TAG: ('views', SheetViewList),
|
||||
FORMAT_TAG: ('sheet_format', SheetFormatProperties),
|
||||
SCENARIOS_TAG: ('scenarios', ScenarioList),
|
||||
TABLE_TAG: ('tables', TablePartList),
|
||||
HYPERLINK_TAG: ('hyperlinks', HyperlinkList),
|
||||
MERGE_TAG: ('merged_cells', MergeCells),
|
||||
|
||||
}
|
||||
|
||||
it = iterparse(self.source) # add a finaliser to close the source when this becomes possible
|
||||
|
||||
for _, element in it:
|
||||
tag_name = element.tag
|
||||
if tag_name in dispatcher:
|
||||
dispatcher[tag_name](element)
|
||||
element.clear()
|
||||
elif tag_name in properties:
|
||||
prop = properties[tag_name]
|
||||
obj = prop[1].from_tree(element)
|
||||
setattr(self, prop[0], obj)
|
||||
element.clear()
|
||||
elif tag_name == ROW_TAG:
|
||||
row = self.parse_row(element)
|
||||
element.clear()
|
||||
yield row
|
||||
|
||||
|
||||
def parse_dimensions(self):
|
||||
"""
|
||||
Get worksheet dimensions if they are provided.
|
||||
"""
|
||||
it = iterparse(self.source)
|
||||
|
||||
for _event, element in it:
|
||||
if element.tag == DIMENSION_TAG:
|
||||
dim = SheetDimension.from_tree(element)
|
||||
return dim.boundaries
|
||||
|
||||
elif element.tag == DATA_TAG:
|
||||
# Dimensions missing
|
||||
break
|
||||
element.clear()
|
||||
|
||||
|
||||
def parse_cell(self, element):
|
||||
data_type = element.get('t', 'n')
|
||||
coordinate = element.get('r')
|
||||
style_id = element.get('s', 0)
|
||||
if style_id:
|
||||
style_id = int(style_id)
|
||||
|
||||
if data_type == "inlineStr":
|
||||
value = None
|
||||
else:
|
||||
value = element.findtext(VALUE_TAG, None) or None
|
||||
|
||||
if coordinate:
|
||||
row, column = coordinate_to_tuple(coordinate)
|
||||
self.col_counter = column
|
||||
else:
|
||||
self.col_counter += 1
|
||||
row, column = self.row_counter, self.col_counter
|
||||
|
||||
if not self.data_only and element.find(FORMULA_TAG) is not None:
|
||||
data_type = 'f'
|
||||
value = self.parse_formula(element)
|
||||
|
||||
elif value is not None:
|
||||
if data_type == 'n':
|
||||
value = _cast_number(value)
|
||||
if style_id in self.date_formats:
|
||||
data_type = 'd'
|
||||
try:
|
||||
value = from_excel(
|
||||
value, self.epoch, timedelta=style_id in self.timedelta_formats
|
||||
)
|
||||
except (OverflowError, ValueError):
|
||||
msg = f"""Cell {coordinate} is marked as a date but the serial value {value} is outside the limits for dates. The cell will be treated as an error."""
|
||||
warn(msg)
|
||||
data_type = "e"
|
||||
value = "#VALUE!"
|
||||
elif data_type == 's':
|
||||
value = self.shared_strings[int(value)]
|
||||
elif data_type == 'b':
|
||||
value = bool(int(value))
|
||||
elif data_type == "str":
|
||||
data_type = "s"
|
||||
elif data_type == 'd':
|
||||
value = from_ISO8601(value)
|
||||
|
||||
elif data_type == 'inlineStr':
|
||||
child = element.find(INLINE_STRING)
|
||||
if child is not None:
|
||||
data_type = 's'
|
||||
if self.rich_text:
|
||||
value = parse_richtext_string(child)
|
||||
else:
|
||||
value = Text.from_tree(child).content
|
||||
|
||||
return {'row':row, 'column':column, 'value':value, 'data_type':data_type, 'style_id':style_id}
|
||||
|
||||
|
||||
def parse_formula(self, element):
|
||||
"""
|
||||
possible formulae types: shared, array, datatable
|
||||
"""
|
||||
formula = element.find(FORMULA_TAG)
|
||||
formula_type = formula.get('t')
|
||||
coordinate = element.get('r')
|
||||
value = "="
|
||||
if formula.text is not None:
|
||||
value += formula.text
|
||||
|
||||
if formula_type == "array":
|
||||
value = ArrayFormula(ref=formula.get('ref'), text=value)
|
||||
|
||||
elif formula_type == "shared":
|
||||
idx = formula.get('si')
|
||||
if idx in self.shared_formulae:
|
||||
trans = self.shared_formulae[idx]
|
||||
value = trans.translate_formula(coordinate)
|
||||
elif value != "=":
|
||||
self.shared_formulae[idx] = Translator(value, coordinate)
|
||||
|
||||
elif formula_type == "dataTable":
|
||||
value = DataTableFormula(**formula.attrib)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def parse_column_dimensions(self, col):
|
||||
attrs = dict(col.attrib)
|
||||
column = get_column_letter(int(attrs['min']))
|
||||
attrs['index'] = column
|
||||
self.column_dimensions[column] = attrs
|
||||
|
||||
|
||||
def parse_row(self, row):
|
||||
attrs = dict(row.attrib)
|
||||
|
||||
if "r" in attrs:
|
||||
try:
|
||||
self.row_counter = int(attrs['r'])
|
||||
except ValueError:
|
||||
val = float(attrs['r'])
|
||||
if val.is_integer():
|
||||
self.row_counter = int(val)
|
||||
else:
|
||||
raise ValueError(f"{attrs['r']} is not a valid row number")
|
||||
else:
|
||||
self.row_counter += 1
|
||||
self.col_counter = 0
|
||||
|
||||
keys = {k for k in attrs if not k.startswith('{')}
|
||||
if keys - {'r', 'spans'}:
|
||||
# don't create dimension objects unless they have relevant information
|
||||
self.row_dimensions[str(self.row_counter)] = attrs
|
||||
|
||||
cells = [self.parse_cell(el) for el in row]
|
||||
return self.row_counter, cells
|
||||
|
||||
|
||||
def parse_formatting(self, element):
|
||||
try:
|
||||
cf = ConditionalFormatting.from_tree(element)
|
||||
self.formatting.append(cf)
|
||||
except TypeError as e:
|
||||
msg = f"Failed to load a conditional formatting rule. It will be discarded. Cause: {e}"
|
||||
warn(msg)
|
||||
|
||||
|
||||
def parse_sheet_protection(self, element):
|
||||
protection = SheetProtection.from_tree(element)
|
||||
password = element.get("password")
|
||||
if password is not None:
|
||||
protection.set_password(password, True)
|
||||
self.protection = protection
|
||||
|
||||
|
||||
def parse_extensions(self, element):
|
||||
extLst = ExtensionList.from_tree(element)
|
||||
for e in extLst.ext:
|
||||
ext_type = EXT_TYPES.get(e.uri.upper(), "Unknown")
|
||||
msg = "{0} extension is not supported and will be removed".format(ext_type)
|
||||
warn(msg)
|
||||
|
||||
|
||||
def parse_legacy(self, element):
|
||||
obj = Related.from_tree(element)
|
||||
self.legacy_drawing = obj.id
|
||||
|
||||
|
||||
def parse_row_breaks(self, element):
|
||||
brk = RowBreak.from_tree(element)
|
||||
self.row_breaks = brk
|
||||
|
||||
|
||||
def parse_col_breaks(self, element):
|
||||
brk = ColBreak.from_tree(element)
|
||||
self.col_breaks = brk
|
||||
|
||||
|
||||
def parse_custom_views(self, element):
|
||||
# clear page_breaks to avoid duplication which Excel doesn't like
|
||||
# basically they're ignored in custom views
|
||||
self.row_breaks = RowBreak()
|
||||
self.col_breaks = ColBreak()
|
||||
|
||||
|
||||
class WorksheetReader:
|
||||
"""
|
||||
Create a parser and apply it to a workbook
|
||||
"""
|
||||
|
||||
def __init__(self, ws, xml_source, shared_strings, data_only, rich_text):
|
||||
self.ws = ws
|
||||
self.parser = WorkSheetParser(xml_source, shared_strings,
|
||||
data_only, ws.parent.epoch, ws.parent._date_formats,
|
||||
ws.parent._timedelta_formats, rich_text)
|
||||
self.tables = []
|
||||
|
||||
|
||||
def bind_cells(self):
|
||||
for idx, row in self.parser.parse():
|
||||
for cell in row:
|
||||
style = self.ws.parent._cell_styles[cell['style_id']]
|
||||
c = Cell(self.ws, row=cell['row'], column=cell['column'], style_array=style)
|
||||
c._value = cell['value']
|
||||
c.data_type = cell['data_type']
|
||||
self.ws._cells[(cell['row'], cell['column'])] = c
|
||||
|
||||
if self.ws._cells:
|
||||
self.ws._current_row = self.ws.max_row # use cells not row dimensions
|
||||
|
||||
|
||||
def bind_formatting(self):
|
||||
for cf in self.parser.formatting:
|
||||
for rule in cf.rules:
|
||||
if rule.dxfId is not None:
|
||||
rule.dxf = self.ws.parent._differential_styles[rule.dxfId]
|
||||
self.ws.conditional_formatting[cf] = rule
|
||||
|
||||
|
||||
def bind_tables(self):
|
||||
for t in self.parser.tables.tablePart:
|
||||
rel = self.ws._rels.get(t.id)
|
||||
self.tables.append(rel.Target)
|
||||
|
||||
|
||||
def bind_merged_cells(self):
|
||||
from openpyxl.worksheet.cell_range import MultiCellRange
|
||||
from openpyxl.worksheet.merge import MergedCellRange
|
||||
if not self.parser.merged_cells:
|
||||
return
|
||||
|
||||
ranges = []
|
||||
for cr in self.parser.merged_cells.mergeCell:
|
||||
mcr = MergedCellRange(self.ws, cr.ref)
|
||||
self.ws._clean_merge_range(mcr)
|
||||
ranges.append(mcr)
|
||||
self.ws.merged_cells = MultiCellRange(ranges)
|
||||
|
||||
|
||||
def bind_hyperlinks(self):
|
||||
for link in self.parser.hyperlinks.hyperlink:
|
||||
if link.id:
|
||||
rel = self.ws._rels.get(link.id)
|
||||
link.target = rel.Target
|
||||
if ":" in link.ref:
|
||||
# range of cells
|
||||
for row in self.ws[link.ref]:
|
||||
for cell in row:
|
||||
try:
|
||||
cell.hyperlink = copy(link)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
cell = self.ws[link.ref]
|
||||
if isinstance(cell, MergedCell):
|
||||
cell = self.normalize_merged_cell_link(cell.coordinate)
|
||||
cell.hyperlink = link
|
||||
|
||||
def normalize_merged_cell_link(self, coord):
|
||||
"""
|
||||
Returns the appropriate cell to which a hyperlink, which references a merged cell at the specified coordinates,
|
||||
should be bound.
|
||||
"""
|
||||
for rng in self.ws.merged_cells:
|
||||
if coord in rng:
|
||||
return self.ws.cell(*rng.top[0])
|
||||
|
||||
def bind_col_dimensions(self):
|
||||
for col, cd in self.parser.column_dimensions.items():
|
||||
if 'style' in cd:
|
||||
key = int(cd['style'])
|
||||
cd['style'] = self.ws.parent._cell_styles[key]
|
||||
self.ws.column_dimensions[col] = ColumnDimension(self.ws, **cd)
|
||||
|
||||
|
||||
def bind_row_dimensions(self):
|
||||
for row, rd in self.parser.row_dimensions.items():
|
||||
if 's' in rd:
|
||||
key = int(rd['s'])
|
||||
rd['s'] = self.ws.parent._cell_styles[key]
|
||||
self.ws.row_dimensions[int(row)] = RowDimension(self.ws, **rd)
|
||||
|
||||
|
||||
def bind_properties(self):
|
||||
for k in ('print_options', 'page_margins', 'page_setup',
|
||||
'HeaderFooter', 'auto_filter', 'data_validations',
|
||||
'sheet_properties', 'views', 'sheet_format',
|
||||
'row_breaks', 'col_breaks', 'scenarios', 'legacy_drawing',
|
||||
'protection',
|
||||
):
|
||||
v = getattr(self.parser, k, None)
|
||||
if v is not None:
|
||||
setattr(self.ws, k, v)
|
||||
|
||||
|
||||
def bind_all(self):
|
||||
self.bind_cells()
|
||||
self.bind_merged_cells()
|
||||
self.bind_hyperlinks()
|
||||
self.bind_formatting()
|
||||
self.bind_col_dimensions()
|
||||
self.bind_row_dimensions()
|
||||
self.bind_tables()
|
||||
self.bind_properties()
|
160
lib/python3.13/site-packages/openpyxl/worksheet/_write_only.py
Normal file
160
lib/python3.13/site-packages/openpyxl/worksheet/_write_only.py
Normal file
@ -0,0 +1,160 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
"""Write worksheets to xml representations in an optimized way"""
|
||||
|
||||
from inspect import isgenerator
|
||||
|
||||
from openpyxl.cell import Cell, WriteOnlyCell
|
||||
from openpyxl.workbook.child import _WorkbookChild
|
||||
from .worksheet import Worksheet
|
||||
from openpyxl.utils.exceptions import WorkbookAlreadySaved
|
||||
|
||||
from ._writer import WorksheetWriter
|
||||
|
||||
|
||||
class WriteOnlyWorksheet(_WorkbookChild):
|
||||
"""
|
||||
Streaming worksheet. Optimised to reduce memory by writing rows just in
|
||||
time.
|
||||
Cells can be styled and have comments Styles for rows and columns
|
||||
must be applied before writing cells
|
||||
"""
|
||||
|
||||
__saved = False
|
||||
_writer = None
|
||||
_rows = None
|
||||
_rel_type = Worksheet._rel_type
|
||||
_path = Worksheet._path
|
||||
mime_type = Worksheet.mime_type
|
||||
|
||||
# copy methods from Standard worksheet
|
||||
_add_row = Worksheet._add_row
|
||||
_add_column = Worksheet._add_column
|
||||
add_chart = Worksheet.add_chart
|
||||
add_image = Worksheet.add_image
|
||||
add_table = Worksheet.add_table
|
||||
tables = Worksheet.tables
|
||||
print_titles = Worksheet.print_titles
|
||||
print_title_cols = Worksheet.print_title_cols
|
||||
print_title_rows = Worksheet.print_title_rows
|
||||
freeze_panes = Worksheet.freeze_panes
|
||||
print_area = Worksheet.print_area
|
||||
sheet_view = Worksheet.sheet_view
|
||||
_setup = Worksheet._setup
|
||||
|
||||
def __init__(self, parent, title):
|
||||
super().__init__(parent, title)
|
||||
self._max_col = 0
|
||||
self._max_row = 0
|
||||
self._setup()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self.__saved
|
||||
|
||||
|
||||
def _write_rows(self):
|
||||
"""
|
||||
Send rows to the writer's stream
|
||||
"""
|
||||
try:
|
||||
xf = self._writer.xf.send(True)
|
||||
except StopIteration:
|
||||
self._already_saved()
|
||||
|
||||
with xf.element("sheetData"):
|
||||
row_idx = 1
|
||||
try:
|
||||
while True:
|
||||
row = (yield)
|
||||
row = self._values_to_row(row, row_idx)
|
||||
self._writer.write_row(xf, row, row_idx)
|
||||
row_idx += 1
|
||||
except GeneratorExit:
|
||||
pass
|
||||
|
||||
self._writer.xf.send(None)
|
||||
|
||||
|
||||
def _get_writer(self):
|
||||
if self._writer is None:
|
||||
self._writer = WorksheetWriter(self)
|
||||
self._writer.write_top()
|
||||
|
||||
|
||||
def close(self):
|
||||
if self.__saved:
|
||||
self._already_saved()
|
||||
|
||||
self._get_writer()
|
||||
|
||||
if self._rows is None:
|
||||
self._writer.write_rows()
|
||||
else:
|
||||
self._rows.close()
|
||||
|
||||
self._writer.write_tail()
|
||||
|
||||
self._writer.close()
|
||||
self.__saved = True
|
||||
|
||||
|
||||
def append(self, row):
|
||||
"""
|
||||
:param row: iterable containing values to append
|
||||
:type row: iterable
|
||||
"""
|
||||
|
||||
if (not isgenerator(row) and
|
||||
not isinstance(row, (list, tuple, range))
|
||||
):
|
||||
self._invalid_row(row)
|
||||
|
||||
self._get_writer()
|
||||
|
||||
if self._rows is None:
|
||||
self._rows = self._write_rows()
|
||||
next(self._rows)
|
||||
|
||||
self._rows.send(row)
|
||||
|
||||
|
||||
def _values_to_row(self, values, row_idx):
|
||||
"""
|
||||
Convert whatever has been appended into a form suitable for work_rows
|
||||
"""
|
||||
cell = WriteOnlyCell(self)
|
||||
|
||||
for col_idx, value in enumerate(values, 1):
|
||||
if value is None:
|
||||
continue
|
||||
try:
|
||||
cell.value = value
|
||||
except ValueError:
|
||||
if isinstance(value, Cell):
|
||||
cell = value
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
cell.column = col_idx
|
||||
cell.row = row_idx
|
||||
|
||||
if cell.hyperlink is not None:
|
||||
cell.hyperlink.ref = cell.coordinate
|
||||
|
||||
yield cell
|
||||
|
||||
# reset cell if style applied
|
||||
if cell.has_style or cell.hyperlink:
|
||||
cell = WriteOnlyCell(self)
|
||||
|
||||
|
||||
def _already_saved(self):
|
||||
raise WorkbookAlreadySaved('Workbook has already been saved and cannot be modified or saved anymore.')
|
||||
|
||||
|
||||
def _invalid_row(self, iterable):
|
||||
raise TypeError('Value must be a list, tuple, range or a generator Supplied value is {0}'.format(
|
||||
type(iterable))
|
||||
)
|
390
lib/python3.13/site-packages/openpyxl/worksheet/_writer.py
Normal file
390
lib/python3.13/site-packages/openpyxl/worksheet/_writer.py
Normal file
@ -0,0 +1,390 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
import atexit
|
||||
from collections import defaultdict
|
||||
from io import BytesIO
|
||||
import os
|
||||
from tempfile import NamedTemporaryFile
|
||||
from warnings import warn
|
||||
|
||||
from openpyxl.xml.functions import xmlfile
|
||||
from openpyxl.xml.constants import SHEET_MAIN_NS
|
||||
|
||||
from openpyxl.comments.comment_sheet import CommentRecord
|
||||
from openpyxl.packaging.relationship import Relationship, RelationshipList
|
||||
from openpyxl.styles.differential import DifferentialStyle
|
||||
|
||||
from .dimensions import SheetDimension
|
||||
from .hyperlink import HyperlinkList
|
||||
from .merge import MergeCell, MergeCells
|
||||
from .related import Related
|
||||
from .table import TablePartList
|
||||
|
||||
from openpyxl.cell._writer import write_cell
|
||||
|
||||
|
||||
ALL_TEMP_FILES = []
|
||||
|
||||
@atexit.register
|
||||
def _openpyxl_shutdown():
|
||||
for path in ALL_TEMP_FILES:
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def create_temporary_file(suffix=''):
|
||||
fobj = NamedTemporaryFile(mode='w+', suffix=suffix,
|
||||
prefix='openpyxl.', delete=False)
|
||||
filename = fobj.name
|
||||
fobj.close()
|
||||
ALL_TEMP_FILES.append(filename)
|
||||
return filename
|
||||
|
||||
|
||||
class WorksheetWriter:
|
||||
|
||||
|
||||
def __init__(self, ws, out=None):
|
||||
self.ws = ws
|
||||
self.ws._hyperlinks = []
|
||||
self.ws._comments = []
|
||||
if out is None:
|
||||
out = create_temporary_file()
|
||||
self.out = out
|
||||
self._rels = RelationshipList()
|
||||
self.xf = self.get_stream()
|
||||
next(self.xf) # start generator
|
||||
|
||||
|
||||
def write_properties(self):
|
||||
props = self.ws.sheet_properties
|
||||
self.xf.send(props.to_tree())
|
||||
|
||||
|
||||
def write_dimensions(self):
|
||||
"""
|
||||
Write worksheet size if known
|
||||
"""
|
||||
ref = getattr(self.ws, 'calculate_dimension', None)
|
||||
if ref:
|
||||
dim = SheetDimension(ref())
|
||||
self.xf.send(dim.to_tree())
|
||||
|
||||
|
||||
def write_format(self):
|
||||
self.ws.sheet_format.outlineLevelCol = self.ws.column_dimensions.max_outline
|
||||
fmt = self.ws.sheet_format
|
||||
self.xf.send(fmt.to_tree())
|
||||
|
||||
|
||||
def write_views(self):
|
||||
views = self.ws.views
|
||||
self.xf.send(views.to_tree())
|
||||
|
||||
|
||||
def write_cols(self):
|
||||
cols = self.ws.column_dimensions
|
||||
self.xf.send(cols.to_tree())
|
||||
|
||||
|
||||
def write_top(self):
|
||||
"""
|
||||
Write all elements up to rows:
|
||||
properties
|
||||
dimensions
|
||||
views
|
||||
format
|
||||
cols
|
||||
"""
|
||||
self.write_properties()
|
||||
self.write_dimensions()
|
||||
self.write_views()
|
||||
self.write_format()
|
||||
self.write_cols()
|
||||
|
||||
|
||||
def rows(self):
|
||||
"""Return all rows, and any cells that they contain"""
|
||||
# order cells by row
|
||||
rows = defaultdict(list)
|
||||
for (row, col), cell in sorted(self.ws._cells.items()):
|
||||
rows[row].append(cell)
|
||||
|
||||
# add empty rows if styling has been applied
|
||||
for row in self.ws.row_dimensions.keys() - rows.keys():
|
||||
rows[row] = []
|
||||
|
||||
return sorted(rows.items())
|
||||
|
||||
|
||||
def write_rows(self):
|
||||
xf = self.xf.send(True)
|
||||
|
||||
with xf.element("sheetData"):
|
||||
for row_idx, row in self.rows():
|
||||
self.write_row(xf, row, row_idx)
|
||||
|
||||
self.xf.send(None) # return control to generator
|
||||
|
||||
|
||||
def write_row(self, xf, row, row_idx):
|
||||
attrs = {'r': f"{row_idx}"}
|
||||
dims = self.ws.row_dimensions
|
||||
attrs.update(dims.get(row_idx, {}))
|
||||
|
||||
with xf.element("row", attrs):
|
||||
|
||||
for cell in row:
|
||||
if cell._comment is not None:
|
||||
comment = CommentRecord.from_cell(cell)
|
||||
self.ws._comments.append(comment)
|
||||
if (
|
||||
cell._value is None
|
||||
and not cell.has_style
|
||||
and not cell._comment
|
||||
):
|
||||
continue
|
||||
write_cell(xf, self.ws, cell, cell.has_style)
|
||||
|
||||
|
||||
def write_protection(self):
|
||||
prot = self.ws.protection
|
||||
if prot:
|
||||
self.xf.send(prot.to_tree())
|
||||
|
||||
|
||||
def write_scenarios(self):
|
||||
scenarios = self.ws.scenarios
|
||||
if scenarios:
|
||||
self.xf.send(scenarios.to_tree())
|
||||
|
||||
|
||||
def write_filter(self):
|
||||
flt = self.ws.auto_filter
|
||||
if flt:
|
||||
self.xf.send(flt.to_tree())
|
||||
|
||||
|
||||
def write_sort(self):
|
||||
"""
|
||||
As per discusion with the OOXML Working Group global sort state is not required.
|
||||
openpyxl never reads it from existing files
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def write_merged_cells(self):
|
||||
merged = self.ws.merged_cells
|
||||
if merged:
|
||||
cells = [MergeCell(str(ref)) for ref in self.ws.merged_cells]
|
||||
self.xf.send(MergeCells(mergeCell=cells).to_tree())
|
||||
|
||||
|
||||
def write_formatting(self):
|
||||
df = DifferentialStyle()
|
||||
wb = self.ws.parent
|
||||
for cf in self.ws.conditional_formatting:
|
||||
for rule in cf.rules:
|
||||
if rule.dxf and rule.dxf != df:
|
||||
rule.dxfId = wb._differential_styles.add(rule.dxf)
|
||||
self.xf.send(cf.to_tree())
|
||||
|
||||
|
||||
def write_validations(self):
|
||||
dv = self.ws.data_validations
|
||||
if dv:
|
||||
self.xf.send(dv.to_tree())
|
||||
|
||||
|
||||
def write_hyperlinks(self):
|
||||
|
||||
links = self.ws._hyperlinks
|
||||
|
||||
for link in links:
|
||||
if link.target:
|
||||
rel = Relationship(type="hyperlink", TargetMode="External", Target=link.target)
|
||||
self._rels.append(rel)
|
||||
link.id = rel.id
|
||||
|
||||
if links:
|
||||
self.xf.send(HyperlinkList(links).to_tree())
|
||||
|
||||
|
||||
def write_print(self):
|
||||
print_options = self.ws.print_options
|
||||
if print_options:
|
||||
self.xf.send(print_options.to_tree())
|
||||
|
||||
|
||||
def write_margins(self):
|
||||
margins = self.ws.page_margins
|
||||
if margins:
|
||||
self.xf.send(margins.to_tree())
|
||||
|
||||
|
||||
def write_page(self):
|
||||
setup = self.ws.page_setup
|
||||
if setup:
|
||||
self.xf.send(setup.to_tree())
|
||||
|
||||
|
||||
def write_header(self):
|
||||
hf = self.ws.HeaderFooter
|
||||
if hf:
|
||||
self.xf.send(hf.to_tree())
|
||||
|
||||
|
||||
def write_breaks(self):
|
||||
brks = (self.ws.row_breaks, self.ws.col_breaks)
|
||||
for brk in brks:
|
||||
if brk:
|
||||
self.xf.send(brk.to_tree())
|
||||
|
||||
|
||||
def write_drawings(self):
|
||||
if self.ws._charts or self.ws._images:
|
||||
rel = Relationship(type="drawing", Target="")
|
||||
self._rels.append(rel)
|
||||
drawing = Related()
|
||||
drawing.id = rel.id
|
||||
self.xf.send(drawing.to_tree("drawing"))
|
||||
|
||||
|
||||
def write_legacy(self):
|
||||
"""
|
||||
Comments & VBA controls use VML and require an additional element
|
||||
that is no longer in the specification.
|
||||
"""
|
||||
if (self.ws.legacy_drawing is not None or self.ws._comments):
|
||||
legacy = Related(id="anysvml")
|
||||
self.xf.send(legacy.to_tree("legacyDrawing"))
|
||||
|
||||
|
||||
def write_tables(self):
|
||||
tables = TablePartList()
|
||||
|
||||
for table in self.ws.tables.values():
|
||||
if not table.tableColumns:
|
||||
table._initialise_columns()
|
||||
if table.headerRowCount:
|
||||
try:
|
||||
row = self.ws[table.ref][0]
|
||||
for cell, col in zip(row, table.tableColumns):
|
||||
if cell.data_type != "s":
|
||||
warn("File may not be readable: column headings must be strings.")
|
||||
col.name = str(cell.value)
|
||||
except TypeError:
|
||||
warn("Column headings are missing, file may not be readable")
|
||||
rel = Relationship(Type=table._rel_type, Target="")
|
||||
self._rels.append(rel)
|
||||
table._rel_id = rel.Id
|
||||
tables.append(Related(id=rel.Id))
|
||||
|
||||
if tables:
|
||||
self.xf.send(tables.to_tree())
|
||||
|
||||
|
||||
def get_stream(self):
|
||||
with xmlfile(self.out) as xf:
|
||||
with xf.element("worksheet", xmlns=SHEET_MAIN_NS):
|
||||
try:
|
||||
while True:
|
||||
el = (yield)
|
||||
if el is True:
|
||||
yield xf
|
||||
elif el is None: # et_xmlfile chokes
|
||||
continue
|
||||
else:
|
||||
xf.write(el)
|
||||
except GeneratorExit:
|
||||
pass
|
||||
|
||||
|
||||
def write_tail(self):
|
||||
"""
|
||||
Write all elements after the rows
|
||||
calc properties
|
||||
protection
|
||||
protected ranges #
|
||||
scenarios
|
||||
filters
|
||||
sorts # always ignored
|
||||
data consolidation #
|
||||
custom views #
|
||||
merged cells
|
||||
phonetic properties #
|
||||
conditional formatting
|
||||
data validation
|
||||
hyperlinks
|
||||
print options
|
||||
page margins
|
||||
page setup
|
||||
header
|
||||
row breaks
|
||||
col breaks
|
||||
custom properties #
|
||||
cell watches #
|
||||
ignored errors #
|
||||
smart tags #
|
||||
drawing
|
||||
drawingHF #
|
||||
background #
|
||||
OLE objects #
|
||||
controls #
|
||||
web publishing #
|
||||
tables
|
||||
"""
|
||||
self.write_protection()
|
||||
self.write_scenarios()
|
||||
self.write_filter()
|
||||
self.write_merged_cells()
|
||||
self.write_formatting()
|
||||
self.write_validations()
|
||||
self.write_hyperlinks()
|
||||
self.write_print()
|
||||
self.write_margins()
|
||||
self.write_page()
|
||||
self.write_header()
|
||||
self.write_breaks()
|
||||
self.write_drawings()
|
||||
self.write_legacy()
|
||||
self.write_tables()
|
||||
|
||||
|
||||
def write(self):
|
||||
"""
|
||||
High level
|
||||
"""
|
||||
self.write_top()
|
||||
self.write_rows()
|
||||
self.write_tail()
|
||||
self.close()
|
||||
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the context manager
|
||||
"""
|
||||
if self.xf:
|
||||
self.xf.close()
|
||||
|
||||
|
||||
def read(self):
|
||||
"""
|
||||
Close the context manager and return serialised XML
|
||||
"""
|
||||
self.close()
|
||||
if isinstance(self.out, BytesIO):
|
||||
return self.out.getvalue()
|
||||
with open(self.out, "rb") as src:
|
||||
out = src.read()
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Remove tempfile
|
||||
"""
|
||||
os.remove(self.out)
|
||||
ALL_TEMP_FILES.remove(self.out)
|
512
lib/python3.13/site-packages/openpyxl/worksheet/cell_range.py
Normal file
512
lib/python3.13/site-packages/openpyxl/worksheet/cell_range.py
Normal file
@ -0,0 +1,512 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from copy import copy
|
||||
from operator import attrgetter
|
||||
|
||||
from openpyxl.descriptors import Strict
|
||||
from openpyxl.descriptors import MinMax
|
||||
from openpyxl.descriptors.sequence import UniqueSequence
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
from openpyxl.utils import (
|
||||
range_boundaries,
|
||||
range_to_tuple,
|
||||
get_column_letter,
|
||||
quote_sheetname,
|
||||
)
|
||||
|
||||
class CellRange(Serialisable):
|
||||
"""
|
||||
Represents a range in a sheet: title and coordinates.
|
||||
|
||||
This object is used to perform operations on ranges, like:
|
||||
|
||||
- shift, expand or shrink
|
||||
- union/intersection with another sheet range,
|
||||
|
||||
We can check whether a range is:
|
||||
|
||||
- equal or not equal to another,
|
||||
- disjoint of another,
|
||||
- contained in another.
|
||||
|
||||
We can get:
|
||||
|
||||
- the size of a range.
|
||||
- the range bounds (vertices)
|
||||
- the coordinates,
|
||||
- the string representation,
|
||||
|
||||
"""
|
||||
|
||||
min_col = MinMax(min=1, max=18278, expected_type=int)
|
||||
min_row = MinMax(min=1, max=1048576, expected_type=int)
|
||||
max_col = MinMax(min=1, max=18278, expected_type=int)
|
||||
max_row = MinMax(min=1, max=1048576, expected_type=int)
|
||||
|
||||
|
||||
def __init__(self, range_string=None, min_col=None, min_row=None,
|
||||
max_col=None, max_row=None, title=None):
|
||||
if range_string is not None:
|
||||
if "!" in range_string:
|
||||
title, (min_col, min_row, max_col, max_row) = range_to_tuple(range_string)
|
||||
else:
|
||||
min_col, min_row, max_col, max_row = range_boundaries(range_string)
|
||||
|
||||
self.min_col = min_col
|
||||
self.min_row = min_row
|
||||
self.max_col = max_col
|
||||
self.max_row = max_row
|
||||
self.title = title
|
||||
|
||||
if min_col > max_col:
|
||||
fmt = "{max_col} must be greater than {min_col}"
|
||||
raise ValueError(fmt.format(min_col=min_col, max_col=max_col))
|
||||
if min_row > max_row:
|
||||
fmt = "{max_row} must be greater than {min_row}"
|
||||
raise ValueError(fmt.format(min_row=min_row, max_row=max_row))
|
||||
|
||||
|
||||
@property
|
||||
def bounds(self):
|
||||
"""
|
||||
Vertices of the range as a tuple
|
||||
"""
|
||||
return self.min_col, self.min_row, self.max_col, self.max_row
|
||||
|
||||
|
||||
@property
|
||||
def coord(self):
|
||||
"""
|
||||
Excel-style representation of the range
|
||||
"""
|
||||
fmt = "{min_col}{min_row}:{max_col}{max_row}"
|
||||
if (self.min_col == self.max_col
|
||||
and self.min_row == self.max_row):
|
||||
fmt = "{min_col}{min_row}"
|
||||
|
||||
return fmt.format(
|
||||
min_col=get_column_letter(self.min_col),
|
||||
min_row=self.min_row,
|
||||
max_col=get_column_letter(self.max_col),
|
||||
max_row=self.max_row
|
||||
)
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
"""
|
||||
Return cell coordinates as rows
|
||||
"""
|
||||
for row in range(self.min_row, self.max_row+1):
|
||||
yield [(row, col) for col in range(self.min_col, self.max_col+1)]
|
||||
|
||||
|
||||
@property
|
||||
def cols(self):
|
||||
"""
|
||||
Return cell coordinates as columns
|
||||
"""
|
||||
for col in range(self.min_col, self.max_col+1):
|
||||
yield [(row, col) for row in range(self.min_row, self.max_row+1)]
|
||||
|
||||
|
||||
@property
|
||||
def cells(self):
|
||||
from itertools import product
|
||||
return product(range(self.min_row, self.max_row+1), range(self.min_col, self.max_col+1))
|
||||
|
||||
|
||||
def _check_title(self, other):
|
||||
"""
|
||||
Check whether comparisons between ranges are possible.
|
||||
Cannot compare ranges from different worksheets
|
||||
Skip if the range passed in has no title.
|
||||
"""
|
||||
if not isinstance(other, CellRange):
|
||||
raise TypeError(repr(type(other)))
|
||||
|
||||
if other.title and self.title != other.title:
|
||||
raise ValueError("Cannot work with ranges from different worksheets")
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
fmt = u"<{cls} {coord}>"
|
||||
if self.title:
|
||||
fmt = u"<{cls} {title!r}!{coord}>"
|
||||
return fmt.format(cls=self.__class__.__name__, title=self.title, coord=self.coord)
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.min_row, self.min_col, self.max_row, self.max_col))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
fmt = "{coord}"
|
||||
title = self.title
|
||||
if title:
|
||||
fmt = u"{title}!{coord}"
|
||||
title = quote_sheetname(title)
|
||||
return fmt.format(title=title, coord=self.coord)
|
||||
|
||||
|
||||
def __copy__(self):
|
||||
return self.__class__(min_col=self.min_col, min_row=self.min_row,
|
||||
max_col=self.max_col, max_row=self.max_row,
|
||||
title=self.title)
|
||||
|
||||
|
||||
def shift(self, col_shift=0, row_shift=0):
|
||||
"""
|
||||
Shift the focus of the range according to the shift values (*col_shift*, *row_shift*).
|
||||
|
||||
:type col_shift: int
|
||||
:param col_shift: number of columns to be moved by, can be negative
|
||||
:type row_shift: int
|
||||
:param row_shift: number of rows to be moved by, can be negative
|
||||
:raise: :class:`ValueError` if any row or column index < 1
|
||||
"""
|
||||
|
||||
if (self.min_col + col_shift <= 0
|
||||
or self.min_row + row_shift <= 0):
|
||||
raise ValueError("Invalid shift value: col_shift={0}, row_shift={1}".format(col_shift, row_shift))
|
||||
self.min_col += col_shift
|
||||
self.min_row += row_shift
|
||||
self.max_col += col_shift
|
||||
self.max_row += row_shift
|
||||
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
Test whether the ranges are not equal.
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range
|
||||
:return: ``True`` if *range* != *other*.
|
||||
"""
|
||||
try:
|
||||
self._check_title(other)
|
||||
except ValueError:
|
||||
return True
|
||||
|
||||
return (
|
||||
other.min_row != self.min_row
|
||||
or self.max_row != other.max_row
|
||||
or other.min_col != self.min_col
|
||||
or self.max_col != other.max_col
|
||||
)
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Test whether the ranges are equal.
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range
|
||||
:return: ``True`` if *range* == *other*.
|
||||
"""
|
||||
return not self.__ne__(other)
|
||||
|
||||
|
||||
def issubset(self, other):
|
||||
"""
|
||||
Test whether every cell in this range is also in *other*.
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range
|
||||
:return: ``True`` if *range* <= *other*.
|
||||
"""
|
||||
self._check_title(other)
|
||||
|
||||
return other.__superset(self)
|
||||
|
||||
__le__ = issubset
|
||||
|
||||
|
||||
def __lt__(self, other):
|
||||
"""
|
||||
Test whether *other* contains every cell of this range, and more.
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range
|
||||
:return: ``True`` if *range* < *other*.
|
||||
"""
|
||||
return self.__le__(other) and self.__ne__(other)
|
||||
|
||||
|
||||
def __superset(self, other):
|
||||
return (
|
||||
(self.min_row <= other.min_row <= other.max_row <= self.max_row)
|
||||
and
|
||||
(self.min_col <= other.min_col <= other.max_col <= self.max_col)
|
||||
)
|
||||
|
||||
|
||||
def issuperset(self, other):
|
||||
"""
|
||||
Test whether every cell in *other* is in this range.
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range
|
||||
:return: ``True`` if *range* >= *other* (or *other* in *range*).
|
||||
"""
|
||||
self._check_title(other)
|
||||
|
||||
return self.__superset(other)
|
||||
|
||||
__ge__ = issuperset
|
||||
|
||||
|
||||
def __contains__(self, coord):
|
||||
"""
|
||||
Check whether the range contains a particular cell coordinate
|
||||
"""
|
||||
cr = self.__class__(coord)
|
||||
return self.__superset(cr)
|
||||
|
||||
|
||||
def __gt__(self, other):
|
||||
"""
|
||||
Test whether this range contains every cell in *other*, and more.
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range
|
||||
:return: ``True`` if *range* > *other*.
|
||||
"""
|
||||
return self.__ge__(other) and self.__ne__(other)
|
||||
|
||||
|
||||
def isdisjoint(self, other):
|
||||
"""
|
||||
Return ``True`` if this range has no cell in common with *other*.
|
||||
Ranges are disjoint if and only if their intersection is the empty range.
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range.
|
||||
:return: ``True`` if the range has no cells in common with other.
|
||||
"""
|
||||
self._check_title(other)
|
||||
|
||||
# Sort by top-left vertex
|
||||
if self.bounds > other.bounds:
|
||||
self, other = other, self
|
||||
|
||||
return (self.max_col < other.min_col
|
||||
or self.max_row < other.min_row
|
||||
or other.max_row < self.min_row)
|
||||
|
||||
|
||||
def intersection(self, other):
|
||||
"""
|
||||
Return a new range with cells common to this range and *other*
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range.
|
||||
:return: the intersecting sheet range.
|
||||
:raise: :class:`ValueError` if the *other* range doesn't intersect
|
||||
with this range.
|
||||
"""
|
||||
if self.isdisjoint(other):
|
||||
raise ValueError("Range {0} doesn't intersect {0}".format(self, other))
|
||||
|
||||
min_row = max(self.min_row, other.min_row)
|
||||
max_row = min(self.max_row, other.max_row)
|
||||
min_col = max(self.min_col, other.min_col)
|
||||
max_col = min(self.max_col, other.max_col)
|
||||
|
||||
return CellRange(min_col=min_col, min_row=min_row, max_col=max_col,
|
||||
max_row=max_row)
|
||||
|
||||
__and__ = intersection
|
||||
|
||||
|
||||
def union(self, other):
|
||||
"""
|
||||
Return the minimal superset of this range and *other*. This new range
|
||||
will contain all cells from this range, *other*, and any additional
|
||||
cells required to form a rectangular ``CellRange``.
|
||||
|
||||
:type other: openpyxl.worksheet.cell_range.CellRange
|
||||
:param other: Other sheet range.
|
||||
:return: a ``CellRange`` that is a superset of this and *other*.
|
||||
"""
|
||||
self._check_title(other)
|
||||
|
||||
min_row = min(self.min_row, other.min_row)
|
||||
max_row = max(self.max_row, other.max_row)
|
||||
min_col = min(self.min_col, other.min_col)
|
||||
max_col = max(self.max_col, other.max_col)
|
||||
return CellRange(min_col=min_col, min_row=min_row, max_col=max_col,
|
||||
max_row=max_row, title=self.title)
|
||||
|
||||
__or__ = union
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
For use as a dictionary elsewhere in the library.
|
||||
"""
|
||||
for x in self.__attrs__:
|
||||
if x == "title":
|
||||
continue
|
||||
v = getattr(self, x)
|
||||
yield x, v
|
||||
|
||||
|
||||
def expand(self, right=0, down=0, left=0, up=0):
|
||||
"""
|
||||
Expand the range by the dimensions provided.
|
||||
|
||||
:type right: int
|
||||
:param right: expand range to the right by this number of cells
|
||||
:type down: int
|
||||
:param down: expand range down by this number of cells
|
||||
:type left: int
|
||||
:param left: expand range to the left by this number of cells
|
||||
:type up: int
|
||||
:param up: expand range up by this number of cells
|
||||
"""
|
||||
self.min_col -= left
|
||||
self.min_row -= up
|
||||
self.max_col += right
|
||||
self.max_row += down
|
||||
|
||||
|
||||
def shrink(self, right=0, bottom=0, left=0, top=0):
|
||||
"""
|
||||
Shrink the range by the dimensions provided.
|
||||
|
||||
:type right: int
|
||||
:param right: shrink range from the right by this number of cells
|
||||
:type down: int
|
||||
:param down: shrink range from the top by this number of cells
|
||||
:type left: int
|
||||
:param left: shrink range from the left by this number of cells
|
||||
:type up: int
|
||||
:param up: shrink range from the bottom by this number of cells
|
||||
"""
|
||||
self.min_col += left
|
||||
self.min_row += top
|
||||
self.max_col -= right
|
||||
self.max_row -= bottom
|
||||
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
""" Return the size of the range as a dictionary of rows and columns. """
|
||||
cols = self.max_col + 1 - self.min_col
|
||||
rows = self.max_row + 1 - self.min_row
|
||||
return {'columns':cols, 'rows':rows}
|
||||
|
||||
|
||||
@property
|
||||
def top(self):
|
||||
"""A list of cell coordinates that comprise the top of the range"""
|
||||
return [(self.min_row, col) for col in range(self.min_col, self.max_col+1)]
|
||||
|
||||
|
||||
@property
|
||||
def bottom(self):
|
||||
"""A list of cell coordinates that comprise the bottom of the range"""
|
||||
return [(self.max_row, col) for col in range(self.min_col, self.max_col+1)]
|
||||
|
||||
|
||||
@property
|
||||
def left(self):
|
||||
"""A list of cell coordinates that comprise the left-side of the range"""
|
||||
return [(row, self.min_col) for row in range(self.min_row, self.max_row+1)]
|
||||
|
||||
|
||||
@property
|
||||
def right(self):
|
||||
"""A list of cell coordinates that comprise the right-side of the range"""
|
||||
return [(row, self.max_col) for row in range(self.min_row, self.max_row+1)]
|
||||
|
||||
|
||||
class MultiCellRange(Strict):
|
||||
|
||||
|
||||
ranges = UniqueSequence(expected_type=CellRange)
|
||||
|
||||
|
||||
def __init__(self, ranges=set()):
|
||||
if isinstance(ranges, str):
|
||||
ranges = [CellRange(r) for r in ranges.split()]
|
||||
self.ranges = set(ranges)
|
||||
|
||||
|
||||
def __contains__(self, coord):
|
||||
if isinstance(coord, str):
|
||||
coord = CellRange(coord)
|
||||
for r in self.ranges:
|
||||
if coord <= r:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
ranges = " ".join([str(r) for r in self.sorted()])
|
||||
return f"<{self.__class__.__name__} [{ranges}]>"
|
||||
|
||||
|
||||
def __str__(self):
|
||||
ranges = u" ".join([str(r) for r in self.sorted()])
|
||||
return ranges
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self))
|
||||
|
||||
|
||||
def sorted(self):
|
||||
"""
|
||||
Return a sorted list of items
|
||||
"""
|
||||
return sorted(self.ranges, key=attrgetter('min_col', 'min_row', 'max_col', 'max_row'))
|
||||
|
||||
|
||||
def add(self, coord):
|
||||
"""
|
||||
Add a cell coordinate or CellRange
|
||||
"""
|
||||
cr = coord
|
||||
if isinstance(coord, str):
|
||||
cr = CellRange(coord)
|
||||
elif not isinstance(coord, CellRange):
|
||||
raise ValueError("You can only add CellRanges")
|
||||
if cr not in self:
|
||||
self.ranges.add(cr)
|
||||
|
||||
|
||||
def __iadd__(self, coord):
|
||||
self.add(coord)
|
||||
return self
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, str):
|
||||
other = self.__class__(other)
|
||||
return self.ranges == other.ranges
|
||||
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.ranges)
|
||||
|
||||
|
||||
def remove(self, coord):
|
||||
if not isinstance(coord, CellRange):
|
||||
coord = CellRange(coord)
|
||||
self.ranges.remove(coord)
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
for cr in self.ranges:
|
||||
yield cr
|
||||
|
||||
|
||||
def __copy__(self):
|
||||
ranges = {copy(r) for r in self.ranges}
|
||||
return MultiCellRange(ranges)
|
@ -0,0 +1,34 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Sequence,
|
||||
String,
|
||||
)
|
||||
|
||||
# could be done using a nestedSequence
|
||||
|
||||
class CellWatch(Serialisable):
|
||||
|
||||
tagname = "cellWatch"
|
||||
|
||||
r = String()
|
||||
|
||||
def __init__(self,
|
||||
r=None,
|
||||
):
|
||||
self.r = r
|
||||
|
||||
|
||||
class CellWatches(Serialisable):
|
||||
|
||||
tagname = "cellWatches"
|
||||
|
||||
cellWatch = Sequence(expected_type=CellWatch)
|
||||
|
||||
__elements__ = ('cellWatch',)
|
||||
|
||||
def __init__(self,
|
||||
cellWatch=(),
|
||||
):
|
||||
self.cellWatch = cellWatch
|
||||
|
107
lib/python3.13/site-packages/openpyxl/worksheet/controls.py
Normal file
107
lib/python3.13/site-packages/openpyxl/worksheet/controls.py
Normal file
@ -0,0 +1,107 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Bool,
|
||||
Integer,
|
||||
String,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.excel import Relation
|
||||
from .ole import ObjectAnchor
|
||||
|
||||
|
||||
class ControlProperty(Serialisable):
|
||||
|
||||
tagname = "controlPr"
|
||||
|
||||
anchor = Typed(expected_type=ObjectAnchor, )
|
||||
locked = Bool(allow_none=True)
|
||||
defaultSize = Bool(allow_none=True)
|
||||
_print = Bool(allow_none=True)
|
||||
disabled = Bool(allow_none=True)
|
||||
recalcAlways = Bool(allow_none=True)
|
||||
uiObject = Bool(allow_none=True)
|
||||
autoFill = Bool(allow_none=True)
|
||||
autoLine = Bool(allow_none=True)
|
||||
autoPict = Bool(allow_none=True)
|
||||
macro = String(allow_none=True)
|
||||
altText = String(allow_none=True)
|
||||
linkedCell = String(allow_none=True)
|
||||
listFillRange = String(allow_none=True)
|
||||
cf = String(allow_none=True)
|
||||
id = Relation(allow_none=True)
|
||||
|
||||
__elements__ = ('anchor',)
|
||||
|
||||
def __init__(self,
|
||||
anchor=None,
|
||||
locked=True,
|
||||
defaultSize=True,
|
||||
_print=True,
|
||||
disabled=False,
|
||||
recalcAlways=False,
|
||||
uiObject=False,
|
||||
autoFill=True,
|
||||
autoLine=True,
|
||||
autoPict=True,
|
||||
macro=None,
|
||||
altText=None,
|
||||
linkedCell=None,
|
||||
listFillRange=None,
|
||||
cf='pict',
|
||||
id=None,
|
||||
):
|
||||
self.anchor = anchor
|
||||
self.locked = locked
|
||||
self.defaultSize = defaultSize
|
||||
self._print = _print
|
||||
self.disabled = disabled
|
||||
self.recalcAlways = recalcAlways
|
||||
self.uiObject = uiObject
|
||||
self.autoFill = autoFill
|
||||
self.autoLine = autoLine
|
||||
self.autoPict = autoPict
|
||||
self.macro = macro
|
||||
self.altText = altText
|
||||
self.linkedCell = linkedCell
|
||||
self.listFillRange = listFillRange
|
||||
self.cf = cf
|
||||
self.id = id
|
||||
|
||||
|
||||
class Control(Serialisable):
|
||||
|
||||
tagname = "control"
|
||||
|
||||
controlPr = Typed(expected_type=ControlProperty, allow_none=True)
|
||||
shapeId = Integer()
|
||||
name = String(allow_none=True)
|
||||
|
||||
__elements__ = ('controlPr',)
|
||||
|
||||
def __init__(self,
|
||||
controlPr=None,
|
||||
shapeId=None,
|
||||
name=None,
|
||||
):
|
||||
self.controlPr = controlPr
|
||||
self.shapeId = shapeId
|
||||
self.name = name
|
||||
|
||||
|
||||
class Controls(Serialisable):
|
||||
|
||||
tagname = "controls"
|
||||
|
||||
control = Sequence(expected_type=Control)
|
||||
|
||||
__elements__ = ('control',)
|
||||
|
||||
def __init__(self,
|
||||
control=(),
|
||||
):
|
||||
self.control = control
|
||||
|
70
lib/python3.13/site-packages/openpyxl/worksheet/copier.py
Normal file
70
lib/python3.13/site-packages/openpyxl/worksheet/copier.py
Normal file
@ -0,0 +1,70 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
#standard lib imports
|
||||
from copy import copy
|
||||
|
||||
from .worksheet import Worksheet
|
||||
|
||||
|
||||
class WorksheetCopy:
|
||||
"""
|
||||
Copy the values, styles, dimensions, merged cells, margins, and
|
||||
print/page setup from one worksheet to another within the same
|
||||
workbook.
|
||||
"""
|
||||
|
||||
def __init__(self, source_worksheet, target_worksheet):
|
||||
self.source = source_worksheet
|
||||
self.target = target_worksheet
|
||||
self._verify_resources()
|
||||
|
||||
|
||||
def _verify_resources(self):
|
||||
|
||||
if (not isinstance(self.source, Worksheet)
|
||||
and not isinstance(self.target, Worksheet)):
|
||||
raise TypeError("Can only copy worksheets")
|
||||
|
||||
if self.source is self.target:
|
||||
raise ValueError("Cannot copy a worksheet to itself")
|
||||
|
||||
if self.source.parent != self.target.parent:
|
||||
raise ValueError('Cannot copy between worksheets from different workbooks')
|
||||
|
||||
|
||||
def copy_worksheet(self):
|
||||
self._copy_cells()
|
||||
self._copy_dimensions()
|
||||
|
||||
self.target.sheet_format = copy(self.source.sheet_format)
|
||||
self.target.sheet_properties = copy(self.source.sheet_properties)
|
||||
self.target.merged_cells = copy(self.source.merged_cells)
|
||||
self.target.page_margins = copy(self.source.page_margins)
|
||||
self.target.page_setup = copy(self.source.page_setup)
|
||||
self.target.print_options = copy(self.source.print_options)
|
||||
|
||||
|
||||
def _copy_cells(self):
|
||||
for (row, col), source_cell in self.source._cells.items():
|
||||
target_cell = self.target.cell(column=col, row=row)
|
||||
|
||||
target_cell._value = source_cell._value
|
||||
target_cell.data_type = source_cell.data_type
|
||||
|
||||
if source_cell.has_style:
|
||||
target_cell._style = copy(source_cell._style)
|
||||
|
||||
if source_cell.hyperlink:
|
||||
target_cell._hyperlink = copy(source_cell.hyperlink)
|
||||
|
||||
if source_cell.comment:
|
||||
target_cell.comment = copy(source_cell.comment)
|
||||
|
||||
|
||||
def _copy_dimensions(self):
|
||||
for attr in ('row_dimensions', 'column_dimensions'):
|
||||
src = getattr(self.source, attr)
|
||||
target = getattr(self.target, attr)
|
||||
for key, dim in src.items():
|
||||
target[key] = copy(dim)
|
||||
target[key].worksheet = self.target
|
35
lib/python3.13/site-packages/openpyxl/worksheet/custom.py
Normal file
35
lib/python3.13/site-packages/openpyxl/worksheet/custom.py
Normal file
@ -0,0 +1,35 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
String,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
# can be done with a nested sequence
|
||||
|
||||
|
||||
class CustomProperty(Serialisable):
|
||||
|
||||
tagname = "customProperty"
|
||||
|
||||
name = String()
|
||||
|
||||
def __init__(self,
|
||||
name=None,
|
||||
):
|
||||
self.name = name
|
||||
|
||||
|
||||
class CustomProperties(Serialisable):
|
||||
|
||||
tagname = "customProperties"
|
||||
|
||||
customPr = Sequence(expected_type=CustomProperty)
|
||||
|
||||
__elements__ = ('customPr',)
|
||||
|
||||
def __init__(self,
|
||||
customPr=(),
|
||||
):
|
||||
self.customPr = customPr
|
||||
|
@ -0,0 +1,202 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
from operator import itemgetter
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
NoneSet,
|
||||
String,
|
||||
Sequence,
|
||||
Alias,
|
||||
Integer,
|
||||
Convertible,
|
||||
)
|
||||
from openpyxl.descriptors.nested import NestedText
|
||||
|
||||
from openpyxl.utils import (
|
||||
rows_from_range,
|
||||
coordinate_to_tuple,
|
||||
get_column_letter,
|
||||
)
|
||||
|
||||
|
||||
def collapse_cell_addresses(cells, input_ranges=()):
|
||||
""" Collapse a collection of cell co-ordinates down into an optimal
|
||||
range or collection of ranges.
|
||||
|
||||
E.g. Cells A1, A2, A3, B1, B2 and B3 should have the data-validation
|
||||
object applied, attempt to collapse down to a single range, A1:B3.
|
||||
|
||||
Currently only collapsing contiguous vertical ranges (i.e. above
|
||||
example results in A1:A3 B1:B3).
|
||||
"""
|
||||
|
||||
ranges = list(input_ranges)
|
||||
|
||||
# convert cell into row, col tuple
|
||||
raw_coords = (coordinate_to_tuple(cell) for cell in cells)
|
||||
|
||||
# group by column in order
|
||||
grouped_coords = defaultdict(list)
|
||||
for row, col in sorted(raw_coords, key=itemgetter(1)):
|
||||
grouped_coords[col].append(row)
|
||||
|
||||
# create range string from first and last row in column
|
||||
for col, cells in grouped_coords.items():
|
||||
col = get_column_letter(col)
|
||||
fmt = "{0}{1}:{2}{3}"
|
||||
if len(cells) == 1:
|
||||
fmt = "{0}{1}"
|
||||
r = fmt.format(col, min(cells), col, max(cells))
|
||||
ranges.append(r)
|
||||
|
||||
return " ".join(ranges)
|
||||
|
||||
|
||||
def expand_cell_ranges(range_string):
|
||||
"""
|
||||
Expand cell ranges to a sequence of addresses.
|
||||
Reverse of collapse_cell_addresses
|
||||
Eg. converts "A1:A2 B1:B2" to (A1, A2, B1, B2)
|
||||
"""
|
||||
# expand ranges to rows and then flatten
|
||||
rows = (rows_from_range(rs) for rs in range_string.split()) # list of rows
|
||||
cells = (chain(*row) for row in rows) # flatten rows
|
||||
return set(chain(*cells))
|
||||
|
||||
|
||||
from .cell_range import MultiCellRange
|
||||
|
||||
|
||||
class DataValidation(Serialisable):
|
||||
|
||||
tagname = "dataValidation"
|
||||
|
||||
sqref = Convertible(expected_type=MultiCellRange)
|
||||
cells = Alias("sqref")
|
||||
ranges = Alias("sqref")
|
||||
|
||||
showDropDown = Bool(allow_none=True)
|
||||
hide_drop_down = Alias('showDropDown')
|
||||
showInputMessage = Bool(allow_none=True)
|
||||
showErrorMessage = Bool(allow_none=True)
|
||||
allowBlank = Bool(allow_none=True)
|
||||
allow_blank = Alias('allowBlank')
|
||||
|
||||
errorTitle = String(allow_none = True)
|
||||
error = String(allow_none = True)
|
||||
promptTitle = String(allow_none = True)
|
||||
prompt = String(allow_none = True)
|
||||
formula1 = NestedText(allow_none=True, expected_type=str)
|
||||
formula2 = NestedText(allow_none=True, expected_type=str)
|
||||
|
||||
type = NoneSet(values=("whole", "decimal", "list", "date", "time",
|
||||
"textLength", "custom"))
|
||||
errorStyle = NoneSet(values=("stop", "warning", "information"))
|
||||
imeMode = NoneSet(values=("noControl", "off", "on", "disabled",
|
||||
"hiragana", "fullKatakana", "halfKatakana", "fullAlpha","halfAlpha",
|
||||
"fullHangul", "halfHangul"))
|
||||
operator = NoneSet(values=("between", "notBetween", "equal", "notEqual",
|
||||
"lessThan", "lessThanOrEqual", "greaterThan", "greaterThanOrEqual"))
|
||||
validation_type = Alias('type')
|
||||
|
||||
def __init__(self,
|
||||
type=None,
|
||||
formula1=None,
|
||||
formula2=None,
|
||||
showErrorMessage=False,
|
||||
showInputMessage=False,
|
||||
showDropDown=False,
|
||||
allowBlank=False,
|
||||
sqref=(),
|
||||
promptTitle=None,
|
||||
errorStyle=None,
|
||||
error=None,
|
||||
prompt=None,
|
||||
errorTitle=None,
|
||||
imeMode=None,
|
||||
operator=None,
|
||||
allow_blank=None,
|
||||
):
|
||||
self.sqref = sqref
|
||||
self.showDropDown = showDropDown
|
||||
self.imeMode = imeMode
|
||||
self.operator = operator
|
||||
self.formula1 = formula1
|
||||
self.formula2 = formula2
|
||||
if allow_blank is not None:
|
||||
allowBlank = allow_blank
|
||||
self.allowBlank = allowBlank
|
||||
self.showErrorMessage = showErrorMessage
|
||||
self.showInputMessage = showInputMessage
|
||||
self.type = type
|
||||
self.promptTitle = promptTitle
|
||||
self.errorStyle = errorStyle
|
||||
self.error = error
|
||||
self.prompt = prompt
|
||||
self.errorTitle = errorTitle
|
||||
|
||||
|
||||
def add(self, cell):
|
||||
"""Adds a cell or cell coordinate to this validator"""
|
||||
if hasattr(cell, "coordinate"):
|
||||
cell = cell.coordinate
|
||||
self.sqref += cell
|
||||
|
||||
|
||||
def __contains__(self, cell):
|
||||
if hasattr(cell, "coordinate"):
|
||||
cell = cell.coordinate
|
||||
return cell in self.sqref
|
||||
|
||||
|
||||
class DataValidationList(Serialisable):
|
||||
|
||||
tagname = "dataValidations"
|
||||
|
||||
disablePrompts = Bool(allow_none=True)
|
||||
xWindow = Integer(allow_none=True)
|
||||
yWindow = Integer(allow_none=True)
|
||||
dataValidation = Sequence(expected_type=DataValidation)
|
||||
|
||||
__elements__ = ('dataValidation',)
|
||||
__attrs__ = ('disablePrompts', 'xWindow', 'yWindow', 'count')
|
||||
|
||||
def __init__(self,
|
||||
disablePrompts=None,
|
||||
xWindow=None,
|
||||
yWindow=None,
|
||||
count=None,
|
||||
dataValidation=(),
|
||||
):
|
||||
self.disablePrompts = disablePrompts
|
||||
self.xWindow = xWindow
|
||||
self.yWindow = yWindow
|
||||
self.dataValidation = dataValidation
|
||||
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return len(self)
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return len(self.dataValidation)
|
||||
|
||||
|
||||
def append(self, dv):
|
||||
self.dataValidation.append(dv)
|
||||
|
||||
|
||||
def to_tree(self, tagname=None):
|
||||
"""
|
||||
Need to skip validations that have no cell ranges
|
||||
"""
|
||||
ranges = self.dataValidation # copy
|
||||
self.dataValidation = [r for r in self.dataValidation if bool(r.sqref)]
|
||||
xml = super().to_tree(tagname)
|
||||
self.dataValidation = ranges
|
||||
return xml
|
306
lib/python3.13/site-packages/openpyxl/worksheet/dimensions.py
Normal file
306
lib/python3.13/site-packages/openpyxl/worksheet/dimensions.py
Normal file
@ -0,0 +1,306 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from copy import copy
|
||||
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.utils import (
|
||||
get_column_letter,
|
||||
get_column_interval,
|
||||
column_index_from_string,
|
||||
range_boundaries,
|
||||
)
|
||||
from openpyxl.utils.units import DEFAULT_COLUMN_WIDTH
|
||||
from openpyxl.descriptors import (
|
||||
Integer,
|
||||
Float,
|
||||
Bool,
|
||||
Strict,
|
||||
String,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.styles.styleable import StyleableObject
|
||||
from openpyxl.utils.bound_dictionary import BoundDictionary
|
||||
from openpyxl.xml.functions import Element
|
||||
|
||||
|
||||
class Dimension(Strict, StyleableObject):
|
||||
"""Information about the display properties of a row or column."""
|
||||
__fields__ = ('hidden',
|
||||
'outlineLevel',
|
||||
'collapsed',)
|
||||
|
||||
index = Integer()
|
||||
hidden = Bool()
|
||||
outlineLevel = Integer(allow_none=True)
|
||||
outline_level = Alias('outlineLevel')
|
||||
collapsed = Bool()
|
||||
style = Alias('style_id')
|
||||
|
||||
|
||||
def __init__(self, index, hidden, outlineLevel,
|
||||
collapsed, worksheet, visible=True, style=None):
|
||||
super().__init__(sheet=worksheet, style_array=style)
|
||||
self.index = index
|
||||
self.hidden = hidden
|
||||
self.outlineLevel = outlineLevel
|
||||
self.collapsed = collapsed
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
for key in self.__fields__:
|
||||
value = getattr(self, key, None)
|
||||
if value:
|
||||
yield key, safe_string(value)
|
||||
|
||||
|
||||
def __copy__(self):
|
||||
cp = self.__new__(self.__class__)
|
||||
attrib = self.__dict__
|
||||
attrib['worksheet'] = self.parent
|
||||
cp.__init__(**attrib)
|
||||
cp._style = copy(self._style)
|
||||
return cp
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} Instance, Attributes={dict(self)}>"
|
||||
|
||||
|
||||
class RowDimension(Dimension):
|
||||
"""Information about the display properties of a row."""
|
||||
|
||||
__fields__ = Dimension.__fields__ + ('ht', 'customFormat', 'customHeight', 's',
|
||||
'thickBot', 'thickTop')
|
||||
r = Alias('index')
|
||||
s = Alias('style_id')
|
||||
ht = Float(allow_none=True)
|
||||
height = Alias('ht')
|
||||
thickBot = Bool()
|
||||
thickTop = Bool()
|
||||
|
||||
def __init__(self,
|
||||
worksheet,
|
||||
index=0,
|
||||
ht=None,
|
||||
customHeight=None, # do not write
|
||||
s=None,
|
||||
customFormat=None, # do not write
|
||||
hidden=False,
|
||||
outlineLevel=0,
|
||||
outline_level=None,
|
||||
collapsed=False,
|
||||
visible=None,
|
||||
height=None,
|
||||
r=None,
|
||||
spans=None,
|
||||
thickBot=None,
|
||||
thickTop=None,
|
||||
**kw
|
||||
):
|
||||
if r is not None:
|
||||
index = r
|
||||
if height is not None:
|
||||
ht = height
|
||||
self.ht = ht
|
||||
if visible is not None:
|
||||
hidden = not visible
|
||||
if outline_level is not None:
|
||||
outlineLevel = outline_level
|
||||
self.thickBot = thickBot
|
||||
self.thickTop = thickTop
|
||||
super().__init__(index, hidden, outlineLevel,
|
||||
collapsed, worksheet, style=s)
|
||||
|
||||
@property
|
||||
def customFormat(self):
|
||||
"""Always true if there is a style for the row"""
|
||||
return self.has_style
|
||||
|
||||
@property
|
||||
def customHeight(self):
|
||||
"""Always true if there is a height for the row"""
|
||||
return self.ht is not None
|
||||
|
||||
|
||||
class ColumnDimension(Dimension):
|
||||
"""Information about the display properties of a column."""
|
||||
|
||||
width = Float()
|
||||
bestFit = Bool()
|
||||
auto_size = Alias('bestFit')
|
||||
index = String()
|
||||
min = Integer(allow_none=True)
|
||||
max = Integer(allow_none=True)
|
||||
collapsed = Bool()
|
||||
|
||||
__fields__ = Dimension.__fields__ + ('width', 'bestFit', 'customWidth', 'style',
|
||||
'min', 'max')
|
||||
|
||||
def __init__(self,
|
||||
worksheet,
|
||||
index='A',
|
||||
width=DEFAULT_COLUMN_WIDTH,
|
||||
bestFit=False,
|
||||
hidden=False,
|
||||
outlineLevel=0,
|
||||
outline_level=None,
|
||||
collapsed=False,
|
||||
style=None,
|
||||
min=None,
|
||||
max=None,
|
||||
customWidth=False, # do not write
|
||||
visible=None,
|
||||
auto_size=None,):
|
||||
self.width = width
|
||||
self.min = min
|
||||
self.max = max
|
||||
if visible is not None:
|
||||
hidden = not visible
|
||||
if auto_size is not None:
|
||||
bestFit = auto_size
|
||||
self.bestFit = bestFit
|
||||
if outline_level is not None:
|
||||
outlineLevel = outline_level
|
||||
self.collapsed = collapsed
|
||||
super().__init__(index, hidden, outlineLevel,
|
||||
collapsed, worksheet, style=style)
|
||||
|
||||
|
||||
@property
|
||||
def customWidth(self):
|
||||
"""Always true if there is a width for the column"""
|
||||
return bool(self.width)
|
||||
|
||||
|
||||
def reindex(self):
|
||||
"""
|
||||
Set boundaries for column definition
|
||||
"""
|
||||
if not all([self.min, self.max]):
|
||||
self.min = self.max = column_index_from_string(self.index)
|
||||
|
||||
@property
|
||||
def range(self):
|
||||
"""Return the range of cells actually covered"""
|
||||
return f"{get_column_letter(self.min)}:{get_column_letter(self.max)}"
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
attrs = dict(self)
|
||||
if attrs.keys() != {'min', 'max'}:
|
||||
return Element("col", **attrs)
|
||||
|
||||
|
||||
class DimensionHolder(BoundDictionary):
|
||||
"""
|
||||
Allow columns to be grouped
|
||||
"""
|
||||
|
||||
def __init__(self, worksheet, reference="index", default_factory=None):
|
||||
self.worksheet = worksheet
|
||||
self.max_outline = None
|
||||
self.default_factory = default_factory
|
||||
super().__init__(reference, default_factory)
|
||||
|
||||
|
||||
def group(self, start, end=None, outline_level=1, hidden=False):
|
||||
"""allow grouping a range of consecutive rows or columns together
|
||||
|
||||
:param start: first row or column to be grouped (mandatory)
|
||||
:param end: last row or column to be grouped (optional, default to start)
|
||||
:param outline_level: outline level
|
||||
:param hidden: should the group be hidden on workbook open or not
|
||||
"""
|
||||
if end is None:
|
||||
end = start
|
||||
|
||||
if isinstance(self.default_factory(), ColumnDimension):
|
||||
new_dim = self[start]
|
||||
new_dim.outline_level = outline_level
|
||||
new_dim.hidden = hidden
|
||||
work_sequence = get_column_interval(start, end)[1:]
|
||||
for column_letter in work_sequence:
|
||||
if column_letter in self:
|
||||
del self[column_letter]
|
||||
new_dim.min, new_dim.max = map(column_index_from_string, (start, end))
|
||||
elif isinstance(self.default_factory(), RowDimension):
|
||||
for el in range(start, end + 1):
|
||||
new_dim = self.worksheet.row_dimensions[el]
|
||||
new_dim.outline_level = outline_level
|
||||
new_dim.hidden = hidden
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
|
||||
def sorter(value):
|
||||
value.reindex()
|
||||
return value.min
|
||||
|
||||
el = Element('cols')
|
||||
outlines = set()
|
||||
|
||||
for col in sorted(self.values(), key=sorter):
|
||||
obj = col.to_tree()
|
||||
if obj is not None:
|
||||
outlines.add(col.outlineLevel)
|
||||
el.append(obj)
|
||||
|
||||
if outlines:
|
||||
self.max_outline = max(outlines)
|
||||
|
||||
if len(el):
|
||||
return el # must have at least one child
|
||||
|
||||
|
||||
class SheetFormatProperties(Serialisable):
|
||||
|
||||
tagname = "sheetFormatPr"
|
||||
|
||||
baseColWidth = Integer(allow_none=True)
|
||||
defaultColWidth = Float(allow_none=True)
|
||||
defaultRowHeight = Float()
|
||||
customHeight = Bool(allow_none=True)
|
||||
zeroHeight = Bool(allow_none=True)
|
||||
thickTop = Bool(allow_none=True)
|
||||
thickBottom = Bool(allow_none=True)
|
||||
outlineLevelRow = Integer(allow_none=True)
|
||||
outlineLevelCol = Integer(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
baseColWidth=8, #according to spec
|
||||
defaultColWidth=None,
|
||||
defaultRowHeight=15,
|
||||
customHeight=None,
|
||||
zeroHeight=None,
|
||||
thickTop=None,
|
||||
thickBottom=None,
|
||||
outlineLevelRow=None,
|
||||
outlineLevelCol=None,
|
||||
):
|
||||
self.baseColWidth = baseColWidth
|
||||
self.defaultColWidth = defaultColWidth
|
||||
self.defaultRowHeight = defaultRowHeight
|
||||
self.customHeight = customHeight
|
||||
self.zeroHeight = zeroHeight
|
||||
self.thickTop = thickTop
|
||||
self.thickBottom = thickBottom
|
||||
self.outlineLevelRow = outlineLevelRow
|
||||
self.outlineLevelCol = outlineLevelCol
|
||||
|
||||
|
||||
class SheetDimension(Serialisable):
|
||||
|
||||
tagname = "dimension"
|
||||
|
||||
ref = String()
|
||||
|
||||
def __init__(self,
|
||||
ref=None,
|
||||
):
|
||||
self.ref = ref
|
||||
|
||||
|
||||
@property
|
||||
def boundaries(self):
|
||||
return range_boundaries(self.ref)
|
14
lib/python3.13/site-packages/openpyxl/worksheet/drawing.py
Normal file
14
lib/python3.13/site-packages/openpyxl/worksheet/drawing.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors.excel import Relation
|
||||
|
||||
|
||||
class Drawing(Serialisable):
|
||||
|
||||
tagname = "drawing"
|
||||
|
||||
id = Relation()
|
||||
|
||||
def __init__(self, id=None):
|
||||
self.id = id
|
93
lib/python3.13/site-packages/openpyxl/worksheet/errors.py
Normal file
93
lib/python3.13/site-packages/openpyxl/worksheet/errors.py
Normal file
@ -0,0 +1,93 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
String,
|
||||
Bool,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import CellRange
|
||||
|
||||
|
||||
class Extension(Serialisable):
|
||||
|
||||
tagname = "extension"
|
||||
|
||||
uri = String(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
uri=None,
|
||||
):
|
||||
self.uri = uri
|
||||
|
||||
|
||||
class ExtensionList(Serialisable):
|
||||
|
||||
tagname = "extensionList"
|
||||
|
||||
# uses element group EG_ExtensionList
|
||||
ext = Sequence(expected_type=Extension)
|
||||
|
||||
__elements__ = ('ext',)
|
||||
|
||||
def __init__(self,
|
||||
ext=(),
|
||||
):
|
||||
self.ext = ext
|
||||
|
||||
|
||||
class IgnoredError(Serialisable):
|
||||
|
||||
tagname = "ignoredError"
|
||||
|
||||
sqref = CellRange
|
||||
evalError = Bool(allow_none=True)
|
||||
twoDigitTextYear = Bool(allow_none=True)
|
||||
numberStoredAsText = Bool(allow_none=True)
|
||||
formula = Bool(allow_none=True)
|
||||
formulaRange = Bool(allow_none=True)
|
||||
unlockedFormula = Bool(allow_none=True)
|
||||
emptyCellReference = Bool(allow_none=True)
|
||||
listDataValidation = Bool(allow_none=True)
|
||||
calculatedColumn = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
sqref=None,
|
||||
evalError=False,
|
||||
twoDigitTextYear=False,
|
||||
numberStoredAsText=False,
|
||||
formula=False,
|
||||
formulaRange=False,
|
||||
unlockedFormula=False,
|
||||
emptyCellReference=False,
|
||||
listDataValidation=False,
|
||||
calculatedColumn=False,
|
||||
):
|
||||
self.sqref = sqref
|
||||
self.evalError = evalError
|
||||
self.twoDigitTextYear = twoDigitTextYear
|
||||
self.numberStoredAsText = numberStoredAsText
|
||||
self.formula = formula
|
||||
self.formulaRange = formulaRange
|
||||
self.unlockedFormula = unlockedFormula
|
||||
self.emptyCellReference = emptyCellReference
|
||||
self.listDataValidation = listDataValidation
|
||||
self.calculatedColumn = calculatedColumn
|
||||
|
||||
|
||||
class IgnoredErrors(Serialisable):
|
||||
|
||||
tagname = "ignoredErrors"
|
||||
|
||||
ignoredError = Sequence(expected_type=IgnoredError)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('ignoredError', 'extLst')
|
||||
|
||||
def __init__(self,
|
||||
ignoredError=(),
|
||||
extLst=None,
|
||||
):
|
||||
self.ignoredError = ignoredError
|
||||
self.extLst = extLst
|
||||
|
486
lib/python3.13/site-packages/openpyxl/worksheet/filters.py
Normal file
486
lib/python3.13/site-packages/openpyxl/worksheet/filters.py
Normal file
@ -0,0 +1,486 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
import re
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Alias,
|
||||
Typed,
|
||||
Set,
|
||||
Float,
|
||||
DateTime,
|
||||
NoneSet,
|
||||
Bool,
|
||||
Integer,
|
||||
String,
|
||||
Sequence,
|
||||
MinMax,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList, CellRange
|
||||
from openpyxl.descriptors.sequence import ValueSequence
|
||||
from openpyxl.utils import absolute_coordinate
|
||||
|
||||
|
||||
class SortCondition(Serialisable):
|
||||
|
||||
tagname = "sortCondition"
|
||||
|
||||
descending = Bool(allow_none=True)
|
||||
sortBy = NoneSet(values=(['value', 'cellColor', 'fontColor', 'icon']))
|
||||
ref = CellRange()
|
||||
customList = String(allow_none=True)
|
||||
dxfId = Integer(allow_none=True)
|
||||
iconSet = NoneSet(values=(['3Arrows', '3ArrowsGray', '3Flags',
|
||||
'3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2',
|
||||
'4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights',
|
||||
'5Arrows', '5ArrowsGray', '5Rating', '5Quarters']))
|
||||
iconId = Integer(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
ref=None,
|
||||
descending=None,
|
||||
sortBy=None,
|
||||
customList=None,
|
||||
dxfId=None,
|
||||
iconSet=None,
|
||||
iconId=None,
|
||||
):
|
||||
self.descending = descending
|
||||
self.sortBy = sortBy
|
||||
self.ref = ref
|
||||
self.customList = customList
|
||||
self.dxfId = dxfId
|
||||
self.iconSet = iconSet
|
||||
self.iconId = iconId
|
||||
|
||||
|
||||
class SortState(Serialisable):
|
||||
|
||||
tagname = "sortState"
|
||||
|
||||
columnSort = Bool(allow_none=True)
|
||||
caseSensitive = Bool(allow_none=True)
|
||||
sortMethod = NoneSet(values=(['stroke', 'pinYin']))
|
||||
ref = CellRange()
|
||||
sortCondition = Sequence(expected_type=SortCondition, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('sortCondition',)
|
||||
|
||||
def __init__(self,
|
||||
columnSort=None,
|
||||
caseSensitive=None,
|
||||
sortMethod=None,
|
||||
ref=None,
|
||||
sortCondition=(),
|
||||
extLst=None,
|
||||
):
|
||||
self.columnSort = columnSort
|
||||
self.caseSensitive = caseSensitive
|
||||
self.sortMethod = sortMethod
|
||||
self.ref = ref
|
||||
self.sortCondition = sortCondition
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return self.ref is not None
|
||||
|
||||
|
||||
|
||||
class IconFilter(Serialisable):
|
||||
|
||||
tagname = "iconFilter"
|
||||
|
||||
iconSet = Set(values=(['3Arrows', '3ArrowsGray', '3Flags',
|
||||
'3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2',
|
||||
'4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights',
|
||||
'5Arrows', '5ArrowsGray', '5Rating', '5Quarters']))
|
||||
iconId = Integer(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
iconSet=None,
|
||||
iconId=None,
|
||||
):
|
||||
self.iconSet = iconSet
|
||||
self.iconId = iconId
|
||||
|
||||
|
||||
class ColorFilter(Serialisable):
|
||||
|
||||
tagname = "colorFilter"
|
||||
|
||||
dxfId = Integer(allow_none=True)
|
||||
cellColor = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
dxfId=None,
|
||||
cellColor=None,
|
||||
):
|
||||
self.dxfId = dxfId
|
||||
self.cellColor = cellColor
|
||||
|
||||
|
||||
class DynamicFilter(Serialisable):
|
||||
|
||||
tagname = "dynamicFilter"
|
||||
|
||||
type = Set(values=(['null', 'aboveAverage', 'belowAverage', 'tomorrow',
|
||||
'today', 'yesterday', 'nextWeek', 'thisWeek', 'lastWeek', 'nextMonth',
|
||||
'thisMonth', 'lastMonth', 'nextQuarter', 'thisQuarter', 'lastQuarter',
|
||||
'nextYear', 'thisYear', 'lastYear', 'yearToDate', 'Q1', 'Q2', 'Q3', 'Q4',
|
||||
'M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11',
|
||||
'M12']))
|
||||
val = Float(allow_none=True)
|
||||
valIso = DateTime(allow_none=True)
|
||||
maxVal = Float(allow_none=True)
|
||||
maxValIso = DateTime(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
type=None,
|
||||
val=None,
|
||||
valIso=None,
|
||||
maxVal=None,
|
||||
maxValIso=None,
|
||||
):
|
||||
self.type = type
|
||||
self.val = val
|
||||
self.valIso = valIso
|
||||
self.maxVal = maxVal
|
||||
self.maxValIso = maxValIso
|
||||
|
||||
|
||||
class CustomFilter(Serialisable):
|
||||
|
||||
tagname = "customFilter"
|
||||
|
||||
val = String()
|
||||
operator = Set(values=['equal', 'lessThan', 'lessThanOrEqual',
|
||||
'notEqual', 'greaterThanOrEqual', 'greaterThan'])
|
||||
|
||||
def __init__(self, operator="equal", val=None):
|
||||
self.operator = operator
|
||||
self.val = val
|
||||
|
||||
|
||||
def _get_subtype(self):
|
||||
if self.val == " ":
|
||||
subtype = BlankFilter
|
||||
else:
|
||||
try:
|
||||
float(self.val)
|
||||
subtype = NumberFilter
|
||||
except ValueError:
|
||||
subtype = StringFilter
|
||||
return subtype
|
||||
|
||||
|
||||
def convert(self):
|
||||
"""Convert to more specific filter"""
|
||||
typ = self._get_subtype()
|
||||
if typ in (BlankFilter, NumberFilter):
|
||||
return typ(**dict(self))
|
||||
|
||||
operator, term = StringFilter._guess_operator(self.val)
|
||||
flt = StringFilter(operator, term)
|
||||
if self.operator == "notEqual":
|
||||
flt.exclude = True
|
||||
return flt
|
||||
|
||||
|
||||
class BlankFilter(CustomFilter):
|
||||
"""
|
||||
Exclude blanks
|
||||
"""
|
||||
|
||||
__attrs__ = ("operator", "val")
|
||||
|
||||
def __init__(self, **kw):
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def operator(self):
|
||||
return "notEqual"
|
||||
|
||||
|
||||
@property
|
||||
def val(self):
|
||||
return " "
|
||||
|
||||
|
||||
class NumberFilter(CustomFilter):
|
||||
|
||||
|
||||
operator = Set(values=
|
||||
['equal', 'lessThan', 'lessThanOrEqual',
|
||||
'notEqual', 'greaterThanOrEqual', 'greaterThan'])
|
||||
val = Float()
|
||||
|
||||
def __init__(self, operator="equal", val=None):
|
||||
self.operator = operator
|
||||
self.val = val
|
||||
|
||||
|
||||
string_format_mapping = {
|
||||
"contains": "*{}*",
|
||||
"startswith": "{}*",
|
||||
"endswith": "*{}",
|
||||
"wildcard": "{}",
|
||||
}
|
||||
|
||||
|
||||
class StringFilter(CustomFilter):
|
||||
|
||||
operator = Set(values=['contains', 'startswith', 'endswith', 'wildcard']
|
||||
)
|
||||
val = String()
|
||||
exclude = Bool()
|
||||
|
||||
|
||||
def __init__(self, operator="contains", val=None, exclude=False):
|
||||
self.operator = operator
|
||||
self.val = val
|
||||
self.exclude = exclude
|
||||
|
||||
|
||||
def _escape(self):
|
||||
"""Escape wildcards ~, * ? when serialising"""
|
||||
if self.operator == "wildcard":
|
||||
return self.val
|
||||
return re.sub(r"~|\*|\?", r"~\g<0>", self.val)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _unescape(value):
|
||||
"""
|
||||
Unescape value
|
||||
"""
|
||||
return re.sub(r"~(?P<op>[~*?])", r"\g<op>", value)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _guess_operator(value):
|
||||
value = StringFilter._unescape(value)
|
||||
endswith = r"^(?P<endswith>\*)(?P<term>[^\*\?]*$)"
|
||||
startswith = r"^(?P<term>[^\*\?]*)(?P<startswith>\*)$"
|
||||
contains = r"^(?P<contains>\*)(?P<term>[^\*\?]*)\*$"
|
||||
d = {"wildcard": True, "term": value}
|
||||
for pat in [contains, startswith, endswith]:
|
||||
m = re.match(pat, value)
|
||||
if m:
|
||||
d = m.groupdict()
|
||||
|
||||
term = d.pop("term")
|
||||
op = list(d)[0]
|
||||
return op, term
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, idx=None, namespace=None):
|
||||
fmt = string_format_mapping[self.operator]
|
||||
op = self.exclude and "notEqual" or "equal"
|
||||
value = fmt.format(self._escape())
|
||||
flt = CustomFilter(op, value)
|
||||
return flt.to_tree(tagname, idx, namespace)
|
||||
|
||||
|
||||
class CustomFilters(Serialisable):
|
||||
|
||||
tagname = "customFilters"
|
||||
|
||||
_and = Bool(allow_none=True)
|
||||
customFilter = Sequence(expected_type=CustomFilter) # min 1, max 2
|
||||
|
||||
__elements__ = ('customFilter',)
|
||||
|
||||
def __init__(self,
|
||||
_and=None,
|
||||
customFilter=(),
|
||||
):
|
||||
self._and = _and
|
||||
self.customFilter = customFilter
|
||||
|
||||
|
||||
class Top10(Serialisable):
|
||||
|
||||
tagname = "top10"
|
||||
|
||||
top = Bool(allow_none=True)
|
||||
percent = Bool(allow_none=True)
|
||||
val = Float()
|
||||
filterVal = Float(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
top=None,
|
||||
percent=None,
|
||||
val=None,
|
||||
filterVal=None,
|
||||
):
|
||||
self.top = top
|
||||
self.percent = percent
|
||||
self.val = val
|
||||
self.filterVal = filterVal
|
||||
|
||||
|
||||
class DateGroupItem(Serialisable):
|
||||
|
||||
tagname = "dateGroupItem"
|
||||
|
||||
year = Integer()
|
||||
month = MinMax(min=1, max=12, allow_none=True)
|
||||
day = MinMax(min=1, max=31, allow_none=True)
|
||||
hour = MinMax(min=0, max=23, allow_none=True)
|
||||
minute = MinMax(min=0, max=59, allow_none=True)
|
||||
second = Integer(min=0, max=59, allow_none=True)
|
||||
dateTimeGrouping = Set(values=(['year', 'month', 'day', 'hour', 'minute',
|
||||
'second']))
|
||||
|
||||
def __init__(self,
|
||||
year=None,
|
||||
month=None,
|
||||
day=None,
|
||||
hour=None,
|
||||
minute=None,
|
||||
second=None,
|
||||
dateTimeGrouping=None,
|
||||
):
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.second = second
|
||||
self.dateTimeGrouping = dateTimeGrouping
|
||||
|
||||
|
||||
class Filters(Serialisable):
|
||||
|
||||
tagname = "filters"
|
||||
|
||||
blank = Bool(allow_none=True)
|
||||
calendarType = NoneSet(values=["gregorian","gregorianUs",
|
||||
"gregorianMeFrench","gregorianArabic", "hijri","hebrew",
|
||||
"taiwan","japan", "thai","korea",
|
||||
"saka","gregorianXlitEnglish","gregorianXlitFrench"])
|
||||
filter = ValueSequence(expected_type=str)
|
||||
dateGroupItem = Sequence(expected_type=DateGroupItem, allow_none=True)
|
||||
|
||||
__elements__ = ('filter', 'dateGroupItem')
|
||||
|
||||
def __init__(self,
|
||||
blank=None,
|
||||
calendarType=None,
|
||||
filter=(),
|
||||
dateGroupItem=(),
|
||||
):
|
||||
self.blank = blank
|
||||
self.calendarType = calendarType
|
||||
self.filter = filter
|
||||
self.dateGroupItem = dateGroupItem
|
||||
|
||||
|
||||
class FilterColumn(Serialisable):
|
||||
|
||||
tagname = "filterColumn"
|
||||
|
||||
colId = Integer()
|
||||
col_id = Alias('colId')
|
||||
hiddenButton = Bool(allow_none=True)
|
||||
showButton = Bool(allow_none=True)
|
||||
# some elements are choice
|
||||
filters = Typed(expected_type=Filters, allow_none=True)
|
||||
top10 = Typed(expected_type=Top10, allow_none=True)
|
||||
customFilters = Typed(expected_type=CustomFilters, allow_none=True)
|
||||
dynamicFilter = Typed(expected_type=DynamicFilter, allow_none=True)
|
||||
colorFilter = Typed(expected_type=ColorFilter, allow_none=True)
|
||||
iconFilter = Typed(expected_type=IconFilter, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('filters', 'top10', 'customFilters', 'dynamicFilter',
|
||||
'colorFilter', 'iconFilter')
|
||||
|
||||
def __init__(self,
|
||||
colId=None,
|
||||
hiddenButton=False,
|
||||
showButton=True,
|
||||
filters=None,
|
||||
top10=None,
|
||||
customFilters=None,
|
||||
dynamicFilter=None,
|
||||
colorFilter=None,
|
||||
iconFilter=None,
|
||||
extLst=None,
|
||||
blank=None,
|
||||
vals=None,
|
||||
):
|
||||
self.colId = colId
|
||||
self.hiddenButton = hiddenButton
|
||||
self.showButton = showButton
|
||||
self.filters = filters
|
||||
self.top10 = top10
|
||||
self.customFilters = customFilters
|
||||
self.dynamicFilter = dynamicFilter
|
||||
self.colorFilter = colorFilter
|
||||
self.iconFilter = iconFilter
|
||||
if blank is not None and self.filters:
|
||||
self.filters.blank = blank
|
||||
if vals is not None and self.filters:
|
||||
self.filters.filter = vals
|
||||
|
||||
|
||||
class AutoFilter(Serialisable):
|
||||
|
||||
tagname = "autoFilter"
|
||||
|
||||
ref = CellRange()
|
||||
filterColumn = Sequence(expected_type=FilterColumn, allow_none=True)
|
||||
sortState = Typed(expected_type=SortState, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('filterColumn', 'sortState')
|
||||
|
||||
def __init__(self,
|
||||
ref=None,
|
||||
filterColumn=(),
|
||||
sortState=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.ref = ref
|
||||
self.filterColumn = filterColumn
|
||||
self.sortState = sortState
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return self.ref is not None
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return absolute_coordinate(self.ref)
|
||||
|
||||
|
||||
def add_filter_column(self, col_id, vals, blank=False):
|
||||
"""
|
||||
Add row filter for specified column.
|
||||
|
||||
:param col_id: Zero-origin column id. 0 means first column.
|
||||
:type col_id: int
|
||||
:param vals: Value list to show.
|
||||
:type vals: str[]
|
||||
:param blank: Show rows that have blank cell if True (default=``False``)
|
||||
:type blank: bool
|
||||
"""
|
||||
self.filterColumn.append(FilterColumn(colId=col_id, filters=Filters(blank=blank, filter=vals)))
|
||||
|
||||
|
||||
def add_sort_condition(self, ref, descending=False):
|
||||
"""
|
||||
Add sort condition for cpecified range of cells.
|
||||
|
||||
:param ref: range of the cells (e.g. 'A2:A150')
|
||||
:type ref: string, is the same as that of the filter
|
||||
:param descending: Descending sort order (default=``False``)
|
||||
:type descending: bool
|
||||
"""
|
||||
cond = SortCondition(ref, descending)
|
||||
if self.sortState is None:
|
||||
self.sortState = SortState(ref=self.ref)
|
||||
self.sortState.sortCondition.append(cond)
|
51
lib/python3.13/site-packages/openpyxl/worksheet/formula.py
Normal file
51
lib/python3.13/site-packages/openpyxl/worksheet/formula.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.compat import safe_string
|
||||
|
||||
class DataTableFormula:
|
||||
|
||||
|
||||
t = "dataTable"
|
||||
|
||||
def __init__(self,
|
||||
ref,
|
||||
ca=False,
|
||||
dt2D=False,
|
||||
dtr=False,
|
||||
r1=None,
|
||||
r2=None,
|
||||
del1=False,
|
||||
del2=False,
|
||||
**kw):
|
||||
self.ref = ref
|
||||
self.ca = ca
|
||||
self.dt2D = dt2D
|
||||
self.dtr = dtr
|
||||
self.r1 = r1
|
||||
self.r2 = r2
|
||||
self.del1 = del1
|
||||
self.del2 = del2
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
for k in ["t", "ref", "dt2D", "dtr", "r1", "r2", "del1", "del2", "ca"]:
|
||||
v = getattr(self, k)
|
||||
if v:
|
||||
yield k, safe_string(v)
|
||||
|
||||
|
||||
class ArrayFormula:
|
||||
|
||||
t = "array"
|
||||
|
||||
|
||||
def __init__(self, ref, text=None):
|
||||
self.ref = ref
|
||||
self.text = text
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
for k in ["t", "ref"]:
|
||||
v = getattr(self, k)
|
||||
if v:
|
||||
yield k, safe_string(v)
|
270
lib/python3.13/site-packages/openpyxl/worksheet/header_footer.py
Normal file
270
lib/python3.13/site-packages/openpyxl/worksheet/header_footer.py
Normal file
@ -0,0 +1,270 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
# Simplified implementation of headers and footers: let worksheets have separate items
|
||||
|
||||
import re
|
||||
from warnings import warn
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Alias,
|
||||
Bool,
|
||||
Strict,
|
||||
String,
|
||||
Integer,
|
||||
MatchPattern,
|
||||
Typed,
|
||||
)
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
|
||||
from openpyxl.xml.functions import Element
|
||||
from openpyxl.utils.escape import escape, unescape
|
||||
|
||||
|
||||
FONT_PATTERN = '&"(?P<font>.+)"'
|
||||
COLOR_PATTERN = "&K(?P<color>[A-F0-9]{6})"
|
||||
SIZE_REGEX = r"&(?P<size>\d+\s?)"
|
||||
FORMAT_REGEX = re.compile("{0}|{1}|{2}".format(FONT_PATTERN, COLOR_PATTERN,
|
||||
SIZE_REGEX)
|
||||
)
|
||||
|
||||
def _split_string(text):
|
||||
"""
|
||||
Split the combined (decoded) string into left, center and right parts
|
||||
|
||||
# See http://stackoverflow.com/questions/27711175/regex-with-multiple-optional-groups for discussion
|
||||
"""
|
||||
|
||||
ITEM_REGEX = re.compile("""
|
||||
(&L(?P<left>.+?))?
|
||||
(&C(?P<center>.+?))?
|
||||
(&R(?P<right>.+?))?
|
||||
$""", re.VERBOSE | re.DOTALL)
|
||||
|
||||
m = ITEM_REGEX.match(text)
|
||||
try:
|
||||
parts = m.groupdict()
|
||||
except AttributeError:
|
||||
warn("""Cannot parse header or footer so it will be ignored""")
|
||||
parts = {'left':'', 'right':'', 'center':''}
|
||||
return parts
|
||||
|
||||
|
||||
class _HeaderFooterPart(Strict):
|
||||
|
||||
"""
|
||||
Individual left/center/right header/footer part
|
||||
|
||||
Do not use directly.
|
||||
|
||||
Header & Footer ampersand codes:
|
||||
|
||||
* &A Inserts the worksheet name
|
||||
* &B Toggles bold
|
||||
* &D or &[Date] Inserts the current date
|
||||
* &E Toggles double-underline
|
||||
* &F or &[File] Inserts the workbook name
|
||||
* &I Toggles italic
|
||||
* &N or &[Pages] Inserts the total page count
|
||||
* &S Toggles strikethrough
|
||||
* &T Inserts the current time
|
||||
* &[Tab] Inserts the worksheet name
|
||||
* &U Toggles underline
|
||||
* &X Toggles superscript
|
||||
* &Y Toggles subscript
|
||||
* &P or &[Page] Inserts the current page number
|
||||
* &P+n Inserts the page number incremented by n
|
||||
* &P-n Inserts the page number decremented by n
|
||||
* &[Path] Inserts the workbook path
|
||||
* && Escapes the ampersand character
|
||||
* &"fontname" Selects the named font
|
||||
* &nn Selects the specified 2-digit font point size
|
||||
|
||||
Colours are in RGB Hex
|
||||
"""
|
||||
|
||||
text = String(allow_none=True)
|
||||
font = String(allow_none=True)
|
||||
size = Integer(allow_none=True)
|
||||
RGB = ("^[A-Fa-f0-9]{6}$")
|
||||
color = MatchPattern(allow_none=True, pattern=RGB)
|
||||
|
||||
|
||||
def __init__(self, text=None, font=None, size=None, color=None):
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.size = size
|
||||
self.color = color
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Convert to Excel HeaderFooter miniformat minus position
|
||||
"""
|
||||
fmt = []
|
||||
if self.font:
|
||||
fmt.append(u'&"{0}"'.format(self.font))
|
||||
if self.size:
|
||||
fmt.append("&{0} ".format(self.size))
|
||||
if self.color:
|
||||
fmt.append("&K{0}".format(self.color))
|
||||
return u"".join(fmt + [self.text])
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.text)
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, text):
|
||||
"""
|
||||
Convert from miniformat to object
|
||||
"""
|
||||
keys = ('font', 'color', 'size')
|
||||
kw = dict((k, v) for match in FORMAT_REGEX.findall(text)
|
||||
for k, v in zip(keys, match) if v)
|
||||
|
||||
kw['text'] = FORMAT_REGEX.sub('', text)
|
||||
|
||||
return cls(**kw)
|
||||
|
||||
|
||||
class HeaderFooterItem(Strict):
|
||||
"""
|
||||
Header or footer item
|
||||
|
||||
"""
|
||||
|
||||
left = Typed(expected_type=_HeaderFooterPart)
|
||||
center = Typed(expected_type=_HeaderFooterPart)
|
||||
centre = Alias("center")
|
||||
right = Typed(expected_type=_HeaderFooterPart)
|
||||
|
||||
__keys = ('L', 'C', 'R')
|
||||
|
||||
|
||||
def __init__(self, left=None, right=None, center=None):
|
||||
if left is None:
|
||||
left = _HeaderFooterPart()
|
||||
self.left = left
|
||||
if center is None:
|
||||
center = _HeaderFooterPart()
|
||||
self.center = center
|
||||
if right is None:
|
||||
right = _HeaderFooterPart()
|
||||
self.right = right
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Pack parts into a single string
|
||||
"""
|
||||
TRANSFORM = {'&[Tab]': '&A', '&[Pages]': '&N', '&[Date]': '&D',
|
||||
'&[Path]': '&Z', '&[Page]': '&P', '&[Time]': '&T', '&[File]': '&F',
|
||||
'&[Picture]': '&G'}
|
||||
|
||||
# escape keys and create regex
|
||||
SUBS_REGEX = re.compile("|".join(["({0})".format(re.escape(k))
|
||||
for k in TRANSFORM]))
|
||||
|
||||
def replace(match):
|
||||
"""
|
||||
Callback for re.sub
|
||||
Replace expanded control with mini-format equivalent
|
||||
"""
|
||||
sub = match.group(0)
|
||||
return TRANSFORM[sub]
|
||||
|
||||
txt = []
|
||||
for key, part in zip(
|
||||
self.__keys, [self.left, self.center, self.right]):
|
||||
if part.text is not None:
|
||||
txt.append(u"&{0}{1}".format(key, str(part)))
|
||||
txt = "".join(txt)
|
||||
txt = SUBS_REGEX.sub(replace, txt)
|
||||
return escape(txt)
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return any([self.left, self.center, self.right])
|
||||
|
||||
|
||||
|
||||
def to_tree(self, tagname):
|
||||
"""
|
||||
Return as XML node
|
||||
"""
|
||||
el = Element(tagname)
|
||||
el.text = str(self)
|
||||
return el
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
if node.text:
|
||||
text = unescape(node.text)
|
||||
parts = _split_string(text)
|
||||
for k, v in parts.items():
|
||||
if v is not None:
|
||||
parts[k] = _HeaderFooterPart.from_str(v)
|
||||
self = cls(**parts)
|
||||
return self
|
||||
|
||||
|
||||
class HeaderFooter(Serialisable):
|
||||
|
||||
tagname = "headerFooter"
|
||||
|
||||
differentOddEven = Bool(allow_none=True)
|
||||
differentFirst = Bool(allow_none=True)
|
||||
scaleWithDoc = Bool(allow_none=True)
|
||||
alignWithMargins = Bool(allow_none=True)
|
||||
oddHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
|
||||
oddFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)
|
||||
evenHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
|
||||
evenFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)
|
||||
firstHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
|
||||
firstFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)
|
||||
|
||||
__elements__ = ("oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter")
|
||||
|
||||
def __init__(self,
|
||||
differentOddEven=None,
|
||||
differentFirst=None,
|
||||
scaleWithDoc=None,
|
||||
alignWithMargins=None,
|
||||
oddHeader=None,
|
||||
oddFooter=None,
|
||||
evenHeader=None,
|
||||
evenFooter=None,
|
||||
firstHeader=None,
|
||||
firstFooter=None,
|
||||
):
|
||||
self.differentOddEven = differentOddEven
|
||||
self.differentFirst = differentFirst
|
||||
self.scaleWithDoc = scaleWithDoc
|
||||
self.alignWithMargins = alignWithMargins
|
||||
if oddHeader is None:
|
||||
oddHeader = HeaderFooterItem()
|
||||
self.oddHeader = oddHeader
|
||||
if oddFooter is None:
|
||||
oddFooter = HeaderFooterItem()
|
||||
self.oddFooter = oddFooter
|
||||
if evenHeader is None:
|
||||
evenHeader = HeaderFooterItem()
|
||||
self.evenHeader = evenHeader
|
||||
if evenFooter is None:
|
||||
evenFooter = HeaderFooterItem()
|
||||
self.evenFooter = evenFooter
|
||||
if firstHeader is None:
|
||||
firstHeader = HeaderFooterItem()
|
||||
self.firstHeader = firstHeader
|
||||
if firstFooter is None:
|
||||
firstFooter = HeaderFooterItem()
|
||||
self.firstFooter = firstFooter
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
parts = [getattr(self, attr) for attr in self.__attrs__ + self.__elements__]
|
||||
return any(parts)
|
||||
|
46
lib/python3.13/site-packages/openpyxl/worksheet/hyperlink.py
Normal file
46
lib/python3.13/site-packages/openpyxl/worksheet/hyperlink.py
Normal file
@ -0,0 +1,46 @@
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
String,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import Relation
|
||||
|
||||
|
||||
class Hyperlink(Serialisable):
|
||||
|
||||
tagname = "hyperlink"
|
||||
|
||||
ref = String()
|
||||
location = String(allow_none=True)
|
||||
tooltip = String(allow_none=True)
|
||||
display = String(allow_none=True)
|
||||
id = Relation()
|
||||
target = String(allow_none=True)
|
||||
|
||||
__attrs__ = ("ref", "location", "tooltip", "display", "id")
|
||||
|
||||
def __init__(self,
|
||||
ref=None,
|
||||
location=None,
|
||||
tooltip=None,
|
||||
display=None,
|
||||
id=None,
|
||||
target=None,
|
||||
):
|
||||
self.ref = ref
|
||||
self.location = location
|
||||
self.tooltip = tooltip
|
||||
self.display = display
|
||||
self.id = id
|
||||
self.target = target
|
||||
|
||||
|
||||
class HyperlinkList(Serialisable):
|
||||
|
||||
tagname = "hyperlinks"
|
||||
|
||||
__expected_type = Hyperlink
|
||||
hyperlink = Sequence(expected_type=__expected_type)
|
||||
|
||||
def __init__(self, hyperlink=()):
|
||||
self.hyperlink = hyperlink
|
141
lib/python3.13/site-packages/openpyxl/worksheet/merge.py
Normal file
141
lib/python3.13/site-packages/openpyxl/worksheet/merge.py
Normal file
@ -0,0 +1,141 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
import copy
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Integer,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
from openpyxl.cell.cell import MergedCell
|
||||
from openpyxl.styles.borders import Border
|
||||
|
||||
from .cell_range import CellRange
|
||||
|
||||
|
||||
class MergeCell(CellRange):
|
||||
|
||||
tagname = "mergeCell"
|
||||
ref = CellRange.coord
|
||||
|
||||
__attrs__ = ("ref",)
|
||||
|
||||
|
||||
def __init__(self,
|
||||
ref=None,
|
||||
):
|
||||
super().__init__(ref)
|
||||
|
||||
|
||||
def __copy__(self):
|
||||
return self.__class__(self.ref)
|
||||
|
||||
|
||||
class MergeCells(Serialisable):
|
||||
|
||||
tagname = "mergeCells"
|
||||
|
||||
count = Integer(allow_none=True)
|
||||
mergeCell = Sequence(expected_type=MergeCell, )
|
||||
|
||||
__elements__ = ('mergeCell',)
|
||||
__attrs__ = ('count',)
|
||||
|
||||
def __init__(self,
|
||||
count=None,
|
||||
mergeCell=(),
|
||||
):
|
||||
self.mergeCell = mergeCell
|
||||
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return len(self.mergeCell)
|
||||
|
||||
|
||||
class MergedCellRange(CellRange):
|
||||
|
||||
"""
|
||||
MergedCellRange stores the border information of a merged cell in the top
|
||||
left cell of the merged cell.
|
||||
The remaining cells in the merged cell are stored as MergedCell objects and
|
||||
get their border information from the upper left cell.
|
||||
"""
|
||||
|
||||
def __init__(self, worksheet, coord):
|
||||
self.ws = worksheet
|
||||
super().__init__(range_string=coord)
|
||||
self.start_cell = None
|
||||
self._get_borders()
|
||||
|
||||
|
||||
def _get_borders(self):
|
||||
"""
|
||||
If the upper left cell of the merged cell does not yet exist, it is
|
||||
created.
|
||||
The upper left cell gets the border information of the bottom and right
|
||||
border from the bottom right cell of the merged cell, if available.
|
||||
"""
|
||||
|
||||
# Top-left cell.
|
||||
self.start_cell = self.ws._cells.get((self.min_row, self.min_col))
|
||||
if self.start_cell is None:
|
||||
self.start_cell = self.ws.cell(row=self.min_row, column=self.min_col)
|
||||
|
||||
# Bottom-right cell
|
||||
end_cell = self.ws._cells.get((self.max_row, self.max_col))
|
||||
if end_cell is not None:
|
||||
self.start_cell.border += Border(right=end_cell.border.right,
|
||||
bottom=end_cell.border.bottom)
|
||||
|
||||
|
||||
def format(self):
|
||||
"""
|
||||
Each cell of the merged cell is created as MergedCell if it does not
|
||||
already exist.
|
||||
|
||||
The MergedCells at the edge of the merged cell gets its borders from
|
||||
the upper left cell.
|
||||
|
||||
- The top MergedCells get the top border from the top left cell.
|
||||
- The bottom MergedCells get the bottom border from the top left cell.
|
||||
- The left MergedCells get the left border from the top left cell.
|
||||
- The right MergedCells get the right border from the top left cell.
|
||||
"""
|
||||
|
||||
names = ['top', 'left', 'right', 'bottom']
|
||||
|
||||
for name in names:
|
||||
side = getattr(self.start_cell.border, name)
|
||||
if side and side.style is None:
|
||||
continue # don't need to do anything if there is no border style
|
||||
border = Border(**{name:side})
|
||||
for coord in getattr(self, name):
|
||||
cell = self.ws._cells.get(coord)
|
||||
if cell is None:
|
||||
row, col = coord
|
||||
cell = MergedCell(self.ws, row=row, column=col)
|
||||
self.ws._cells[(cell.row, cell.column)] = cell
|
||||
cell.border += border
|
||||
|
||||
protected = self.start_cell.protection is not None
|
||||
if protected:
|
||||
protection = copy.copy(self.start_cell.protection)
|
||||
for coord in self.cells:
|
||||
cell = self.ws._cells.get(coord)
|
||||
if cell is None:
|
||||
row, col = coord
|
||||
cell = MergedCell(self.ws, row=row, column=col)
|
||||
self.ws._cells[(cell.row, cell.column)] = cell
|
||||
|
||||
if protected:
|
||||
cell.protection = protection
|
||||
|
||||
|
||||
def __contains__(self, coord):
|
||||
return coord in CellRange(self.coord)
|
||||
|
||||
|
||||
def __copy__(self):
|
||||
return self.__class__(self.ws, self.coord)
|
133
lib/python3.13/site-packages/openpyxl/worksheet/ole.py
Normal file
133
lib/python3.13/site-packages/openpyxl/worksheet/ole.py
Normal file
@ -0,0 +1,133 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Integer,
|
||||
String,
|
||||
Set,
|
||||
Bool,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
from openpyxl.drawing.spreadsheet_drawing import AnchorMarker
|
||||
from openpyxl.xml.constants import SHEET_DRAWING_NS
|
||||
|
||||
|
||||
class ObjectAnchor(Serialisable):
|
||||
|
||||
tagname = "anchor"
|
||||
|
||||
_from = Typed(expected_type=AnchorMarker, namespace=SHEET_DRAWING_NS)
|
||||
to = Typed(expected_type=AnchorMarker, namespace=SHEET_DRAWING_NS)
|
||||
moveWithCells = Bool(allow_none=True)
|
||||
sizeWithCells = Bool(allow_none=True)
|
||||
z_order = Integer(allow_none=True, hyphenated=True)
|
||||
|
||||
|
||||
def __init__(self,
|
||||
_from=None,
|
||||
to=None,
|
||||
moveWithCells=False,
|
||||
sizeWithCells=False,
|
||||
z_order=None,
|
||||
):
|
||||
self._from = _from
|
||||
self.to = to
|
||||
self.moveWithCells = moveWithCells
|
||||
self.sizeWithCells = sizeWithCells
|
||||
self.z_order = z_order
|
||||
|
||||
|
||||
class ObjectPr(Serialisable):
|
||||
|
||||
tagname = "objectPr"
|
||||
|
||||
anchor = Typed(expected_type=ObjectAnchor, )
|
||||
locked = Bool(allow_none=True)
|
||||
defaultSize = Bool(allow_none=True)
|
||||
_print = Bool(allow_none=True)
|
||||
disabled = Bool(allow_none=True)
|
||||
uiObject = Bool(allow_none=True)
|
||||
autoFill = Bool(allow_none=True)
|
||||
autoLine = Bool(allow_none=True)
|
||||
autoPict = Bool(allow_none=True)
|
||||
macro = String()
|
||||
altText = String(allow_none=True)
|
||||
dde = Bool(allow_none=True)
|
||||
|
||||
__elements__ = ('anchor',)
|
||||
|
||||
def __init__(self,
|
||||
anchor=None,
|
||||
locked=True,
|
||||
defaultSize=True,
|
||||
_print=True,
|
||||
disabled=False,
|
||||
uiObject=False,
|
||||
autoFill=True,
|
||||
autoLine=True,
|
||||
autoPict=True,
|
||||
macro=None,
|
||||
altText=None,
|
||||
dde=False,
|
||||
):
|
||||
self.anchor = anchor
|
||||
self.locked = locked
|
||||
self.defaultSize = defaultSize
|
||||
self._print = _print
|
||||
self.disabled = disabled
|
||||
self.uiObject = uiObject
|
||||
self.autoFill = autoFill
|
||||
self.autoLine = autoLine
|
||||
self.autoPict = autoPict
|
||||
self.macro = macro
|
||||
self.altText = altText
|
||||
self.dde = dde
|
||||
|
||||
|
||||
class OleObject(Serialisable):
|
||||
|
||||
tagname = "oleObject"
|
||||
|
||||
objectPr = Typed(expected_type=ObjectPr, allow_none=True)
|
||||
progId = String(allow_none=True)
|
||||
dvAspect = Set(values=(['DVASPECT_CONTENT', 'DVASPECT_ICON']))
|
||||
link = String(allow_none=True)
|
||||
oleUpdate = Set(values=(['OLEUPDATE_ALWAYS', 'OLEUPDATE_ONCALL']))
|
||||
autoLoad = Bool(allow_none=True)
|
||||
shapeId = Integer()
|
||||
|
||||
__elements__ = ('objectPr',)
|
||||
|
||||
def __init__(self,
|
||||
objectPr=None,
|
||||
progId=None,
|
||||
dvAspect='DVASPECT_CONTENT',
|
||||
link=None,
|
||||
oleUpdate=None,
|
||||
autoLoad=False,
|
||||
shapeId=None,
|
||||
):
|
||||
self.objectPr = objectPr
|
||||
self.progId = progId
|
||||
self.dvAspect = dvAspect
|
||||
self.link = link
|
||||
self.oleUpdate = oleUpdate
|
||||
self.autoLoad = autoLoad
|
||||
self.shapeId = shapeId
|
||||
|
||||
|
||||
class OleObjects(Serialisable):
|
||||
|
||||
tagname = "oleObjects"
|
||||
|
||||
oleObject = Sequence(expected_type=OleObject)
|
||||
|
||||
__elements__ = ('oleObject',)
|
||||
|
||||
def __init__(self,
|
||||
oleObject=(),
|
||||
):
|
||||
self.oleObject = oleObject
|
||||
|
174
lib/python3.13/site-packages/openpyxl/worksheet/page.py
Normal file
174
lib/python3.13/site-packages/openpyxl/worksheet/page.py
Normal file
@ -0,0 +1,174 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Float,
|
||||
Bool,
|
||||
Integer,
|
||||
NoneSet,
|
||||
)
|
||||
from openpyxl.descriptors.excel import UniversalMeasure, Relation
|
||||
|
||||
|
||||
class PrintPageSetup(Serialisable):
|
||||
""" Worksheet print page setup """
|
||||
|
||||
tagname = "pageSetup"
|
||||
|
||||
orientation = NoneSet(values=("default", "portrait", "landscape"))
|
||||
paperSize = Integer(allow_none=True)
|
||||
scale = Integer(allow_none=True)
|
||||
fitToHeight = Integer(allow_none=True)
|
||||
fitToWidth = Integer(allow_none=True)
|
||||
firstPageNumber = Integer(allow_none=True)
|
||||
useFirstPageNumber = Bool(allow_none=True)
|
||||
paperHeight = UniversalMeasure(allow_none=True)
|
||||
paperWidth = UniversalMeasure(allow_none=True)
|
||||
pageOrder = NoneSet(values=("downThenOver", "overThenDown"))
|
||||
usePrinterDefaults = Bool(allow_none=True)
|
||||
blackAndWhite = Bool(allow_none=True)
|
||||
draft = Bool(allow_none=True)
|
||||
cellComments = NoneSet(values=("asDisplayed", "atEnd"))
|
||||
errors = NoneSet(values=("displayed", "blank", "dash", "NA"))
|
||||
horizontalDpi = Integer(allow_none=True)
|
||||
verticalDpi = Integer(allow_none=True)
|
||||
copies = Integer(allow_none=True)
|
||||
id = Relation()
|
||||
|
||||
|
||||
def __init__(self,
|
||||
worksheet=None,
|
||||
orientation=None,
|
||||
paperSize=None,
|
||||
scale=None,
|
||||
fitToHeight=None,
|
||||
fitToWidth=None,
|
||||
firstPageNumber=None,
|
||||
useFirstPageNumber=None,
|
||||
paperHeight=None,
|
||||
paperWidth=None,
|
||||
pageOrder=None,
|
||||
usePrinterDefaults=None,
|
||||
blackAndWhite=None,
|
||||
draft=None,
|
||||
cellComments=None,
|
||||
errors=None,
|
||||
horizontalDpi=None,
|
||||
verticalDpi=None,
|
||||
copies=None,
|
||||
id=None):
|
||||
self._parent = worksheet
|
||||
self.orientation = orientation
|
||||
self.paperSize = paperSize
|
||||
self.scale = scale
|
||||
self.fitToHeight = fitToHeight
|
||||
self.fitToWidth = fitToWidth
|
||||
self.firstPageNumber = firstPageNumber
|
||||
self.useFirstPageNumber = useFirstPageNumber
|
||||
self.paperHeight = paperHeight
|
||||
self.paperWidth = paperWidth
|
||||
self.pageOrder = pageOrder
|
||||
self.usePrinterDefaults = usePrinterDefaults
|
||||
self.blackAndWhite = blackAndWhite
|
||||
self.draft = draft
|
||||
self.cellComments = cellComments
|
||||
self.errors = errors
|
||||
self.horizontalDpi = horizontalDpi
|
||||
self.verticalDpi = verticalDpi
|
||||
self.copies = copies
|
||||
self.id = id
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return bool(dict(self))
|
||||
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def sheet_properties(self):
|
||||
"""
|
||||
Proxy property
|
||||
"""
|
||||
return self._parent.sheet_properties.pageSetUpPr
|
||||
|
||||
|
||||
@property
|
||||
def fitToPage(self):
|
||||
return self.sheet_properties.fitToPage
|
||||
|
||||
|
||||
@fitToPage.setter
|
||||
def fitToPage(self, value):
|
||||
self.sheet_properties.fitToPage = value
|
||||
|
||||
|
||||
@property
|
||||
def autoPageBreaks(self):
|
||||
return self.sheet_properties.autoPageBreaks
|
||||
|
||||
|
||||
@autoPageBreaks.setter
|
||||
def autoPageBreaks(self, value):
|
||||
self.sheet_properties.autoPageBreaks = value
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
self = super().from_tree(node)
|
||||
self.id = None # strip link to binary settings
|
||||
return self
|
||||
|
||||
|
||||
class PrintOptions(Serialisable):
|
||||
""" Worksheet print options """
|
||||
|
||||
tagname = "printOptions"
|
||||
horizontalCentered = Bool(allow_none=True)
|
||||
verticalCentered = Bool(allow_none=True)
|
||||
headings = Bool(allow_none=True)
|
||||
gridLines = Bool(allow_none=True)
|
||||
gridLinesSet = Bool(allow_none=True)
|
||||
|
||||
def __init__(self, horizontalCentered=None,
|
||||
verticalCentered=None,
|
||||
headings=None,
|
||||
gridLines=None,
|
||||
gridLinesSet=None,
|
||||
):
|
||||
self.horizontalCentered = horizontalCentered
|
||||
self.verticalCentered = verticalCentered
|
||||
self.headings = headings
|
||||
self.gridLines = gridLines
|
||||
self.gridLinesSet = gridLinesSet
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return bool(dict(self))
|
||||
|
||||
|
||||
class PageMargins(Serialisable):
|
||||
"""
|
||||
Information about page margins for view/print layouts.
|
||||
Standard values (in inches)
|
||||
left, right = 0.75
|
||||
top, bottom = 1
|
||||
header, footer = 0.5
|
||||
"""
|
||||
tagname = "pageMargins"
|
||||
|
||||
left = Float()
|
||||
right = Float()
|
||||
top = Float()
|
||||
bottom = Float()
|
||||
header = Float()
|
||||
footer = Float()
|
||||
|
||||
def __init__(self, left=0.75, right=0.75, top=1, bottom=1, header=0.5,
|
||||
footer=0.5):
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
self.header = header
|
||||
self.footer = footer
|
94
lib/python3.13/site-packages/openpyxl/worksheet/pagebreak.py
Normal file
94
lib/python3.13/site-packages/openpyxl/worksheet/pagebreak.py
Normal file
@ -0,0 +1,94 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Integer,
|
||||
Bool,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
|
||||
class Break(Serialisable):
|
||||
|
||||
tagname = "brk"
|
||||
|
||||
id = Integer(allow_none=True)
|
||||
min = Integer(allow_none=True)
|
||||
max = Integer(allow_none=True)
|
||||
man = Bool(allow_none=True)
|
||||
pt = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
id=0,
|
||||
min=0,
|
||||
max=16383,
|
||||
man=True,
|
||||
pt=None,
|
||||
):
|
||||
self.id = id
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.man = man
|
||||
self.pt = pt
|
||||
|
||||
|
||||
class RowBreak(Serialisable):
|
||||
|
||||
tagname = "rowBreaks"
|
||||
|
||||
count = Integer(allow_none=True)
|
||||
manualBreakCount = Integer(allow_none=True)
|
||||
brk = Sequence(expected_type=Break, allow_none=True)
|
||||
|
||||
__elements__ = ('brk',)
|
||||
__attrs__ = ("count", "manualBreakCount",)
|
||||
|
||||
def __init__(self,
|
||||
count=None,
|
||||
manualBreakCount=None,
|
||||
brk=(),
|
||||
):
|
||||
self.brk = brk
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return len(self.brk) > 0
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return len(self.brk)
|
||||
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return len(self)
|
||||
|
||||
|
||||
@property
|
||||
def manualBreakCount(self):
|
||||
return len(self)
|
||||
|
||||
|
||||
def append(self, brk=None):
|
||||
"""
|
||||
Add a page break
|
||||
"""
|
||||
vals = list(self.brk)
|
||||
if not isinstance(brk, Break):
|
||||
brk = Break(id=self.count+1)
|
||||
vals.append(brk)
|
||||
self.brk = vals
|
||||
|
||||
|
||||
PageBreak = RowBreak
|
||||
|
||||
|
||||
class ColBreak(RowBreak):
|
||||
|
||||
tagname = "colBreaks"
|
||||
|
||||
count = RowBreak.count
|
||||
manualBreakCount = RowBreak.manualBreakCount
|
||||
brk = RowBreak.brk
|
||||
|
||||
__attrs__ = RowBreak.__attrs__
|
@ -0,0 +1,8 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
# same as related
|
||||
|
||||
class SheetBackgroundPicture(Serialisable):
|
||||
|
||||
tagname = "sheetBackgroundPicture"
|
@ -0,0 +1,184 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
import re
|
||||
from openpyxl.descriptors import (
|
||||
Strict,
|
||||
Integer,
|
||||
String,
|
||||
Typed,
|
||||
)
|
||||
from openpyxl.utils import quote_sheetname, absolute_coordinate
|
||||
from openpyxl.utils.cell import SHEET_TITLE, SHEETRANGE_RE, RANGE_EXPR
|
||||
|
||||
from .cell_range import MultiCellRange
|
||||
|
||||
COL_RANGE = r"""(?P<cols>[$]?(?P<min_col>[a-zA-Z]{1,3}):[$]?(?P<max_col>[a-zA-Z]{1,3}))"""
|
||||
COL_RANGE_RE = re.compile(COL_RANGE)
|
||||
ROW_RANGE = r"""(?P<rows>[$]?(?P<min_row>\d+):[$]?(?P<max_row>\d+))"""
|
||||
ROW_RANGE_RE = re.compile(ROW_RANGE)
|
||||
TITLES_REGEX = re.compile("""{0}{1}?,?{2}?,?""".format(SHEET_TITLE, ROW_RANGE, COL_RANGE),
|
||||
re.VERBOSE)
|
||||
PRINT_AREA_RE = re.compile(f"({SHEET_TITLE})?(?P<cells>{RANGE_EXPR})", re.VERBOSE)
|
||||
|
||||
class ColRange(Strict):
|
||||
"""
|
||||
Represent a range of at least one column
|
||||
"""
|
||||
|
||||
min_col = String()
|
||||
max_col = String()
|
||||
|
||||
|
||||
def __init__(self, range_string=None, min_col=None, max_col=None):
|
||||
if range_string is not None:
|
||||
match = COL_RANGE_RE.match(range_string)
|
||||
if not match:
|
||||
raise ValueError(f"{range_string} is not a valid column range")
|
||||
min_col, max_col = match.groups()[1:]
|
||||
self.min_col = min_col
|
||||
self.max_col = max_col
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return (self.min_col == other.min_col
|
||||
and
|
||||
self.max_col == other.max_col)
|
||||
elif isinstance(other, str):
|
||||
return (str(self) == other
|
||||
or
|
||||
f"{self.min_col}:{self.max_col}")
|
||||
return False
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"Range of columns from '{self.min_col}' to '{self.max_col}'"
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"${self.min_col}:${self.max_col}"
|
||||
|
||||
|
||||
class RowRange(Strict):
|
||||
"""
|
||||
Represent a range of at least one row
|
||||
"""
|
||||
|
||||
min_row = Integer()
|
||||
max_row = Integer()
|
||||
|
||||
def __init__(self, range_string=None, min_row=None, max_row=None):
|
||||
if range_string is not None:
|
||||
match = ROW_RANGE_RE.match(range_string)
|
||||
if not match:
|
||||
raise ValueError(f"{range_string} is not a valid row range")
|
||||
min_row, max_row = match.groups()[1:]
|
||||
self.min_row = min_row
|
||||
self.max_row = max_row
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return (self.min_row == other.min_row
|
||||
and
|
||||
self.max_row == other.max_row)
|
||||
elif isinstance(other, str):
|
||||
return (str(self) == other
|
||||
or
|
||||
f"{self.min_row}:{self.max_row}")
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f"Range of rows from '{self.min_row}' to '{self.max_row}'"
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"${self.min_row}:${self.max_row}"
|
||||
|
||||
|
||||
class PrintTitles(Strict):
|
||||
"""
|
||||
Contains at least either a range of rows or columns
|
||||
"""
|
||||
|
||||
cols = Typed(expected_type=ColRange, allow_none=True)
|
||||
rows = Typed(expected_type=RowRange, allow_none=True)
|
||||
title = String()
|
||||
|
||||
|
||||
def __init__(self, cols=None, rows=None, title=""):
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
self.title = title
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value):
|
||||
kw = dict((k, v) for match in TITLES_REGEX.finditer(value)
|
||||
for k, v in match.groupdict().items() if v)
|
||||
|
||||
if not kw:
|
||||
raise ValueError(f"{value} is not a valid print titles definition")
|
||||
|
||||
cols = rows = None
|
||||
|
||||
if "cols" in kw:
|
||||
cols = ColRange(kw["cols"])
|
||||
if "rows" in kw:
|
||||
rows = RowRange(kw["rows"])
|
||||
|
||||
title = kw.get("quoted") or kw.get("notquoted")
|
||||
|
||||
return cls(cols=cols, rows=rows, title=title)
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return (self.cols == other.cols
|
||||
and
|
||||
self.rows == other.rows
|
||||
and
|
||||
self.title == other.title)
|
||||
elif isinstance(other, str):
|
||||
return str(self) == other
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f"Print titles for sheet {self.title} cols {self.rows}, rows {self.cols}"
|
||||
|
||||
|
||||
def __str__(self):
|
||||
title = quote_sheetname(self.title)
|
||||
titles = ",".join([f"{title}!{value}" for value in (self.rows, self.cols) if value])
|
||||
return titles or ""
|
||||
|
||||
|
||||
class PrintArea(MultiCellRange):
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value):
|
||||
new = []
|
||||
for m in PRINT_AREA_RE.finditer(value): # can be multiple
|
||||
coord = m.group("cells")
|
||||
if coord:
|
||||
new.append(coord)
|
||||
return cls(new)
|
||||
|
||||
|
||||
def __init__(self, ranges=(), title=""):
|
||||
self.title = ""
|
||||
super().__init__(ranges)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
if self.ranges:
|
||||
return ",".join([f"{quote_sheetname(self.title)}!{absolute_coordinate(str(range))}"
|
||||
for range in self.sorted()])
|
||||
return ""
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
super().__eq__(other)
|
||||
if isinstance(other, str):
|
||||
return str(self) == other
|
@ -0,0 +1,97 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""Worksheet Properties"""
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import String, Bool, Typed
|
||||
from openpyxl.styles.colors import ColorDescriptor
|
||||
|
||||
|
||||
class Outline(Serialisable):
|
||||
|
||||
tagname = "outlinePr"
|
||||
|
||||
applyStyles = Bool(allow_none=True)
|
||||
summaryBelow = Bool(allow_none=True)
|
||||
summaryRight = Bool(allow_none=True)
|
||||
showOutlineSymbols = Bool(allow_none=True)
|
||||
|
||||
|
||||
def __init__(self,
|
||||
applyStyles=None,
|
||||
summaryBelow=None,
|
||||
summaryRight=None,
|
||||
showOutlineSymbols=None
|
||||
):
|
||||
self.applyStyles = applyStyles
|
||||
self.summaryBelow = summaryBelow
|
||||
self.summaryRight = summaryRight
|
||||
self.showOutlineSymbols = showOutlineSymbols
|
||||
|
||||
|
||||
class PageSetupProperties(Serialisable):
|
||||
|
||||
tagname = "pageSetUpPr"
|
||||
|
||||
autoPageBreaks = Bool(allow_none=True)
|
||||
fitToPage = Bool(allow_none=True)
|
||||
|
||||
def __init__(self, autoPageBreaks=None, fitToPage=None):
|
||||
self.autoPageBreaks = autoPageBreaks
|
||||
self.fitToPage = fitToPage
|
||||
|
||||
|
||||
class WorksheetProperties(Serialisable):
|
||||
|
||||
tagname = "sheetPr"
|
||||
|
||||
codeName = String(allow_none=True)
|
||||
enableFormatConditionsCalculation = Bool(allow_none=True)
|
||||
filterMode = Bool(allow_none=True)
|
||||
published = Bool(allow_none=True)
|
||||
syncHorizontal = Bool(allow_none=True)
|
||||
syncRef = String(allow_none=True)
|
||||
syncVertical = Bool(allow_none=True)
|
||||
transitionEvaluation = Bool(allow_none=True)
|
||||
transitionEntry = Bool(allow_none=True)
|
||||
tabColor = ColorDescriptor(allow_none=True)
|
||||
outlinePr = Typed(expected_type=Outline, allow_none=True)
|
||||
pageSetUpPr = Typed(expected_type=PageSetupProperties, allow_none=True)
|
||||
|
||||
__elements__ = ('tabColor', 'outlinePr', 'pageSetUpPr')
|
||||
|
||||
|
||||
def __init__(self,
|
||||
codeName=None,
|
||||
enableFormatConditionsCalculation=None,
|
||||
filterMode=None,
|
||||
published=None,
|
||||
syncHorizontal=None,
|
||||
syncRef=None,
|
||||
syncVertical=None,
|
||||
transitionEvaluation=None,
|
||||
transitionEntry=None,
|
||||
tabColor=None,
|
||||
outlinePr=None,
|
||||
pageSetUpPr=None
|
||||
):
|
||||
""" Attributes """
|
||||
self.codeName = codeName
|
||||
self.enableFormatConditionsCalculation = enableFormatConditionsCalculation
|
||||
self.filterMode = filterMode
|
||||
self.published = published
|
||||
self.syncHorizontal = syncHorizontal
|
||||
self.syncRef = syncRef
|
||||
self.syncVertical = syncVertical
|
||||
self.transitionEvaluation = transitionEvaluation
|
||||
self.transitionEntry = transitionEntry
|
||||
""" Elements """
|
||||
self.tabColor = tabColor
|
||||
if outlinePr is None:
|
||||
self.outlinePr = Outline(summaryBelow=True, summaryRight=True)
|
||||
else:
|
||||
self.outlinePr = outlinePr
|
||||
|
||||
if pageSetUpPr is None:
|
||||
pageSetUpPr = PageSetupProperties()
|
||||
self.pageSetUpPr = pageSetUpPr
|
120
lib/python3.13/site-packages/openpyxl/worksheet/protection.py
Normal file
120
lib/python3.13/site-packages/openpyxl/worksheet/protection.py
Normal file
@ -0,0 +1,120 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
String,
|
||||
Alias,
|
||||
Integer,
|
||||
)
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors.excel import (
|
||||
Base64Binary,
|
||||
)
|
||||
from openpyxl.utils.protection import hash_password
|
||||
|
||||
|
||||
class _Protected:
|
||||
_password = None
|
||||
|
||||
def set_password(self, value='', already_hashed=False):
|
||||
"""Set a password on this sheet."""
|
||||
if not already_hashed:
|
||||
value = hash_password(value)
|
||||
self._password = value
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
"""Return the password value, regardless of hash."""
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
"""Set a password directly, forcing a hash step."""
|
||||
self.set_password(value)
|
||||
|
||||
|
||||
class SheetProtection(Serialisable, _Protected):
|
||||
"""
|
||||
Information about protection of various aspects of a sheet. True values
|
||||
mean that protection for the object or action is active This is the
|
||||
**default** when protection is active, ie. users cannot do something
|
||||
"""
|
||||
|
||||
tagname = "sheetProtection"
|
||||
|
||||
sheet = Bool()
|
||||
enabled = Alias('sheet')
|
||||
objects = Bool()
|
||||
scenarios = Bool()
|
||||
formatCells = Bool()
|
||||
formatColumns = Bool()
|
||||
formatRows = Bool()
|
||||
insertColumns = Bool()
|
||||
insertRows = Bool()
|
||||
insertHyperlinks = Bool()
|
||||
deleteColumns = Bool()
|
||||
deleteRows = Bool()
|
||||
selectLockedCells = Bool()
|
||||
selectUnlockedCells = Bool()
|
||||
sort = Bool()
|
||||
autoFilter = Bool()
|
||||
pivotTables = Bool()
|
||||
saltValue = Base64Binary(allow_none=True)
|
||||
spinCount = Integer(allow_none=True)
|
||||
algorithmName = String(allow_none=True)
|
||||
hashValue = Base64Binary(allow_none=True)
|
||||
|
||||
|
||||
__attrs__ = ('selectLockedCells', 'selectUnlockedCells', 'algorithmName',
|
||||
'sheet', 'objects', 'insertRows', 'insertHyperlinks', 'autoFilter',
|
||||
'scenarios', 'formatColumns', 'deleteColumns', 'insertColumns',
|
||||
'pivotTables', 'deleteRows', 'formatCells', 'saltValue', 'formatRows',
|
||||
'sort', 'spinCount', 'password', 'hashValue')
|
||||
|
||||
|
||||
def __init__(self, sheet=False, objects=False, scenarios=False,
|
||||
formatCells=True, formatRows=True, formatColumns=True,
|
||||
insertColumns=True, insertRows=True, insertHyperlinks=True,
|
||||
deleteColumns=True, deleteRows=True, selectLockedCells=False,
|
||||
selectUnlockedCells=False, sort=True, autoFilter=True, pivotTables=True,
|
||||
password=None, algorithmName=None, saltValue=None, spinCount=None, hashValue=None):
|
||||
self.sheet = sheet
|
||||
self.objects = objects
|
||||
self.scenarios = scenarios
|
||||
self.formatCells = formatCells
|
||||
self.formatColumns = formatColumns
|
||||
self.formatRows = formatRows
|
||||
self.insertColumns = insertColumns
|
||||
self.insertRows = insertRows
|
||||
self.insertHyperlinks = insertHyperlinks
|
||||
self.deleteColumns = deleteColumns
|
||||
self.deleteRows = deleteRows
|
||||
self.selectLockedCells = selectLockedCells
|
||||
self.selectUnlockedCells = selectUnlockedCells
|
||||
self.sort = sort
|
||||
self.autoFilter = autoFilter
|
||||
self.pivotTables = pivotTables
|
||||
if password is not None:
|
||||
self.password = password
|
||||
self.algorithmName = algorithmName
|
||||
self.saltValue = saltValue
|
||||
self.spinCount = spinCount
|
||||
self.hashValue = hashValue
|
||||
|
||||
|
||||
def set_password(self, value='', already_hashed=False):
|
||||
super().set_password(value, already_hashed)
|
||||
self.enable()
|
||||
|
||||
|
||||
def enable(self):
|
||||
self.sheet = True
|
||||
|
||||
|
||||
def disable(self):
|
||||
self.sheet = False
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return self.sheet
|
||||
|
17
lib/python3.13/site-packages/openpyxl/worksheet/related.py
Normal file
17
lib/python3.13/site-packages/openpyxl/worksheet/related.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors.excel import Relation
|
||||
|
||||
|
||||
class Related(Serialisable):
|
||||
|
||||
id = Relation()
|
||||
|
||||
|
||||
def __init__(self, id=None):
|
||||
self.id = id
|
||||
|
||||
|
||||
def to_tree(self, tagname, idx=None):
|
||||
return super().to_tree(tagname)
|
105
lib/python3.13/site-packages/openpyxl/worksheet/scenario.py
Normal file
105
lib/python3.13/site-packages/openpyxl/worksheet/scenario.py
Normal file
@ -0,0 +1,105 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
String,
|
||||
Integer,
|
||||
Bool,
|
||||
Sequence,
|
||||
Convertible,
|
||||
)
|
||||
from .cell_range import MultiCellRange
|
||||
|
||||
|
||||
class InputCells(Serialisable):
|
||||
|
||||
tagname = "inputCells"
|
||||
|
||||
r = String()
|
||||
deleted = Bool(allow_none=True)
|
||||
undone = Bool(allow_none=True)
|
||||
val = String()
|
||||
numFmtId = Integer(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
r=None,
|
||||
deleted=False,
|
||||
undone=False,
|
||||
val=None,
|
||||
numFmtId=None,
|
||||
):
|
||||
self.r = r
|
||||
self.deleted = deleted
|
||||
self.undone = undone
|
||||
self.val = val
|
||||
self.numFmtId = numFmtId
|
||||
|
||||
|
||||
class Scenario(Serialisable):
|
||||
|
||||
tagname = "scenario"
|
||||
|
||||
inputCells = Sequence(expected_type=InputCells)
|
||||
name = String()
|
||||
locked = Bool(allow_none=True)
|
||||
hidden = Bool(allow_none=True)
|
||||
user = String(allow_none=True)
|
||||
comment = String(allow_none=True)
|
||||
|
||||
__elements__ = ('inputCells',)
|
||||
__attrs__ = ('name', 'locked', 'hidden', 'user', 'comment', 'count')
|
||||
|
||||
def __init__(self,
|
||||
inputCells=(),
|
||||
name=None,
|
||||
locked=False,
|
||||
hidden=False,
|
||||
count=None,
|
||||
user=None,
|
||||
comment=None,
|
||||
):
|
||||
self.inputCells = inputCells
|
||||
self.name = name
|
||||
self.locked = locked
|
||||
self.hidden = hidden
|
||||
self.user = user
|
||||
self.comment = comment
|
||||
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return len(self.inputCells)
|
||||
|
||||
|
||||
class ScenarioList(Serialisable):
|
||||
|
||||
tagname = "scenarios"
|
||||
|
||||
scenario = Sequence(expected_type=Scenario)
|
||||
current = Integer(allow_none=True)
|
||||
show = Integer(allow_none=True)
|
||||
sqref = Convertible(expected_type=MultiCellRange, allow_none=True)
|
||||
|
||||
__elements__ = ('scenario',)
|
||||
|
||||
def __init__(self,
|
||||
scenario=(),
|
||||
current=None,
|
||||
show=None,
|
||||
sqref=None,
|
||||
):
|
||||
self.scenario = scenario
|
||||
self.current = current
|
||||
self.show = show
|
||||
self.sqref = sqref
|
||||
|
||||
|
||||
def append(self, scenario):
|
||||
s = self.scenario
|
||||
s.append(scenario)
|
||||
self.scenario = s
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.scenario)
|
||||
|
78
lib/python3.13/site-packages/openpyxl/worksheet/smart_tag.py
Normal file
78
lib/python3.13/site-packages/openpyxl/worksheet/smart_tag.py
Normal file
@ -0,0 +1,78 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
Integer,
|
||||
String,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
|
||||
class CellSmartTagPr(Serialisable):
|
||||
|
||||
tagname = "cellSmartTagPr"
|
||||
|
||||
key = String()
|
||||
val = String()
|
||||
|
||||
def __init__(self,
|
||||
key=None,
|
||||
val=None,
|
||||
):
|
||||
self.key = key
|
||||
self.val = val
|
||||
|
||||
|
||||
class CellSmartTag(Serialisable):
|
||||
|
||||
tagname = "cellSmartTag"
|
||||
|
||||
cellSmartTagPr = Sequence(expected_type=CellSmartTagPr)
|
||||
type = Integer()
|
||||
deleted = Bool(allow_none=True)
|
||||
xmlBased = Bool(allow_none=True)
|
||||
|
||||
__elements__ = ('cellSmartTagPr',)
|
||||
|
||||
def __init__(self,
|
||||
cellSmartTagPr=(),
|
||||
type=None,
|
||||
deleted=False,
|
||||
xmlBased=False,
|
||||
):
|
||||
self.cellSmartTagPr = cellSmartTagPr
|
||||
self.type = type
|
||||
self.deleted = deleted
|
||||
self.xmlBased = xmlBased
|
||||
|
||||
|
||||
class CellSmartTags(Serialisable):
|
||||
|
||||
tagname = "cellSmartTags"
|
||||
|
||||
cellSmartTag = Sequence(expected_type=CellSmartTag)
|
||||
r = String()
|
||||
|
||||
__elements__ = ('cellSmartTag',)
|
||||
|
||||
def __init__(self,
|
||||
cellSmartTag=(),
|
||||
r=None,
|
||||
):
|
||||
self.cellSmartTag = cellSmartTag
|
||||
self.r = r
|
||||
|
||||
|
||||
class SmartTags(Serialisable):
|
||||
|
||||
tagname = "smartTags"
|
||||
|
||||
cellSmartTags = Sequence(expected_type=CellSmartTags)
|
||||
|
||||
__elements__ = ('cellSmartTags',)
|
||||
|
||||
def __init__(self,
|
||||
cellSmartTags=(),
|
||||
):
|
||||
self.cellSmartTags = cellSmartTags
|
||||
|
385
lib/python3.13/site-packages/openpyxl/worksheet/table.py
Normal file
385
lib/python3.13/site-packages/openpyxl/worksheet/table.py
Normal file
@ -0,0 +1,385 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Descriptor,
|
||||
Alias,
|
||||
Typed,
|
||||
Bool,
|
||||
Integer,
|
||||
NoneSet,
|
||||
String,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList, CellRange
|
||||
from openpyxl.descriptors.sequence import NestedSequence
|
||||
from openpyxl.xml.constants import SHEET_MAIN_NS, REL_NS
|
||||
from openpyxl.xml.functions import tostring
|
||||
from openpyxl.utils import range_boundaries
|
||||
from openpyxl.utils.escape import escape, unescape
|
||||
|
||||
from .related import Related
|
||||
|
||||
from .filters import (
|
||||
AutoFilter,
|
||||
SortState,
|
||||
)
|
||||
|
||||
TABLESTYLES = tuple(
|
||||
["TableStyleMedium{0}".format(i) for i in range(1, 29)]
|
||||
+ ["TableStyleLight{0}".format(i) for i in range(1, 22)]
|
||||
+ ["TableStyleDark{0}".format(i) for i in range(1, 12)]
|
||||
)
|
||||
|
||||
PIVOTSTYLES = tuple(
|
||||
["PivotStyleMedium{0}".format(i) for i in range(1, 29)]
|
||||
+ ["PivotStyleLight{0}".format(i) for i in range(1, 29)]
|
||||
+ ["PivotStyleDark{0}".format(i) for i in range(1, 29)]
|
||||
)
|
||||
|
||||
|
||||
class TableStyleInfo(Serialisable):
|
||||
|
||||
tagname = "tableStyleInfo"
|
||||
|
||||
name = String(allow_none=True)
|
||||
showFirstColumn = Bool(allow_none=True)
|
||||
showLastColumn = Bool(allow_none=True)
|
||||
showRowStripes = Bool(allow_none=True)
|
||||
showColumnStripes = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
name=None,
|
||||
showFirstColumn=None,
|
||||
showLastColumn=None,
|
||||
showRowStripes=None,
|
||||
showColumnStripes=None,
|
||||
):
|
||||
self.name = name
|
||||
self.showFirstColumn = showFirstColumn
|
||||
self.showLastColumn = showLastColumn
|
||||
self.showRowStripes = showRowStripes
|
||||
self.showColumnStripes = showColumnStripes
|
||||
|
||||
|
||||
class XMLColumnProps(Serialisable):
|
||||
|
||||
tagname = "xmlColumnPr"
|
||||
|
||||
mapId = Integer()
|
||||
xpath = String()
|
||||
denormalized = Bool(allow_none=True)
|
||||
xmlDataType = String()
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ()
|
||||
|
||||
def __init__(self,
|
||||
mapId=None,
|
||||
xpath=None,
|
||||
denormalized=None,
|
||||
xmlDataType=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.mapId = mapId
|
||||
self.xpath = xpath
|
||||
self.denormalized = denormalized
|
||||
self.xmlDataType = xmlDataType
|
||||
|
||||
|
||||
class TableFormula(Serialisable):
|
||||
|
||||
tagname = "tableFormula"
|
||||
|
||||
## Note formula is stored as the text value
|
||||
|
||||
array = Bool(allow_none=True)
|
||||
attr_text = Descriptor()
|
||||
text = Alias('attr_text')
|
||||
|
||||
|
||||
def __init__(self,
|
||||
array=None,
|
||||
attr_text=None,
|
||||
):
|
||||
self.array = array
|
||||
self.attr_text = attr_text
|
||||
|
||||
|
||||
class TableColumn(Serialisable):
|
||||
|
||||
tagname = "tableColumn"
|
||||
|
||||
id = Integer()
|
||||
uniqueName = String(allow_none=True)
|
||||
name = String()
|
||||
totalsRowFunction = NoneSet(values=(['sum', 'min', 'max', 'average',
|
||||
'count', 'countNums', 'stdDev', 'var', 'custom']))
|
||||
totalsRowLabel = String(allow_none=True)
|
||||
queryTableFieldId = Integer(allow_none=True)
|
||||
headerRowDxfId = Integer(allow_none=True)
|
||||
dataDxfId = Integer(allow_none=True)
|
||||
totalsRowDxfId = Integer(allow_none=True)
|
||||
headerRowCellStyle = String(allow_none=True)
|
||||
dataCellStyle = String(allow_none=True)
|
||||
totalsRowCellStyle = String(allow_none=True)
|
||||
calculatedColumnFormula = Typed(expected_type=TableFormula, allow_none=True)
|
||||
totalsRowFormula = Typed(expected_type=TableFormula, allow_none=True)
|
||||
xmlColumnPr = Typed(expected_type=XMLColumnProps, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('calculatedColumnFormula', 'totalsRowFormula',
|
||||
'xmlColumnPr', 'extLst')
|
||||
|
||||
def __init__(self,
|
||||
id=None,
|
||||
uniqueName=None,
|
||||
name=None,
|
||||
totalsRowFunction=None,
|
||||
totalsRowLabel=None,
|
||||
queryTableFieldId=None,
|
||||
headerRowDxfId=None,
|
||||
dataDxfId=None,
|
||||
totalsRowDxfId=None,
|
||||
headerRowCellStyle=None,
|
||||
dataCellStyle=None,
|
||||
totalsRowCellStyle=None,
|
||||
calculatedColumnFormula=None,
|
||||
totalsRowFormula=None,
|
||||
xmlColumnPr=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.id = id
|
||||
self.uniqueName = uniqueName
|
||||
self.name = name
|
||||
self.totalsRowFunction = totalsRowFunction
|
||||
self.totalsRowLabel = totalsRowLabel
|
||||
self.queryTableFieldId = queryTableFieldId
|
||||
self.headerRowDxfId = headerRowDxfId
|
||||
self.dataDxfId = dataDxfId
|
||||
self.totalsRowDxfId = totalsRowDxfId
|
||||
self.headerRowCellStyle = headerRowCellStyle
|
||||
self.dataCellStyle = dataCellStyle
|
||||
self.totalsRowCellStyle = totalsRowCellStyle
|
||||
self.calculatedColumnFormula = calculatedColumnFormula
|
||||
self.totalsRowFormula = totalsRowFormula
|
||||
self.xmlColumnPr = xmlColumnPr
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
for k, v in super().__iter__():
|
||||
if k == 'name':
|
||||
v = escape(v)
|
||||
yield k, v
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
self = super().from_tree(node)
|
||||
self.name = unescape(self.name)
|
||||
return self
|
||||
|
||||
|
||||
class TableNameDescriptor(String):
|
||||
|
||||
"""
|
||||
Table names cannot have spaces in them
|
||||
"""
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value is not None and " " in value:
|
||||
raise ValueError("Table names cannot have spaces")
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Table(Serialisable):
|
||||
|
||||
_path = "/tables/table{0}.xml"
|
||||
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
|
||||
_rel_type = REL_NS + "/table"
|
||||
_rel_id = None
|
||||
|
||||
tagname = "table"
|
||||
|
||||
id = Integer()
|
||||
name = String(allow_none=True)
|
||||
displayName = TableNameDescriptor()
|
||||
comment = String(allow_none=True)
|
||||
ref = CellRange()
|
||||
tableType = NoneSet(values=(['worksheet', 'xml', 'queryTable']))
|
||||
headerRowCount = Integer(allow_none=True)
|
||||
insertRow = Bool(allow_none=True)
|
||||
insertRowShift = Bool(allow_none=True)
|
||||
totalsRowCount = Integer(allow_none=True)
|
||||
totalsRowShown = Bool(allow_none=True)
|
||||
published = Bool(allow_none=True)
|
||||
headerRowDxfId = Integer(allow_none=True)
|
||||
dataDxfId = Integer(allow_none=True)
|
||||
totalsRowDxfId = Integer(allow_none=True)
|
||||
headerRowBorderDxfId = Integer(allow_none=True)
|
||||
tableBorderDxfId = Integer(allow_none=True)
|
||||
totalsRowBorderDxfId = Integer(allow_none=True)
|
||||
headerRowCellStyle = String(allow_none=True)
|
||||
dataCellStyle = String(allow_none=True)
|
||||
totalsRowCellStyle = String(allow_none=True)
|
||||
connectionId = Integer(allow_none=True)
|
||||
autoFilter = Typed(expected_type=AutoFilter, allow_none=True)
|
||||
sortState = Typed(expected_type=SortState, allow_none=True)
|
||||
tableColumns = NestedSequence(expected_type=TableColumn, count=True)
|
||||
tableStyleInfo = Typed(expected_type=TableStyleInfo, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('autoFilter', 'sortState', 'tableColumns',
|
||||
'tableStyleInfo')
|
||||
|
||||
def __init__(self,
|
||||
id=1,
|
||||
displayName=None,
|
||||
ref=None,
|
||||
name=None,
|
||||
comment=None,
|
||||
tableType=None,
|
||||
headerRowCount=1,
|
||||
insertRow=None,
|
||||
insertRowShift=None,
|
||||
totalsRowCount=None,
|
||||
totalsRowShown=None,
|
||||
published=None,
|
||||
headerRowDxfId=None,
|
||||
dataDxfId=None,
|
||||
totalsRowDxfId=None,
|
||||
headerRowBorderDxfId=None,
|
||||
tableBorderDxfId=None,
|
||||
totalsRowBorderDxfId=None,
|
||||
headerRowCellStyle=None,
|
||||
dataCellStyle=None,
|
||||
totalsRowCellStyle=None,
|
||||
connectionId=None,
|
||||
autoFilter=None,
|
||||
sortState=None,
|
||||
tableColumns=(),
|
||||
tableStyleInfo=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.id = id
|
||||
self.displayName = displayName
|
||||
if name is None:
|
||||
name = displayName
|
||||
self.name = name
|
||||
self.comment = comment
|
||||
self.ref = ref
|
||||
self.tableType = tableType
|
||||
self.headerRowCount = headerRowCount
|
||||
self.insertRow = insertRow
|
||||
self.insertRowShift = insertRowShift
|
||||
self.totalsRowCount = totalsRowCount
|
||||
self.totalsRowShown = totalsRowShown
|
||||
self.published = published
|
||||
self.headerRowDxfId = headerRowDxfId
|
||||
self.dataDxfId = dataDxfId
|
||||
self.totalsRowDxfId = totalsRowDxfId
|
||||
self.headerRowBorderDxfId = headerRowBorderDxfId
|
||||
self.tableBorderDxfId = tableBorderDxfId
|
||||
self.totalsRowBorderDxfId = totalsRowBorderDxfId
|
||||
self.headerRowCellStyle = headerRowCellStyle
|
||||
self.dataCellStyle = dataCellStyle
|
||||
self.totalsRowCellStyle = totalsRowCellStyle
|
||||
self.connectionId = connectionId
|
||||
self.autoFilter = autoFilter
|
||||
self.sortState = sortState
|
||||
self.tableColumns = tableColumns
|
||||
self.tableStyleInfo = tableStyleInfo
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
tree = super().to_tree()
|
||||
tree.set("xmlns", SHEET_MAIN_NS)
|
||||
return tree
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
Return path within the archive
|
||||
"""
|
||||
return "/xl" + self._path.format(self.id)
|
||||
|
||||
|
||||
def _write(self, archive):
|
||||
"""
|
||||
Serialise to XML and write to archive
|
||||
"""
|
||||
xml = self.to_tree()
|
||||
archive.writestr(self.path[1:], tostring(xml))
|
||||
|
||||
|
||||
def _initialise_columns(self):
|
||||
"""
|
||||
Create a list of table columns from a cell range
|
||||
Always set a ref if we have headers (the default)
|
||||
Column headings must be strings and must match cells in the worksheet.
|
||||
"""
|
||||
|
||||
min_col, min_row, max_col, max_row = range_boundaries(self.ref)
|
||||
for idx in range(min_col, max_col+1):
|
||||
col = TableColumn(id=idx, name="Column{0}".format(idx))
|
||||
self.tableColumns.append(col)
|
||||
if self.headerRowCount and not self.autoFilter:
|
||||
self.autoFilter = AutoFilter(ref=self.ref)
|
||||
|
||||
|
||||
@property
|
||||
def column_names(self):
|
||||
return [column.name for column in self.tableColumns]
|
||||
|
||||
|
||||
class TablePartList(Serialisable):
|
||||
|
||||
tagname = "tableParts"
|
||||
|
||||
count = Integer(allow_none=True)
|
||||
tablePart = Sequence(expected_type=Related)
|
||||
|
||||
__elements__ = ('tablePart',)
|
||||
__attrs__ = ('count',)
|
||||
|
||||
def __init__(self,
|
||||
count=None,
|
||||
tablePart=(),
|
||||
):
|
||||
self.tablePart = tablePart
|
||||
|
||||
|
||||
def append(self, part):
|
||||
self.tablePart.append(part)
|
||||
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return len(self.tablePart)
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.tablePart)
|
||||
|
||||
|
||||
class TableList(dict):
|
||||
|
||||
|
||||
def add(self, table):
|
||||
if not isinstance(table, Table):
|
||||
raise TypeError("You can only add tables")
|
||||
self[table.name] = table
|
||||
|
||||
|
||||
def get(self, name=None, table_range=None):
|
||||
if name is not None:
|
||||
return super().get(name)
|
||||
for table in self.values():
|
||||
if table_range == table.ref:
|
||||
return table
|
||||
|
||||
|
||||
def items(self):
|
||||
return [(name, table.ref) for name, table in super().items()]
|
155
lib/python3.13/site-packages/openpyxl/worksheet/views.py
Normal file
155
lib/python3.13/site-packages/openpyxl/worksheet/views.py
Normal file
@ -0,0 +1,155 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
Integer,
|
||||
String,
|
||||
Set,
|
||||
Float,
|
||||
Typed,
|
||||
NoneSet,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
|
||||
class Pane(Serialisable):
|
||||
xSplit = Float(allow_none=True)
|
||||
ySplit = Float(allow_none=True)
|
||||
topLeftCell = String(allow_none=True)
|
||||
activePane = Set(values=("bottomRight", "topRight", "bottomLeft", "topLeft"))
|
||||
state = Set(values=("split", "frozen", "frozenSplit"))
|
||||
|
||||
def __init__(self,
|
||||
xSplit=None,
|
||||
ySplit=None,
|
||||
topLeftCell=None,
|
||||
activePane="topLeft",
|
||||
state="split"):
|
||||
self.xSplit = xSplit
|
||||
self.ySplit = ySplit
|
||||
self.topLeftCell = topLeftCell
|
||||
self.activePane = activePane
|
||||
self.state = state
|
||||
|
||||
|
||||
class Selection(Serialisable):
|
||||
pane = NoneSet(values=("bottomRight", "topRight", "bottomLeft", "topLeft"))
|
||||
activeCell = String(allow_none=True)
|
||||
activeCellId = Integer(allow_none=True)
|
||||
sqref = String(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
pane=None,
|
||||
activeCell="A1",
|
||||
activeCellId=None,
|
||||
sqref="A1"):
|
||||
self.pane = pane
|
||||
self.activeCell = activeCell
|
||||
self.activeCellId = activeCellId
|
||||
self.sqref = sqref
|
||||
|
||||
|
||||
class SheetView(Serialisable):
|
||||
|
||||
"""Information about the visible portions of this sheet."""
|
||||
|
||||
tagname = "sheetView"
|
||||
|
||||
windowProtection = Bool(allow_none=True)
|
||||
showFormulas = Bool(allow_none=True)
|
||||
showGridLines = Bool(allow_none=True)
|
||||
showRowColHeaders = Bool(allow_none=True)
|
||||
showZeros = Bool(allow_none=True)
|
||||
rightToLeft = Bool(allow_none=True)
|
||||
tabSelected = Bool(allow_none=True)
|
||||
showRuler = Bool(allow_none=True)
|
||||
showOutlineSymbols = Bool(allow_none=True)
|
||||
defaultGridColor = Bool(allow_none=True)
|
||||
showWhiteSpace = Bool(allow_none=True)
|
||||
view = NoneSet(values=("normal", "pageBreakPreview", "pageLayout"))
|
||||
topLeftCell = String(allow_none=True)
|
||||
colorId = Integer(allow_none=True)
|
||||
zoomScale = Integer(allow_none=True)
|
||||
zoomScaleNormal = Integer(allow_none=True)
|
||||
zoomScaleSheetLayoutView = Integer(allow_none=True)
|
||||
zoomScalePageLayoutView = Integer(allow_none=True)
|
||||
zoomToFit = Bool(allow_none=True) # Chart sheets only
|
||||
workbookViewId = Integer()
|
||||
selection = Sequence(expected_type=Selection)
|
||||
pane = Typed(expected_type=Pane, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
windowProtection=None,
|
||||
showFormulas=None,
|
||||
showGridLines=None,
|
||||
showRowColHeaders=None,
|
||||
showZeros=None,
|
||||
rightToLeft=None,
|
||||
tabSelected=None,
|
||||
showRuler=None,
|
||||
showOutlineSymbols=None,
|
||||
defaultGridColor=None,
|
||||
showWhiteSpace=None,
|
||||
view=None,
|
||||
topLeftCell=None,
|
||||
colorId=None,
|
||||
zoomScale=None,
|
||||
zoomScaleNormal=None,
|
||||
zoomScaleSheetLayoutView=None,
|
||||
zoomScalePageLayoutView=None,
|
||||
zoomToFit=None,
|
||||
workbookViewId=0,
|
||||
selection=None,
|
||||
pane=None,):
|
||||
self.windowProtection = windowProtection
|
||||
self.showFormulas = showFormulas
|
||||
self.showGridLines = showGridLines
|
||||
self.showRowColHeaders = showRowColHeaders
|
||||
self.showZeros = showZeros
|
||||
self.rightToLeft = rightToLeft
|
||||
self.tabSelected = tabSelected
|
||||
self.showRuler = showRuler
|
||||
self.showOutlineSymbols = showOutlineSymbols
|
||||
self.defaultGridColor = defaultGridColor
|
||||
self.showWhiteSpace = showWhiteSpace
|
||||
self.view = view
|
||||
self.topLeftCell = topLeftCell
|
||||
self.colorId = colorId
|
||||
self.zoomScale = zoomScale
|
||||
self.zoomScaleNormal = zoomScaleNormal
|
||||
self.zoomScaleSheetLayoutView = zoomScaleSheetLayoutView
|
||||
self.zoomScalePageLayoutView = zoomScalePageLayoutView
|
||||
self.zoomToFit = zoomToFit
|
||||
self.workbookViewId = workbookViewId
|
||||
self.pane = pane
|
||||
if selection is None:
|
||||
selection = (Selection(), )
|
||||
self.selection = selection
|
||||
|
||||
|
||||
class SheetViewList(Serialisable):
|
||||
|
||||
tagname = "sheetViews"
|
||||
|
||||
sheetView = Sequence(expected_type=SheetView, )
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('sheetView',)
|
||||
|
||||
def __init__(self,
|
||||
sheetView=None,
|
||||
extLst=None,
|
||||
):
|
||||
if sheetView is None:
|
||||
sheetView = [SheetView()]
|
||||
self.sheetView = sheetView
|
||||
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"""
|
||||
Returns the first sheet view which is assumed to be active
|
||||
"""
|
||||
return self.sheetView[0]
|
907
lib/python3.13/site-packages/openpyxl/worksheet/worksheet.py
Normal file
907
lib/python3.13/site-packages/openpyxl/worksheet/worksheet.py
Normal file
@ -0,0 +1,907 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""Worksheet is the 2nd-level container in Excel."""
|
||||
|
||||
|
||||
# Python stdlib imports
|
||||
from itertools import chain
|
||||
from operator import itemgetter
|
||||
from inspect import isgenerator
|
||||
from warnings import warn
|
||||
|
||||
# compatibility imports
|
||||
from openpyxl.compat import (
|
||||
deprecated,
|
||||
)
|
||||
|
||||
# package imports
|
||||
from openpyxl.utils import (
|
||||
column_index_from_string,
|
||||
get_column_letter,
|
||||
range_boundaries,
|
||||
coordinate_to_tuple,
|
||||
)
|
||||
from openpyxl.cell import Cell, MergedCell
|
||||
from openpyxl.formatting.formatting import ConditionalFormattingList
|
||||
from openpyxl.packaging.relationship import RelationshipList
|
||||
from openpyxl.workbook.child import _WorkbookChild
|
||||
from openpyxl.workbook.defined_name import (
|
||||
DefinedNameDict,
|
||||
)
|
||||
|
||||
from openpyxl.formula.translate import Translator
|
||||
|
||||
from .datavalidation import DataValidationList
|
||||
from .page import (
|
||||
PrintPageSetup,
|
||||
PageMargins,
|
||||
PrintOptions,
|
||||
)
|
||||
from .dimensions import (
|
||||
ColumnDimension,
|
||||
RowDimension,
|
||||
DimensionHolder,
|
||||
SheetFormatProperties,
|
||||
)
|
||||
from .protection import SheetProtection
|
||||
from .filters import AutoFilter
|
||||
from .views import (
|
||||
Pane,
|
||||
Selection,
|
||||
SheetViewList,
|
||||
)
|
||||
from .cell_range import MultiCellRange, CellRange
|
||||
from .merge import MergedCellRange
|
||||
from .properties import WorksheetProperties
|
||||
from .pagebreak import RowBreak, ColBreak
|
||||
from .scenario import ScenarioList
|
||||
from .table import TableList
|
||||
from .formula import ArrayFormula
|
||||
from .print_settings import (
|
||||
PrintTitles,
|
||||
ColRange,
|
||||
RowRange,
|
||||
PrintArea,
|
||||
)
|
||||
|
||||
|
||||
class Worksheet(_WorkbookChild):
|
||||
"""Represents a worksheet.
|
||||
|
||||
Do not create worksheets yourself,
|
||||
use :func:`openpyxl.workbook.Workbook.create_sheet` instead
|
||||
|
||||
"""
|
||||
|
||||
_rel_type = "worksheet"
|
||||
_path = "/xl/worksheets/sheet{0}.xml"
|
||||
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
|
||||
|
||||
BREAK_NONE = 0
|
||||
BREAK_ROW = 1
|
||||
BREAK_COLUMN = 2
|
||||
|
||||
SHEETSTATE_VISIBLE = 'visible'
|
||||
SHEETSTATE_HIDDEN = 'hidden'
|
||||
SHEETSTATE_VERYHIDDEN = 'veryHidden'
|
||||
|
||||
# Paper size
|
||||
PAPERSIZE_LETTER = '1'
|
||||
PAPERSIZE_LETTER_SMALL = '2'
|
||||
PAPERSIZE_TABLOID = '3'
|
||||
PAPERSIZE_LEDGER = '4'
|
||||
PAPERSIZE_LEGAL = '5'
|
||||
PAPERSIZE_STATEMENT = '6'
|
||||
PAPERSIZE_EXECUTIVE = '7'
|
||||
PAPERSIZE_A3 = '8'
|
||||
PAPERSIZE_A4 = '9'
|
||||
PAPERSIZE_A4_SMALL = '10'
|
||||
PAPERSIZE_A5 = '11'
|
||||
|
||||
# Page orientation
|
||||
ORIENTATION_PORTRAIT = 'portrait'
|
||||
ORIENTATION_LANDSCAPE = 'landscape'
|
||||
|
||||
def __init__(self, parent, title=None):
|
||||
_WorkbookChild.__init__(self, parent, title)
|
||||
self._setup()
|
||||
|
||||
def _setup(self):
|
||||
self.row_dimensions = DimensionHolder(worksheet=self,
|
||||
default_factory=self._add_row)
|
||||
self.column_dimensions = DimensionHolder(worksheet=self,
|
||||
default_factory=self._add_column)
|
||||
self.row_breaks = RowBreak()
|
||||
self.col_breaks = ColBreak()
|
||||
self._cells = {}
|
||||
self._charts = []
|
||||
self._images = []
|
||||
self._rels = RelationshipList()
|
||||
self._drawing = None
|
||||
self._comments = []
|
||||
self.merged_cells = MultiCellRange()
|
||||
self._tables = TableList()
|
||||
self._pivots = []
|
||||
self.data_validations = DataValidationList()
|
||||
self._hyperlinks = []
|
||||
self.sheet_state = 'visible'
|
||||
self.page_setup = PrintPageSetup(worksheet=self)
|
||||
self.print_options = PrintOptions()
|
||||
self._print_rows = None
|
||||
self._print_cols = None
|
||||
self._print_area = PrintArea()
|
||||
self.page_margins = PageMargins()
|
||||
self.views = SheetViewList()
|
||||
self.protection = SheetProtection()
|
||||
self.defined_names = DefinedNameDict()
|
||||
|
||||
self._current_row = 0
|
||||
self.auto_filter = AutoFilter()
|
||||
self.conditional_formatting = ConditionalFormattingList()
|
||||
self.legacy_drawing = None
|
||||
self.sheet_properties = WorksheetProperties()
|
||||
self.sheet_format = SheetFormatProperties()
|
||||
self.scenarios = ScenarioList()
|
||||
|
||||
|
||||
@property
|
||||
def sheet_view(self):
|
||||
return self.views.active
|
||||
|
||||
|
||||
@property
|
||||
def selected_cell(self):
|
||||
return self.sheet_view.selection[0].sqref
|
||||
|
||||
|
||||
@property
|
||||
def active_cell(self):
|
||||
return self.sheet_view.selection[0].activeCell
|
||||
|
||||
|
||||
@property
|
||||
def array_formulae(self):
|
||||
"""Returns a dictionary of cells with array formulae and the cells in array"""
|
||||
result = {}
|
||||
for c in self._cells.values():
|
||||
if c.data_type == "f":
|
||||
if isinstance(c.value, ArrayFormula):
|
||||
result[c.coordinate] = c.value.ref
|
||||
return result
|
||||
|
||||
|
||||
@property
|
||||
def show_gridlines(self):
|
||||
return self.sheet_view.showGridLines
|
||||
|
||||
|
||||
@property
|
||||
def freeze_panes(self):
|
||||
if self.sheet_view.pane is not None:
|
||||
return self.sheet_view.pane.topLeftCell
|
||||
|
||||
|
||||
@freeze_panes.setter
|
||||
def freeze_panes(self, topLeftCell=None):
|
||||
if isinstance(topLeftCell, Cell):
|
||||
topLeftCell = topLeftCell.coordinate
|
||||
if topLeftCell == 'A1':
|
||||
topLeftCell = None
|
||||
|
||||
if not topLeftCell:
|
||||
self.sheet_view.pane = None
|
||||
return
|
||||
|
||||
row, column = coordinate_to_tuple(topLeftCell)
|
||||
|
||||
view = self.sheet_view
|
||||
view.pane = Pane(topLeftCell=topLeftCell,
|
||||
activePane="topRight",
|
||||
state="frozen")
|
||||
view.selection[0].pane = "topRight"
|
||||
|
||||
if column > 1:
|
||||
view.pane.xSplit = column - 1
|
||||
if row > 1:
|
||||
view.pane.ySplit = row - 1
|
||||
view.pane.activePane = 'bottomLeft'
|
||||
view.selection[0].pane = "bottomLeft"
|
||||
if column > 1:
|
||||
view.selection[0].pane = "bottomRight"
|
||||
view.pane.activePane = 'bottomRight'
|
||||
|
||||
if row > 1 and column > 1:
|
||||
sel = list(view.selection)
|
||||
sel.insert(0, Selection(pane="topRight", activeCell=None, sqref=None))
|
||||
sel.insert(1, Selection(pane="bottomLeft", activeCell=None, sqref=None))
|
||||
view.selection = sel
|
||||
|
||||
|
||||
def cell(self, row, column, value=None):
|
||||
"""
|
||||
Returns a cell object based on the given coordinates.
|
||||
|
||||
Usage: cell(row=15, column=1, value=5)
|
||||
|
||||
Calling `cell` creates cells in memory when they
|
||||
are first accessed.
|
||||
|
||||
:param row: row index of the cell (e.g. 4)
|
||||
:type row: int
|
||||
|
||||
:param column: column index of the cell (e.g. 3)
|
||||
:type column: int
|
||||
|
||||
:param value: value of the cell (e.g. 5)
|
||||
:type value: numeric or time or string or bool or none
|
||||
|
||||
:rtype: openpyxl.cell.cell.Cell
|
||||
"""
|
||||
|
||||
if row < 1 or column < 1:
|
||||
raise ValueError("Row or column values must be at least 1")
|
||||
|
||||
cell = self._get_cell(row, column)
|
||||
if value is not None:
|
||||
cell.value = value
|
||||
|
||||
return cell
|
||||
|
||||
|
||||
def _get_cell(self, row, column):
|
||||
"""
|
||||
Internal method for getting a cell from a worksheet.
|
||||
Will create a new cell if one doesn't already exist.
|
||||
"""
|
||||
if not 0 < row < 1048577:
|
||||
raise ValueError(f"Row numbers must be between 1 and 1048576. Row number supplied was {row}")
|
||||
coordinate = (row, column)
|
||||
if not coordinate in self._cells:
|
||||
cell = Cell(self, row=row, column=column)
|
||||
self._add_cell(cell)
|
||||
return self._cells[coordinate]
|
||||
|
||||
|
||||
def _add_cell(self, cell):
|
||||
"""
|
||||
Internal method for adding cell objects.
|
||||
"""
|
||||
column = cell.col_idx
|
||||
row = cell.row
|
||||
self._current_row = max(row, self._current_row)
|
||||
self._cells[(row, column)] = cell
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Convenience access by Excel style coordinates
|
||||
|
||||
The key can be a single cell coordinate 'A1', a range of cells 'A1:D25',
|
||||
individual rows or columns 'A', 4 or ranges of rows or columns 'A:D',
|
||||
4:10.
|
||||
|
||||
Single cells will always be created if they do not exist.
|
||||
|
||||
Returns either a single cell or a tuple of rows or columns.
|
||||
"""
|
||||
if isinstance(key, slice):
|
||||
if not all([key.start, key.stop]):
|
||||
raise IndexError("{0} is not a valid coordinate or range".format(key))
|
||||
key = "{0}:{1}".format(key.start, key.stop)
|
||||
|
||||
if isinstance(key, int):
|
||||
key = str(key
|
||||
)
|
||||
min_col, min_row, max_col, max_row = range_boundaries(key)
|
||||
|
||||
if not any([min_col, min_row, max_col, max_row]):
|
||||
raise IndexError("{0} is not a valid coordinate or range".format(key))
|
||||
|
||||
if min_row is None:
|
||||
cols = tuple(self.iter_cols(min_col, max_col))
|
||||
if min_col == max_col:
|
||||
cols = cols[0]
|
||||
return cols
|
||||
if min_col is None:
|
||||
rows = tuple(self.iter_rows(min_col=min_col, min_row=min_row,
|
||||
max_col=self.max_column, max_row=max_row))
|
||||
if min_row == max_row:
|
||||
rows = rows[0]
|
||||
return rows
|
||||
if ":" not in key:
|
||||
return self._get_cell(min_row, min_col)
|
||||
return tuple(self.iter_rows(min_row=min_row, min_col=min_col,
|
||||
max_row=max_row, max_col=max_col))
|
||||
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self[key].value = value
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
return self.iter_rows()
|
||||
|
||||
|
||||
def __delitem__(self, key):
|
||||
row, column = coordinate_to_tuple(key)
|
||||
if (row, column) in self._cells:
|
||||
del self._cells[(row, column)]
|
||||
|
||||
|
||||
@property
|
||||
def min_row(self):
|
||||
"""The minimum row index containing data (1-based)
|
||||
|
||||
:type: int
|
||||
"""
|
||||
min_row = 1
|
||||
if self._cells:
|
||||
min_row = min(self._cells)[0]
|
||||
return min_row
|
||||
|
||||
|
||||
@property
|
||||
def max_row(self):
|
||||
"""The maximum row index containing data (1-based)
|
||||
|
||||
:type: int
|
||||
"""
|
||||
max_row = 1
|
||||
if self._cells:
|
||||
max_row = max(self._cells)[0]
|
||||
return max_row
|
||||
|
||||
|
||||
@property
|
||||
def min_column(self):
|
||||
"""The minimum column index containing data (1-based)
|
||||
|
||||
:type: int
|
||||
"""
|
||||
min_col = 1
|
||||
if self._cells:
|
||||
min_col = min(c[1] for c in self._cells)
|
||||
return min_col
|
||||
|
||||
|
||||
@property
|
||||
def max_column(self):
|
||||
"""The maximum column index containing data (1-based)
|
||||
|
||||
:type: int
|
||||
"""
|
||||
max_col = 1
|
||||
if self._cells:
|
||||
max_col = max(c[1] for c in self._cells)
|
||||
return max_col
|
||||
|
||||
|
||||
def calculate_dimension(self):
|
||||
"""Return the minimum bounding range for all cells containing data (ex. 'A1:M24')
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
if self._cells:
|
||||
rows = set()
|
||||
cols = set()
|
||||
for row, col in self._cells:
|
||||
rows.add(row)
|
||||
cols.add(col)
|
||||
max_row = max(rows)
|
||||
max_col = max(cols)
|
||||
min_col = min(cols)
|
||||
min_row = min(rows)
|
||||
else:
|
||||
return "A1:A1"
|
||||
|
||||
return f"{get_column_letter(min_col)}{min_row}:{get_column_letter(max_col)}{max_row}"
|
||||
|
||||
|
||||
@property
|
||||
def dimensions(self):
|
||||
"""Returns the result of :func:`calculate_dimension`"""
|
||||
return self.calculate_dimension()
|
||||
|
||||
|
||||
def iter_rows(self, min_row=None, max_row=None, min_col=None, max_col=None, values_only=False):
|
||||
"""
|
||||
Produces cells from the worksheet, by row. Specify the iteration range
|
||||
using indices of rows and columns.
|
||||
|
||||
If no indices are specified the range starts at A1.
|
||||
|
||||
If no cells are in the worksheet an empty tuple will be returned.
|
||||
|
||||
:param min_col: smallest column index (1-based index)
|
||||
:type min_col: int
|
||||
|
||||
:param min_row: smallest row index (1-based index)
|
||||
:type min_row: int
|
||||
|
||||
:param max_col: largest column index (1-based index)
|
||||
:type max_col: int
|
||||
|
||||
:param max_row: largest row index (1-based index)
|
||||
:type max_row: int
|
||||
|
||||
:param values_only: whether only cell values should be returned
|
||||
:type values_only: bool
|
||||
|
||||
:rtype: generator
|
||||
"""
|
||||
|
||||
if self._current_row == 0 and not any([min_col, min_row, max_col, max_row ]):
|
||||
return iter(())
|
||||
|
||||
|
||||
min_col = min_col or 1
|
||||
min_row = min_row or 1
|
||||
max_col = max_col or self.max_column
|
||||
max_row = max_row or self.max_row
|
||||
|
||||
return self._cells_by_row(min_col, min_row, max_col, max_row, values_only)
|
||||
|
||||
|
||||
def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False):
|
||||
for row in range(min_row, max_row + 1):
|
||||
cells = (self.cell(row=row, column=column) for column in range(min_col, max_col + 1))
|
||||
if values_only:
|
||||
yield tuple(cell.value for cell in cells)
|
||||
else:
|
||||
yield tuple(cells)
|
||||
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
"""Produces all cells in the worksheet, by row (see :func:`iter_rows`)
|
||||
|
||||
:type: generator
|
||||
"""
|
||||
return self.iter_rows()
|
||||
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
"""Produces all cell values in the worksheet, by row
|
||||
|
||||
:type: generator
|
||||
"""
|
||||
for row in self.iter_rows(values_only=True):
|
||||
yield row
|
||||
|
||||
|
||||
def iter_cols(self, min_col=None, max_col=None, min_row=None, max_row=None, values_only=False):
|
||||
"""
|
||||
Produces cells from the worksheet, by column. Specify the iteration range
|
||||
using indices of rows and columns.
|
||||
|
||||
If no indices are specified the range starts at A1.
|
||||
|
||||
If no cells are in the worksheet an empty tuple will be returned.
|
||||
|
||||
:param min_col: smallest column index (1-based index)
|
||||
:type min_col: int
|
||||
|
||||
:param min_row: smallest row index (1-based index)
|
||||
:type min_row: int
|
||||
|
||||
:param max_col: largest column index (1-based index)
|
||||
:type max_col: int
|
||||
|
||||
:param max_row: largest row index (1-based index)
|
||||
:type max_row: int
|
||||
|
||||
:param values_only: whether only cell values should be returned
|
||||
:type values_only: bool
|
||||
|
||||
:rtype: generator
|
||||
"""
|
||||
|
||||
if self._current_row == 0 and not any([min_col, min_row, max_col, max_row]):
|
||||
return iter(())
|
||||
|
||||
min_col = min_col or 1
|
||||
min_row = min_row or 1
|
||||
max_col = max_col or self.max_column
|
||||
max_row = max_row or self.max_row
|
||||
|
||||
return self._cells_by_col(min_col, min_row, max_col, max_row, values_only)
|
||||
|
||||
|
||||
def _cells_by_col(self, min_col, min_row, max_col, max_row, values_only=False):
|
||||
"""
|
||||
Get cells by column
|
||||
"""
|
||||
for column in range(min_col, max_col+1):
|
||||
cells = (self.cell(row=row, column=column)
|
||||
for row in range(min_row, max_row+1))
|
||||
if values_only:
|
||||
yield tuple(cell.value for cell in cells)
|
||||
else:
|
||||
yield tuple(cells)
|
||||
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
"""Produces all cells in the worksheet, by column (see :func:`iter_cols`)"""
|
||||
return self.iter_cols()
|
||||
|
||||
|
||||
@property
|
||||
def column_groups(self):
|
||||
"""
|
||||
Return a list of column ranges where more than one column
|
||||
"""
|
||||
return [cd.range for cd in self.column_dimensions.values() if cd.min and cd.max > cd.min]
|
||||
|
||||
|
||||
def set_printer_settings(self, paper_size, orientation):
|
||||
"""Set printer settings """
|
||||
|
||||
self.page_setup.paperSize = paper_size
|
||||
self.page_setup.orientation = orientation
|
||||
|
||||
|
||||
def add_data_validation(self, data_validation):
|
||||
""" Add a data-validation object to the sheet. The data-validation
|
||||
object defines the type of data-validation to be applied and the
|
||||
cell or range of cells it should apply to.
|
||||
"""
|
||||
self.data_validations.append(data_validation)
|
||||
|
||||
|
||||
def add_chart(self, chart, anchor=None):
|
||||
"""
|
||||
Add a chart to the sheet
|
||||
Optionally provide a cell for the top-left anchor
|
||||
"""
|
||||
if anchor is not None:
|
||||
chart.anchor = anchor
|
||||
self._charts.append(chart)
|
||||
|
||||
|
||||
def add_image(self, img, anchor=None):
|
||||
"""
|
||||
Add an image to the sheet.
|
||||
Optionally provide a cell for the top-left anchor
|
||||
"""
|
||||
if anchor is not None:
|
||||
img.anchor = anchor
|
||||
self._images.append(img)
|
||||
|
||||
|
||||
def add_table(self, table):
|
||||
"""
|
||||
Check for duplicate name in definedNames and other worksheet tables
|
||||
before adding table.
|
||||
"""
|
||||
|
||||
if self.parent._duplicate_name(table.name):
|
||||
raise ValueError("Table with name {0} already exists".format(table.name))
|
||||
if not hasattr(self, "_get_cell"):
|
||||
warn("In write-only mode you must add table columns manually")
|
||||
self._tables.add(table)
|
||||
|
||||
|
||||
@property
|
||||
def tables(self):
|
||||
return self._tables
|
||||
|
||||
|
||||
def add_pivot(self, pivot):
|
||||
self._pivots.append(pivot)
|
||||
|
||||
|
||||
def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
|
||||
""" Set merge on a cell range. Range is a cell range (e.g. A1:E1) """
|
||||
if range_string is None:
|
||||
cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
|
||||
max_col=end_column, max_row=end_row)
|
||||
range_string = cr.coord
|
||||
mcr = MergedCellRange(self, range_string)
|
||||
self.merged_cells.add(mcr)
|
||||
self._clean_merge_range(mcr)
|
||||
|
||||
|
||||
def _clean_merge_range(self, mcr):
|
||||
"""
|
||||
Remove all but the top left-cell from a range of merged cells
|
||||
and recreate the lost border information.
|
||||
Borders are then applied
|
||||
"""
|
||||
cells = mcr.cells
|
||||
next(cells) # skip first cell
|
||||
for row, col in cells:
|
||||
self._cells[row, col] = MergedCell(self, row, col)
|
||||
mcr.format()
|
||||
|
||||
|
||||
@property
|
||||
@deprecated("Use ws.merged_cells.ranges")
|
||||
def merged_cell_ranges(self):
|
||||
"""Return a copy of cell ranges"""
|
||||
return self.merged_cells.ranges[:]
|
||||
|
||||
|
||||
def unmerge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
|
||||
""" Remove merge on a cell range. Range is a cell range (e.g. A1:E1) """
|
||||
cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
|
||||
max_col=end_column, max_row=end_row)
|
||||
|
||||
if cr.coord not in self.merged_cells:
|
||||
raise ValueError("Cell range {0} is not merged".format(cr.coord))
|
||||
|
||||
self.merged_cells.remove(cr)
|
||||
|
||||
cells = cr.cells
|
||||
next(cells) # skip first cell
|
||||
for row, col in cells:
|
||||
del self._cells[(row, col)]
|
||||
|
||||
|
||||
def append(self, iterable):
|
||||
"""Appends a group of values at the bottom of the current sheet.
|
||||
|
||||
* If it's a list: all values are added in order, starting from the first column
|
||||
* If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters)
|
||||
|
||||
:param iterable: list, range or generator, or dict containing values to append
|
||||
:type iterable: list|tuple|range|generator or dict
|
||||
|
||||
Usage:
|
||||
|
||||
* append(['This is A1', 'This is B1', 'This is C1'])
|
||||
* **or** append({'A' : 'This is A1', 'C' : 'This is C1'})
|
||||
* **or** append({1 : 'This is A1', 3 : 'This is C1'})
|
||||
|
||||
:raise: TypeError when iterable is neither a list/tuple nor a dict
|
||||
|
||||
"""
|
||||
row_idx = self._current_row + 1
|
||||
|
||||
if (isinstance(iterable, (list, tuple, range))
|
||||
or isgenerator(iterable)):
|
||||
for col_idx, content in enumerate(iterable, 1):
|
||||
if isinstance(content, Cell):
|
||||
# compatible with write-only mode
|
||||
cell = content
|
||||
if cell.parent and cell.parent != self:
|
||||
raise ValueError("Cells cannot be copied from other worksheets")
|
||||
cell.parent = self
|
||||
cell.column = col_idx
|
||||
cell.row = row_idx
|
||||
else:
|
||||
cell = Cell(self, row=row_idx, column=col_idx, value=content)
|
||||
self._cells[(row_idx, col_idx)] = cell
|
||||
|
||||
elif isinstance(iterable, dict):
|
||||
for col_idx, content in iterable.items():
|
||||
if isinstance(col_idx, str):
|
||||
col_idx = column_index_from_string(col_idx)
|
||||
cell = Cell(self, row=row_idx, column=col_idx, value=content)
|
||||
self._cells[(row_idx, col_idx)] = cell
|
||||
|
||||
else:
|
||||
self._invalid_row(iterable)
|
||||
|
||||
self._current_row = row_idx
|
||||
|
||||
|
||||
def _move_cells(self, min_row=None, min_col=None, offset=0, row_or_col="row"):
|
||||
"""
|
||||
Move either rows or columns around by the offset
|
||||
"""
|
||||
reverse = offset > 0 # start at the end if inserting
|
||||
row_offset = 0
|
||||
col_offset = 0
|
||||
|
||||
# need to make affected ranges contiguous
|
||||
if row_or_col == 'row':
|
||||
cells = self.iter_rows(min_row=min_row)
|
||||
row_offset = offset
|
||||
key = 0
|
||||
else:
|
||||
cells = self.iter_cols(min_col=min_col)
|
||||
col_offset = offset
|
||||
key = 1
|
||||
cells = list(cells)
|
||||
|
||||
for row, column in sorted(self._cells, key=itemgetter(key), reverse=reverse):
|
||||
if min_row and row < min_row:
|
||||
continue
|
||||
elif min_col and column < min_col:
|
||||
continue
|
||||
|
||||
self._move_cell(row, column, row_offset, col_offset)
|
||||
|
||||
|
||||
def insert_rows(self, idx, amount=1):
|
||||
"""
|
||||
Insert row or rows before row==idx
|
||||
"""
|
||||
self._move_cells(min_row=idx, offset=amount, row_or_col="row")
|
||||
self._current_row = self.max_row
|
||||
|
||||
|
||||
def insert_cols(self, idx, amount=1):
|
||||
"""
|
||||
Insert column or columns before col==idx
|
||||
"""
|
||||
self._move_cells(min_col=idx, offset=amount, row_or_col="column")
|
||||
|
||||
|
||||
def delete_rows(self, idx, amount=1):
|
||||
"""
|
||||
Delete row or rows from row==idx
|
||||
"""
|
||||
|
||||
remainder = _gutter(idx, amount, self.max_row)
|
||||
|
||||
self._move_cells(min_row=idx+amount, offset=-amount, row_or_col="row")
|
||||
|
||||
# calculating min and max col is an expensive operation, do it only once
|
||||
min_col = self.min_column
|
||||
max_col = self.max_column + 1
|
||||
for row in remainder:
|
||||
for col in range(min_col, max_col):
|
||||
if (row, col) in self._cells:
|
||||
del self._cells[row, col]
|
||||
self._current_row = self.max_row
|
||||
if not self._cells:
|
||||
self._current_row = 0
|
||||
|
||||
|
||||
def delete_cols(self, idx, amount=1):
|
||||
"""
|
||||
Delete column or columns from col==idx
|
||||
"""
|
||||
|
||||
remainder = _gutter(idx, amount, self.max_column)
|
||||
|
||||
self._move_cells(min_col=idx+amount, offset=-amount, row_or_col="column")
|
||||
|
||||
# calculating min and max row is an expensive operation, do it only once
|
||||
min_row = self.min_row
|
||||
max_row = self.max_row + 1
|
||||
for col in remainder:
|
||||
for row in range(min_row, max_row):
|
||||
if (row, col) in self._cells:
|
||||
del self._cells[row, col]
|
||||
|
||||
|
||||
def move_range(self, cell_range, rows=0, cols=0, translate=False):
|
||||
"""
|
||||
Move a cell range by the number of rows and/or columns:
|
||||
down if rows > 0 and up if rows < 0
|
||||
right if cols > 0 and left if cols < 0
|
||||
Existing cells will be overwritten.
|
||||
Formulae and references will not be updated.
|
||||
"""
|
||||
if isinstance(cell_range, str):
|
||||
cell_range = CellRange(cell_range)
|
||||
if not isinstance(cell_range, CellRange):
|
||||
raise ValueError("Only CellRange objects can be moved")
|
||||
if not rows and not cols:
|
||||
return
|
||||
|
||||
down = rows > 0
|
||||
right = cols > 0
|
||||
|
||||
if rows:
|
||||
cells = sorted(cell_range.rows, reverse=down)
|
||||
else:
|
||||
cells = sorted(cell_range.cols, reverse=right)
|
||||
|
||||
for row, col in chain.from_iterable(cells):
|
||||
self._move_cell(row, col, rows, cols, translate)
|
||||
|
||||
# rebase moved range
|
||||
cell_range.shift(row_shift=rows, col_shift=cols)
|
||||
|
||||
|
||||
def _move_cell(self, row, column, row_offset, col_offset, translate=False):
|
||||
"""
|
||||
Move a cell from one place to another.
|
||||
Delete at old index
|
||||
Rebase coordinate
|
||||
"""
|
||||
cell = self._get_cell(row, column)
|
||||
new_row = cell.row + row_offset
|
||||
new_col = cell.column + col_offset
|
||||
self._cells[new_row, new_col] = cell
|
||||
del self._cells[(cell.row, cell.column)]
|
||||
cell.row = new_row
|
||||
cell.column = new_col
|
||||
if translate and cell.data_type == "f":
|
||||
t = Translator(cell.value, cell.coordinate)
|
||||
cell.value = t.translate_formula(row_delta=row_offset, col_delta=col_offset)
|
||||
|
||||
|
||||
def _invalid_row(self, iterable):
|
||||
raise TypeError('Value must be a list, tuple, range or generator, or a dict. Supplied value is {0}'.format(
|
||||
type(iterable))
|
||||
)
|
||||
|
||||
|
||||
def _add_column(self):
|
||||
"""Dimension factory for column information"""
|
||||
|
||||
return ColumnDimension(self)
|
||||
|
||||
def _add_row(self):
|
||||
"""Dimension factory for row information"""
|
||||
|
||||
return RowDimension(self)
|
||||
|
||||
|
||||
@property
|
||||
def print_title_rows(self):
|
||||
"""Rows to be printed at the top of every page (ex: '1:3')"""
|
||||
if self._print_rows:
|
||||
return str(self._print_rows)
|
||||
|
||||
|
||||
@print_title_rows.setter
|
||||
def print_title_rows(self, rows):
|
||||
"""
|
||||
Set rows to be printed on the top of every page
|
||||
format `1:3`
|
||||
"""
|
||||
if rows is not None:
|
||||
self._print_rows = RowRange(rows)
|
||||
|
||||
|
||||
@property
|
||||
def print_title_cols(self):
|
||||
"""Columns to be printed at the left side of every page (ex: 'A:C')"""
|
||||
if self._print_cols:
|
||||
return str(self._print_cols)
|
||||
|
||||
|
||||
@print_title_cols.setter
|
||||
def print_title_cols(self, cols):
|
||||
"""
|
||||
Set cols to be printed on the left of every page
|
||||
format ``A:C`
|
||||
"""
|
||||
if cols is not None:
|
||||
self._print_cols = ColRange(cols)
|
||||
|
||||
|
||||
@property
|
||||
def print_titles(self):
|
||||
titles = PrintTitles(cols=self._print_cols, rows=self._print_rows, title=self.title)
|
||||
return str(titles)
|
||||
|
||||
|
||||
@property
|
||||
def print_area(self):
|
||||
"""
|
||||
The print area for the worksheet, or None if not set. To set, supply a range
|
||||
like 'A1:D4' or a list of ranges.
|
||||
"""
|
||||
self._print_area.title = self.title
|
||||
return str(self._print_area)
|
||||
|
||||
|
||||
@print_area.setter
|
||||
def print_area(self, value):
|
||||
"""
|
||||
Range of cells in the form A1:D4 or list of ranges. Print area can be cleared
|
||||
by passing `None` or an empty list
|
||||
"""
|
||||
if not value:
|
||||
self._print_area = PrintArea()
|
||||
elif isinstance(value, str):
|
||||
self._print_area = PrintArea.from_string(value)
|
||||
elif hasattr(value, "__iter__"):
|
||||
self._print_area = PrintArea.from_string(",".join(value))
|
||||
|
||||
|
||||
def _gutter(idx, offset, max_val):
|
||||
"""
|
||||
When deleting rows and columns are deleted we rely on overwriting.
|
||||
This may not be the case for a large offset on small set of cells:
|
||||
range(cells_to_delete) > range(cell_to_be_moved)
|
||||
"""
|
||||
gutter = range(max(max_val+1-offset, idx), min(idx+offset, max_val)+1)
|
||||
return gutter
|
Reference in New Issue
Block a user