diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/worksheet/datavalidation.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/openpyxl/worksheet/datavalidation.py | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/datavalidation.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/datavalidation.py new file mode 100644 index 00000000..f5077d97 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/datavalidation.py @@ -0,0 +1,202 @@ +# Copyright (c) 2010-2024 openpyxl + +from collections import defaultdict +from itertools import chain +from operator import itemgetter + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Bool, + NoneSet, + String, + Sequence, + Alias, + Integer, + Convertible, +) +from openpyxl.descriptors.nested import NestedText + +from openpyxl.utils import ( + rows_from_range, + coordinate_to_tuple, + get_column_letter, +) + + +def collapse_cell_addresses(cells, input_ranges=()): + """ Collapse a collection of cell co-ordinates down into an optimal + range or collection of ranges. + + E.g. Cells A1, A2, A3, B1, B2 and B3 should have the data-validation + object applied, attempt to collapse down to a single range, A1:B3. + + Currently only collapsing contiguous vertical ranges (i.e. above + example results in A1:A3 B1:B3). + """ + + ranges = list(input_ranges) + + # convert cell into row, col tuple + raw_coords = (coordinate_to_tuple(cell) for cell in cells) + + # group by column in order + grouped_coords = defaultdict(list) + for row, col in sorted(raw_coords, key=itemgetter(1)): + grouped_coords[col].append(row) + + # create range string from first and last row in column + for col, cells in grouped_coords.items(): + col = get_column_letter(col) + fmt = "{0}{1}:{2}{3}" + if len(cells) == 1: + fmt = "{0}{1}" + r = fmt.format(col, min(cells), col, max(cells)) + ranges.append(r) + + return " ".join(ranges) + + +def expand_cell_ranges(range_string): + """ + Expand cell ranges to a sequence of addresses. + Reverse of collapse_cell_addresses + Eg. converts "A1:A2 B1:B2" to (A1, A2, B1, B2) + """ + # expand ranges to rows and then flatten + rows = (rows_from_range(rs) for rs in range_string.split()) # list of rows + cells = (chain(*row) for row in rows) # flatten rows + return set(chain(*cells)) + + +from .cell_range import MultiCellRange + + +class DataValidation(Serialisable): + + tagname = "dataValidation" + + sqref = Convertible(expected_type=MultiCellRange) + cells = Alias("sqref") + ranges = Alias("sqref") + + showDropDown = Bool(allow_none=True) + hide_drop_down = Alias('showDropDown') + showInputMessage = Bool(allow_none=True) + showErrorMessage = Bool(allow_none=True) + allowBlank = Bool(allow_none=True) + allow_blank = Alias('allowBlank') + + errorTitle = String(allow_none = True) + error = String(allow_none = True) + promptTitle = String(allow_none = True) + prompt = String(allow_none = True) + formula1 = NestedText(allow_none=True, expected_type=str) + formula2 = NestedText(allow_none=True, expected_type=str) + + type = NoneSet(values=("whole", "decimal", "list", "date", "time", + "textLength", "custom")) + errorStyle = NoneSet(values=("stop", "warning", "information")) + imeMode = NoneSet(values=("noControl", "off", "on", "disabled", + "hiragana", "fullKatakana", "halfKatakana", "fullAlpha","halfAlpha", + "fullHangul", "halfHangul")) + operator = NoneSet(values=("between", "notBetween", "equal", "notEqual", + "lessThan", "lessThanOrEqual", "greaterThan", "greaterThanOrEqual")) + validation_type = Alias('type') + + def __init__(self, + type=None, + formula1=None, + formula2=None, + showErrorMessage=False, + showInputMessage=False, + showDropDown=False, + allowBlank=False, + sqref=(), + promptTitle=None, + errorStyle=None, + error=None, + prompt=None, + errorTitle=None, + imeMode=None, + operator=None, + allow_blank=None, + ): + self.sqref = sqref + self.showDropDown = showDropDown + self.imeMode = imeMode + self.operator = operator + self.formula1 = formula1 + self.formula2 = formula2 + if allow_blank is not None: + allowBlank = allow_blank + self.allowBlank = allowBlank + self.showErrorMessage = showErrorMessage + self.showInputMessage = showInputMessage + self.type = type + self.promptTitle = promptTitle + self.errorStyle = errorStyle + self.error = error + self.prompt = prompt + self.errorTitle = errorTitle + + + def add(self, cell): + """Adds a cell or cell coordinate to this validator""" + if hasattr(cell, "coordinate"): + cell = cell.coordinate + self.sqref += cell + + + def __contains__(self, cell): + if hasattr(cell, "coordinate"): + cell = cell.coordinate + return cell in self.sqref + + +class DataValidationList(Serialisable): + + tagname = "dataValidations" + + disablePrompts = Bool(allow_none=True) + xWindow = Integer(allow_none=True) + yWindow = Integer(allow_none=True) + dataValidation = Sequence(expected_type=DataValidation) + + __elements__ = ('dataValidation',) + __attrs__ = ('disablePrompts', 'xWindow', 'yWindow', 'count') + + def __init__(self, + disablePrompts=None, + xWindow=None, + yWindow=None, + count=None, + dataValidation=(), + ): + self.disablePrompts = disablePrompts + self.xWindow = xWindow + self.yWindow = yWindow + self.dataValidation = dataValidation + + + @property + def count(self): + return len(self) + + + def __len__(self): + return len(self.dataValidation) + + + def append(self, dv): + self.dataValidation.append(dv) + + + def to_tree(self, tagname=None): + """ + Need to skip validations that have no cell ranges + """ + ranges = self.dataValidation # copy + self.dataValidation = [r for r in self.dataValidation if bool(r.sqref)] + xml = super().to_tree(tagname) + self.dataValidation = ranges + return xml |