mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-07-02 14:27:31 +00:00
Version 0.1
Added sidebar, Dashboard, Line Graph
This commit is contained in:
1
.venv/Lib/site-packages/openpyxl/worksheet/__init__.py
Normal file
1
.venv/Lib/site-packages/openpyxl/worksheet/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Copyright (c) 2010-2022 openpyxl
|
186
.venv/Lib/site-packages/openpyxl/worksheet/_read_only.py
Normal file
186
.venv/Lib/site-packages/openpyxl/worksheet/_read_only.py
Normal file
@ -0,0 +1,186 @@
|
||||
# Copyright (c) 2010-2022 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
|
||||
|
||||
|
||||
def read_dimension(source):
|
||||
parser = WorkSheetParser(source, [])
|
||||
return parser.parse_dimensions()
|
||||
|
||||
|
||||
class ReadOnlyWorksheet(object):
|
||||
|
||||
_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()
|
||||
|
||||
|
||||
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
|
||||
src = self._get_source()
|
||||
parser = WorkSheetParser(src, self._shared_strings,
|
||||
data_only=self.parent.data_only, epoch=self.parent.epoch,
|
||||
date_formats=self.parent._date_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
|
||||
|
||||
src.close() # make sure source is always closed
|
||||
|
||||
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
|
455
.venv/Lib/site-packages/openpyxl/worksheet/_reader.py
Normal file
455
.venv/Lib/site-packages/openpyxl/worksheet/_reader.py
Normal file
@ -0,0 +1,455 @@
|
||||
# Copyright (c) 2010-2022 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 .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)
|
||||
|
||||
|
||||
class WorkSheetParser(object):
|
||||
|
||||
def __init__(self, src, shared_strings, data_only=False,
|
||||
epoch=WINDOWS_EPOCH, date_formats=set(),
|
||||
timedelta_formats=set()):
|
||||
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.array_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()
|
||||
|
||||
|
||||
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'
|
||||
richtext = Text.from_tree(child)
|
||||
value = richtext.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":
|
||||
self.array_formulae[coordinate] = dict(formula.attrib)
|
||||
|
||||
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)
|
||||
|
||||
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(object):
|
||||
"""
|
||||
Create a parser and apply it to a workbook
|
||||
"""
|
||||
|
||||
def __init__(self, ws, xml_source, shared_strings, data_only):
|
||||
self.ws = ws
|
||||
self.parser = WorkSheetParser(xml_source, shared_strings,
|
||||
data_only, ws.parent.epoch, ws.parent._date_formats,
|
||||
ws.parent._timedelta_formats)
|
||||
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
|
||||
self.ws.formula_attributes = self.parser.array_formulae
|
||||
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[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[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
.venv/Lib/site-packages/openpyxl/worksheet/_write_only.py
Normal file
160
.venv/Lib/site-packages/openpyxl/worksheet/_write_only.py
Normal file
@ -0,0 +1,160 @@
|
||||
# Copyright (c) 2010-2022 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(WriteOnlyWorksheet, self).__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
.venv/Lib/site-packages/openpyxl/worksheet/_writer.py
Normal file
390
.venv/Lib/site-packages/openpyxl/worksheet/_writer.py
Normal file
@ -0,0 +1,390 @@
|
||||
# Copyright (c) 2010-2022 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 = HyperlinkList()
|
||||
|
||||
for link in self.ws._hyperlinks:
|
||||
if link.target:
|
||||
rel = Relationship(type="hyperlink", TargetMode="External", Target=link.target)
|
||||
self._rels.append(rel)
|
||||
link.id = rel.id
|
||||
links.hyperlink.append(link)
|
||||
|
||||
if links:
|
||||
self.xf.send(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)
|
501
.venv/Lib/site-packages/openpyxl/worksheet/cell_range.py
Normal file
501
.venv/Lib/site-packages/openpyxl/worksheet/cell_range.py
Normal file
@ -0,0 +1,501 @@
|
||||
# Copyright (c) 2010-2022 openpyxl
|
||||
|
||||
from copy import copy
|
||||
|
||||
from openpyxl.descriptors import Strict
|
||||
from openpyxl.descriptors import MinMax, Sequence
|
||||
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 __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 bottown 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 = Sequence(expected_type=CellRange)
|
||||
|
||||
|
||||
def __init__(self, ranges=()):
|
||||
if isinstance(ranges, str):
|
||||
ranges = [CellRange(r) for r in ranges.split()]
|
||||
self.ranges = 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.ranges])
|
||||
return "<{0} [{1}]>".format(self.__class__.__name__, ranges)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
ranges = u" ".join([str(r) for r in self.ranges])
|
||||
return ranges
|
||||
|
||||
__str__ = __str__
|
||||
|
||||
|
||||
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.append(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):
|
||||
n = MultiCellRange()
|
||||
|
||||
for r in self.ranges:
|
||||
n.ranges.append(copy(r))
|
||||
return n
|
34
.venv/Lib/site-packages/openpyxl/worksheet/cell_watch.py
Normal file
34
.venv/Lib/site-packages/openpyxl/worksheet/cell_watch.py
Normal file
@ -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
.venv/Lib/site-packages/openpyxl/worksheet/controls.py
Normal file
107
.venv/Lib/site-packages/openpyxl/worksheet/controls.py
Normal file
@ -0,0 +1,107 @@
|
||||
# Copyright (c) 2010-2022 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
.venv/Lib/site-packages/openpyxl/worksheet/copier.py
Normal file
70
.venv/Lib/site-packages/openpyxl/worksheet/copier.py
Normal file
@ -0,0 +1,70 @@
|
||||
# Copyright (c) 2010-2022 openpyxl
|
||||
|
||||
#standard lib imports
|
||||
from copy import copy
|
||||
|
||||
from .worksheet import Worksheet
|
||||
|
||||
|
||||
class WorksheetCopy(object):
|
||||
"""
|
||||
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
.venv/Lib/site-packages/openpyxl/worksheet/custom.py
Normal file
35
.venv/Lib/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
|
||||
|
203
.venv/Lib/site-packages/openpyxl/worksheet/datavalidation.py
Normal file
203
.venv/Lib/site-packages/openpyxl/worksheet/datavalidation.py
Normal file
@ -0,0 +1,203 @@
|
||||
# Copyright (c) 2010-2022 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")
|
||||
|
||||
showErrorMessage = Bool()
|
||||
showDropDown = Bool(allow_none=True)
|
||||
hide_drop_down = Alias('showDropDown')
|
||||
showInputMessage = Bool()
|
||||
showErrorMessage = Bool()
|
||||
allowBlank = Bool()
|
||||
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=True,
|
||||
showInputMessage=True,
|
||||
showDropDown=None,
|
||||
allowBlank=None,
|
||||
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(DataValidationList, self).to_tree(tagname)
|
||||
self.dataValidation = ranges
|
||||
return xml
|
296
.venv/Lib/site-packages/openpyxl/worksheet/dimensions.py
Normal file
296
.venv/Lib/site-packages/openpyxl/worksheet/dimensions.py
Normal file
@ -0,0 +1,296 @@
|
||||
# Copyright (c) 2010-2022 openpyxl
|
||||
|
||||
from copy import copy
|
||||
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.utils import (
|
||||
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(Dimension, self).__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
|
||||
|
||||
|
||||
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(RowDimension, self).__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(ColumnDimension, self).__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)
|
||||
|
||||
|
||||
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(DimensionHolder, self).__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
.venv/Lib/site-packages/openpyxl/worksheet/drawing.py
Normal file
14
.venv/Lib/site-packages/openpyxl/worksheet/drawing.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2010-2022 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
.venv/Lib/site-packages/openpyxl/worksheet/errors.py
Normal file
93
.venv/Lib/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
|
||||
|
363
.venv/Lib/site-packages/openpyxl/worksheet/filters.py
Normal file
363
.venv/Lib/site-packages/openpyxl/worksheet/filters.py
Normal file
@ -0,0 +1,363 @@
|
||||
# Copyright (c) 2010-2022 openpyxl
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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"
|
||||
|
||||
operator = NoneSet(values=(['equal', 'lessThan', 'lessThanOrEqual',
|
||||
'notEqual', 'greaterThanOrEqual', 'greaterThan']))
|
||||
val = String()
|
||||
|
||||
def __init__(self,
|
||||
operator=None,
|
||||
val=None,
|
||||
):
|
||||
self.operator = operator
|
||||
self.val = val
|
||||
|
||||
|
||||
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=None,
|
||||
showButton=None,
|
||||
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 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)
|
270
.venv/Lib/site-packages/openpyxl/worksheet/header_footer.py
Normal file
270
.venv/Lib/site-packages/openpyxl/worksheet/header_footer.py
Normal file
@ -0,0 +1,270 @@
|
||||
# Copyright (c) 2010-2022 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)
|
||||
|
61
.venv/Lib/site-packages/openpyxl/worksheet/hyperlink.py
Normal file
61
.venv/Lib/site-packages/openpyxl/worksheet/hyperlink.py
Normal file
@ -0,0 +1,61 @@
|
||||
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"
|
||||
|
||||
hyperlink = Sequence(expected_type=Hyperlink)
|
||||
|
||||
def __init__(self, hyperlink=()):
|
||||
self.hyperlink = hyperlink
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.hyperlink)
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return len(self.hyperlink)
|
||||
|
||||
|
||||
def append(self, value):
|
||||
values = self.hyperlink[:]
|
||||
values.append(value)
|
||||
if not value.id:
|
||||
value.id = "rId{0}".format(len(values))
|
||||
self.hyperlink = values
|
141
.venv/Lib/site-packages/openpyxl/worksheet/merge.py
Normal file
141
.venv/Lib/site-packages/openpyxl/worksheet/merge.py
Normal file
@ -0,0 +1,141 @@
|
||||
# Copyright (c) 2010-2022 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(MergeCell, self).__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
.venv/Lib/site-packages/openpyxl/worksheet/ole.py
Normal file
133
.venv/Lib/site-packages/openpyxl/worksheet/ole.py
Normal file
@ -0,0 +1,133 @@
|
||||
# Copyright (c) 2010-2022 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
.venv/Lib/site-packages/openpyxl/worksheet/page.py
Normal file
174
.venv/Lib/site-packages/openpyxl/worksheet/page.py
Normal file
@ -0,0 +1,174 @@
|
||||
# Copyright (c) 2010-2022 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(PrintPageSetup, cls).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
.venv/Lib/site-packages/openpyxl/worksheet/pagebreak.py
Normal file
94
.venv/Lib/site-packages/openpyxl/worksheet/pagebreak.py
Normal file
@ -0,0 +1,94 @@
|
||||
# Copyright (c) 2010-2022 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__
|
8
.venv/Lib/site-packages/openpyxl/worksheet/picture.py
Normal file
8
.venv/Lib/site-packages/openpyxl/worksheet/picture.py
Normal file
@ -0,0 +1,8 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
# same as related
|
||||
|
||||
class SheetBackgroundPicture(Serialisable):
|
||||
|
||||
tagname = "sheetBackgroundPicture"
|
97
.venv/Lib/site-packages/openpyxl/worksheet/properties.py
Normal file
97
.venv/Lib/site-packages/openpyxl/worksheet/properties.py
Normal file
@ -0,0 +1,97 @@
|
||||
# Copyright (c) 2010-2022 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
.venv/Lib/site-packages/openpyxl/worksheet/protection.py
Normal file
120
.venv/Lib/site-packages/openpyxl/worksheet/protection.py
Normal file
@ -0,0 +1,120 @@
|
||||
# Copyright (c) 2010-2022 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(object):
|
||||
_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(SheetProtection, self).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
.venv/Lib/site-packages/openpyxl/worksheet/related.py
Normal file
17
.venv/Lib/site-packages/openpyxl/worksheet/related.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Copyright (c) 2010-2022 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(Related, self).to_tree(tagname)
|
105
.venv/Lib/site-packages/openpyxl/worksheet/scenario.py
Normal file
105
.venv/Lib/site-packages/openpyxl/worksheet/scenario.py
Normal file
@ -0,0 +1,105 @@
|
||||
# Copyright (c) 2010-2022 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
.venv/Lib/site-packages/openpyxl/worksheet/smart_tag.py
Normal file
78
.venv/Lib/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
.venv/Lib/site-packages/openpyxl/worksheet/table.py
Normal file
385
.venv/Lib/site-packages/openpyxl/worksheet/table.py
Normal file
@ -0,0 +1,385 @@
|
||||
# Copyright (c) 2010-2022 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(TableColumn, self).__iter__():
|
||||
if k == 'name':
|
||||
v = escape(v)
|
||||
yield k, v
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
self = super(TableColumn, cls).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(TableNameDescriptor, self).__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(Table, self).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:
|
||||
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()]
|
149
.venv/Lib/site-packages/openpyxl/worksheet/views.py
Normal file
149
.venv/Lib/site-packages/openpyxl/worksheet/views.py
Normal file
@ -0,0 +1,149 @@
|
||||
# Copyright (c) 2010-2022 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
|
901
.venv/Lib/site-packages/openpyxl/worksheet/worksheet.py
Normal file
901
.venv/Lib/site-packages/openpyxl/worksheet/worksheet.py
Normal file
@ -0,0 +1,901 @@
|
||||
# Copyright (c) 2010-2022 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,
|
||||
absolute_coordinate,
|
||||
)
|
||||
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 COL_RANGE_RE, ROW_RANGE_RE
|
||||
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
|
||||
|
||||
|
||||
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 = None
|
||||
self.page_margins = PageMargins()
|
||||
self.views = SheetViewList()
|
||||
self.protection = SheetProtection()
|
||||
|
||||
self._current_row = 0
|
||||
self.auto_filter = AutoFilter()
|
||||
self.paper_size = None
|
||||
self.formula_attributes = {}
|
||||
self.orientation = None
|
||||
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.sheetView[0]
|
||||
|
||||
|
||||
@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 page_breaks(self):
|
||||
return (self.row_breaks, self.col_breaks) # legacy, remove at some point
|
||||
|
||||
|
||||
@property
|
||||
def show_gridlines(self):
|
||||
return self.sheet_view.showGridLines
|
||||
|
||||
|
||||
""" To keep compatibility with previous versions"""
|
||||
@property
|
||||
def show_summary_below(self):
|
||||
return self.sheet_properties.outlinePr.summaryBelow
|
||||
|
||||
@property
|
||||
def show_summary_right(self):
|
||||
return self.sheet_properties.outlinePr.summaryRight
|
||||
|
||||
|
||||
@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("Row numbers must be between 1 and 1048576")
|
||||
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 minimium row index containing data (1-based)
|
||||
|
||||
:type: int
|
||||
"""
|
||||
min_row = 1
|
||||
if self._cells:
|
||||
rows = set(c[0] for c in self._cells)
|
||||
min_row = min(rows)
|
||||
return min_row
|
||||
|
||||
|
||||
@property
|
||||
def max_row(self):
|
||||
"""The maximum row index containing data (1-based)
|
||||
|
||||
:type: int
|
||||
"""
|
||||
max_row = 1
|
||||
if self._cells:
|
||||
rows = set(c[0] for c in self._cells)
|
||||
max_row = max(rows)
|
||||
return max_row
|
||||
|
||||
|
||||
@property
|
||||
def min_column(self):
|
||||
"""The minimum column index containing data (1-based)
|
||||
|
||||
:type: int
|
||||
"""
|
||||
min_col = 1
|
||||
if self._cells:
|
||||
cols = set(c[1] for c in self._cells)
|
||||
min_col = min(cols)
|
||||
return min_col
|
||||
|
||||
|
||||
@property
|
||||
def max_column(self):
|
||||
"""The maximum column index containing data (1-based)
|
||||
|
||||
:type: int
|
||||
"""
|
||||
max_col = 1
|
||||
if self._cells:
|
||||
cols = set(c[1] for c in self._cells)
|
||||
max_col = max(cols)
|
||||
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()
|
||||
|
||||
|
||||
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 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:
|
||||
if not ROW_RANGE_RE.match(rows):
|
||||
raise ValueError("Print title rows must be the form 1:3")
|
||||
self._print_rows = 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 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:
|
||||
if not COL_RANGE_RE.match(cols):
|
||||
raise ValueError("Print title cols must be the form C:D")
|
||||
self._print_cols = cols
|
||||
|
||||
|
||||
@property
|
||||
def print_titles(self):
|
||||
if self.print_title_cols and self.print_title_rows:
|
||||
return ",".join([self.print_title_rows, self.print_title_cols])
|
||||
else:
|
||||
return self.print_title_rows or self.print_title_cols
|
||||
|
||||
|
||||
@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.
|
||||
"""
|
||||
return self._print_area
|
||||
|
||||
|
||||
@print_area.setter
|
||||
def print_area(self, value):
|
||||
"""
|
||||
Range of cells in the form A1:D4 or list of ranges
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
value = [value]
|
||||
|
||||
self._print_area = [absolute_coordinate(v) for v in 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