diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl')
190 files changed, 29137 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/__init__.py new file mode 100644 index 00000000..14e84323 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2010-2024 openpyxl + +DEBUG = False + +from openpyxl.compat.numbers import NUMPY +from openpyxl.xml import DEFUSEDXML, LXML +from openpyxl.workbook import Workbook +from openpyxl.reader.excel import load_workbook as open +from openpyxl.reader.excel import load_workbook +import openpyxl._constants as constants + +# Expose constants especially the version number + +__author__ = constants.__author__ +__author_email__ = constants.__author_email__ +__license__ = constants.__license__ +__maintainer_email__ = constants.__maintainer_email__ +__url__ = constants.__url__ +__version__ = constants.__version__ diff --git a/.venv/lib/python3.12/site-packages/openpyxl/_constants.py b/.venv/lib/python3.12/site-packages/openpyxl/_constants.py new file mode 100644 index 00000000..e7ff6b94 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/_constants.py @@ -0,0 +1,13 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Package metadata +""" + +__author__ = "See AUTHORS" +__author_email__ = "charlie.clark@clark-consulting.eu" +__license__ = "MIT" +__maintainer_email__ = "openpyxl-users@googlegroups.com" +__url__ = "https://openpyxl.readthedocs.io" +__version__ = "3.1.5" +__python__ = "3.8" diff --git a/.venv/lib/python3.12/site-packages/openpyxl/cell/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/cell/__init__.py new file mode 100644 index 00000000..0c1ca3ff --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/cell/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2010-2024 openpyxl + +from .cell import Cell, WriteOnlyCell, MergedCell +from .read_only import ReadOnlyCell diff --git a/.venv/lib/python3.12/site-packages/openpyxl/cell/_writer.py b/.venv/lib/python3.12/site-packages/openpyxl/cell/_writer.py new file mode 100644 index 00000000..4a27d680 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/cell/_writer.py @@ -0,0 +1,136 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element, SubElement, whitespace, XML_NS +from openpyxl import LXML +from openpyxl.utils.datetime import to_excel, to_ISO8601 +from datetime import timedelta + +from openpyxl.worksheet.formula import DataTableFormula, ArrayFormula +from openpyxl.cell.rich_text import CellRichText + +def _set_attributes(cell, styled=None): + """ + Set coordinate and datatype + """ + coordinate = cell.coordinate + attrs = {'r': coordinate} + if styled: + attrs['s'] = f"{cell.style_id}" + + if cell.data_type == "s": + attrs['t'] = "inlineStr" + elif cell.data_type != 'f': + attrs['t'] = cell.data_type + + value = cell._value + + if cell.data_type == "d": + if hasattr(value, "tzinfo") and value.tzinfo is not None: + raise TypeError("Excel does not support timezones in datetimes. " + "The tzinfo in the datetime/time object must be set to None.") + + if cell.parent.parent.iso_dates and not isinstance(value, timedelta): + value = to_ISO8601(value) + else: + attrs['t'] = "n" + value = to_excel(value, cell.parent.parent.epoch) + + if cell.hyperlink: + cell.parent._hyperlinks.append(cell.hyperlink) + + return value, attrs + + +def etree_write_cell(xf, worksheet, cell, styled=None): + + value, attributes = _set_attributes(cell, styled) + + el = Element("c", attributes) + if value is None or value == "": + xf.write(el) + return + + if cell.data_type == 'f': + attrib = {} + + if isinstance(value, ArrayFormula): + attrib = dict(value) + value = value.text + + elif isinstance(value, DataTableFormula): + attrib = dict(value) + value = None + + formula = SubElement(el, 'f', attrib) + if value is not None and not attrib.get('t') == "dataTable": + formula.text = value[1:] + value = None + + if cell.data_type == 's': + if isinstance(value, CellRichText): + el.append(value.to_tree()) + else: + inline_string = Element("is") + text = Element('t') + text.text = value + whitespace(text) + inline_string.append(text) + el.append(inline_string) + + else: + cell_content = SubElement(el, 'v') + if value is not None: + cell_content.text = safe_string(value) + + xf.write(el) + + +def lxml_write_cell(xf, worksheet, cell, styled=False): + value, attributes = _set_attributes(cell, styled) + + if value == '' or value is None: + with xf.element("c", attributes): + return + + with xf.element('c', attributes): + if cell.data_type == 'f': + attrib = {} + + if isinstance(value, ArrayFormula): + attrib = dict(value) + value = value.text + + elif isinstance(value, DataTableFormula): + attrib = dict(value) + value = None + + with xf.element('f', attrib): + if value is not None and not attrib.get('t') == "dataTable": + xf.write(value[1:]) + value = None + + if cell.data_type == 's': + if isinstance(value, CellRichText): + el = value.to_tree() + xf.write(el) + else: + with xf.element("is"): + if isinstance(value, str): + attrs = {} + if value != value.strip(): + attrs["{%s}space" % XML_NS] = "preserve" + el = Element("t", attrs) # lxml can't handle xml-ns + el.text = value + xf.write(el) + + else: + with xf.element("v"): + if value is not None: + xf.write(safe_string(value)) + + +if LXML: + write_cell = lxml_write_cell +else: + write_cell = etree_write_cell diff --git a/.venv/lib/python3.12/site-packages/openpyxl/cell/cell.py b/.venv/lib/python3.12/site-packages/openpyxl/cell/cell.py new file mode 100644 index 00000000..d29be280 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/cell/cell.py @@ -0,0 +1,332 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Manage individual cells in a spreadsheet. + +The Cell class is required to know its value and type, display options, +and any other features of an Excel cell. Utilities for referencing +cells using Excel's 'A1' column/row nomenclature are also provided. + +""" + +__docformat__ = "restructuredtext en" + +# Python stdlib imports +from copy import copy +import datetime +import re + + +from openpyxl.compat import ( + NUMERIC_TYPES, +) + +from openpyxl.utils.exceptions import IllegalCharacterError + +from openpyxl.utils import get_column_letter +from openpyxl.styles import numbers, is_date_format +from openpyxl.styles.styleable import StyleableObject +from openpyxl.worksheet.hyperlink import Hyperlink +from openpyxl.worksheet.formula import DataTableFormula, ArrayFormula +from openpyxl.cell.rich_text import CellRichText + +# constants + +TIME_TYPES = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta) +TIME_FORMATS = { + datetime.datetime:numbers.FORMAT_DATE_DATETIME, + datetime.date:numbers.FORMAT_DATE_YYYYMMDD2, + datetime.time:numbers.FORMAT_DATE_TIME6, + datetime.timedelta:numbers.FORMAT_DATE_TIMEDELTA, + } + +STRING_TYPES = (str, bytes, CellRichText) +KNOWN_TYPES = NUMERIC_TYPES + TIME_TYPES + STRING_TYPES + (bool, type(None)) + +ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]') +ERROR_CODES = ('#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', + '#N/A') + +TYPE_STRING = 's' +TYPE_FORMULA = 'f' +TYPE_NUMERIC = 'n' +TYPE_BOOL = 'b' +TYPE_NULL = 'n' +TYPE_INLINE = 'inlineStr' +TYPE_ERROR = 'e' +TYPE_FORMULA_CACHE_STRING = 'str' + +VALID_TYPES = (TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL, + TYPE_NULL, TYPE_INLINE, TYPE_ERROR, TYPE_FORMULA_CACHE_STRING) + + +_TYPES = {int:'n', float:'n', str:'s', bool:'b'} + + +def get_type(t, value): + if isinstance(value, NUMERIC_TYPES): + dt = 'n' + elif isinstance(value, STRING_TYPES): + dt = 's' + elif isinstance(value, TIME_TYPES): + dt = 'd' + elif isinstance(value, (DataTableFormula, ArrayFormula)): + dt = 'f' + else: + return + _TYPES[t] = dt + return dt + + +def get_time_format(t): + value = TIME_FORMATS.get(t) + if value: + return value + for base in t.mro()[1:]: + value = TIME_FORMATS.get(base) + if value: + TIME_FORMATS[t] = value + return value + raise ValueError("Could not get time format for {0!r}".format(value)) + + +class Cell(StyleableObject): + """Describes cell associated properties. + + Properties of interest include style, type, value, and address. + + """ + __slots__ = ( + 'row', + 'column', + '_value', + 'data_type', + 'parent', + '_hyperlink', + '_comment', + ) + + def __init__(self, worksheet, row=None, column=None, value=None, style_array=None): + super().__init__(worksheet, style_array) + self.row = row + """Row number of this cell (1-based)""" + self.column = column + """Column number of this cell (1-based)""" + # _value is the stored value, while value is the displayed value + self._value = None + self._hyperlink = None + self.data_type = 'n' + if value is not None: + self.value = value + self._comment = None + + + @property + def coordinate(self): + """This cell's coordinate (ex. 'A5')""" + col = get_column_letter(self.column) + return f"{col}{self.row}" + + + @property + def col_idx(self): + """The numerical index of the column""" + return self.column + + + @property + def column_letter(self): + return get_column_letter(self.column) + + + @property + def encoding(self): + return self.parent.encoding + + @property + def base_date(self): + return self.parent.parent.epoch + + + def __repr__(self): + return "<Cell {0!r}.{1}>".format(self.parent.title, self.coordinate) + + def check_string(self, value): + """Check string coding, length, and line break character""" + if value is None: + return + # convert to str string + if not isinstance(value, str): + value = str(value, self.encoding) + value = str(value) + # string must never be longer than 32,767 characters + # truncate if necessary + value = value[:32767] + if next(ILLEGAL_CHARACTERS_RE.finditer(value), None): + raise IllegalCharacterError(f"{value} cannot be used in worksheets.") + return value + + def check_error(self, value): + """Tries to convert Error" else N/A""" + try: + return str(value) + except UnicodeDecodeError: + return u'#N/A' + + + def _bind_value(self, value): + """Given a value, infer the correct data type""" + + self.data_type = "n" + t = type(value) + try: + dt = _TYPES[t] + except KeyError: + dt = get_type(t, value) + + if dt is None and value is not None: + raise ValueError("Cannot convert {0!r} to Excel".format(value)) + + if dt: + self.data_type = dt + + if dt == 'd': + if not is_date_format(self.number_format): + self.number_format = get_time_format(t) + + elif dt == "s" and not isinstance(value, CellRichText): + value = self.check_string(value) + if len(value) > 1 and value.startswith("="): + self.data_type = 'f' + elif value in ERROR_CODES: + self.data_type = 'e' + + self._value = value + + + @property + def value(self): + """Get or set the value held in the cell. + + :type: depends on the value (string, float, int or + :class:`datetime.datetime`) + """ + return self._value + + @value.setter + def value(self, value): + """Set the value and infer type and display options.""" + self._bind_value(value) + + @property + def internal_value(self): + """Always returns the value for excel.""" + return self._value + + @property + def hyperlink(self): + """Return the hyperlink target or an empty string""" + return self._hyperlink + + + @hyperlink.setter + def hyperlink(self, val): + """Set value and display for hyperlinks in a cell. + Automatically sets the `value` of the cell with link text, + but you can modify it afterwards by setting the `value` + property, and the hyperlink will remain. + Hyperlink is removed if set to ``None``.""" + if val is None: + self._hyperlink = None + else: + if not isinstance(val, Hyperlink): + val = Hyperlink(ref="", target=val) + val.ref = self.coordinate + self._hyperlink = val + if self._value is None: + self.value = val.target or val.location + + + @property + def is_date(self): + """True if the value is formatted as a date + + :type: bool + """ + return self.data_type == 'd' or ( + self.data_type == 'n' and is_date_format(self.number_format) + ) + + + def offset(self, row=0, column=0): + """Returns a cell location relative to this cell. + + :param row: number of rows to offset + :type row: int + + :param column: number of columns to offset + :type column: int + + :rtype: :class:`openpyxl.cell.Cell` + """ + offset_column = self.col_idx + column + offset_row = self.row + row + return self.parent.cell(column=offset_column, row=offset_row) + + + @property + def comment(self): + """ Returns the comment associated with this cell + + :type: :class:`openpyxl.comments.Comment` + """ + return self._comment + + + @comment.setter + def comment(self, value): + """ + Assign a comment to a cell + """ + + if value is not None: + if value.parent: + value = copy(value) + value.bind(self) + elif value is None and self._comment: + self._comment.unbind() + self._comment = value + + +class MergedCell(StyleableObject): + + """ + Describes the properties of a cell in a merged cell and helps to + display the borders of the merged cell. + + The value of a MergedCell is always None. + """ + + __slots__ = ('row', 'column') + + _value = None + data_type = "n" + comment = None + hyperlink = None + + + def __init__(self, worksheet, row=None, column=None): + super().__init__(worksheet) + self.row = row + self.column = column + + + def __repr__(self): + return "<MergedCell {0!r}.{1}>".format(self.parent.title, self.coordinate) + + coordinate = Cell.coordinate + _comment = comment + value = _value + + +def WriteOnlyCell(ws=None, value=None): + return Cell(worksheet=ws, column=1, row=1, value=value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/cell/read_only.py b/.venv/lib/python3.12/site-packages/openpyxl/cell/read_only.py new file mode 100644 index 00000000..2eec09e4 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/cell/read_only.py @@ -0,0 +1,136 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.cell import Cell +from openpyxl.utils import get_column_letter +from openpyxl.utils.datetime import from_excel +from openpyxl.styles import is_date_format +from openpyxl.styles.numbers import BUILTIN_FORMATS, BUILTIN_FORMATS_MAX_SIZE + + +class ReadOnlyCell: + + __slots__ = ('parent', 'row', 'column', '_value', 'data_type', '_style_id') + + def __init__(self, sheet, row, column, value, data_type='n', style_id=0): + self.parent = sheet + self._value = None + self.row = row + self.column = column + self.data_type = data_type + self.value = value + self._style_id = style_id + + + def __eq__(self, other): + for a in self.__slots__: + if getattr(self, a) != getattr(other, a): + return + return True + + def __ne__(self, other): + return not self.__eq__(other) + + + def __repr__(self): + return "<ReadOnlyCell {0!r}.{1}>".format(self.parent.title, self.coordinate) + + + @property + def coordinate(self): + column = get_column_letter(self.column) + return "{1}{0}".format(self.row, column) + + + @property + def coordinate(self): + return Cell.coordinate.__get__(self) + + + @property + def column_letter(self): + return Cell.column_letter.__get__(self) + + + @property + def style_array(self): + return self.parent.parent._cell_styles[self._style_id] + + + @property + def has_style(self): + return self._style_id != 0 + + + @property + def number_format(self): + _id = self.style_array.numFmtId + if _id < BUILTIN_FORMATS_MAX_SIZE: + return BUILTIN_FORMATS.get(_id, "General") + else: + return self.parent.parent._number_formats[ + _id - BUILTIN_FORMATS_MAX_SIZE] + + @property + def font(self): + _id = self.style_array.fontId + return self.parent.parent._fonts[_id] + + @property + def fill(self): + _id = self.style_array.fillId + return self.parent.parent._fills[_id] + + @property + def border(self): + _id = self.style_array.borderId + return self.parent.parent._borders[_id] + + @property + def alignment(self): + _id = self.style_array.alignmentId + return self.parent.parent._alignments[_id] + + @property + def protection(self): + _id = self.style_array.protectionId + return self.parent.parent._protections[_id] + + + @property + def is_date(self): + return Cell.is_date.__get__(self) + + + @property + def internal_value(self): + return self._value + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + if self._value is not None: + raise AttributeError("Cell is read only") + self._value = value + + +class EmptyCell: + + __slots__ = () + + value = None + is_date = False + font = None + border = None + fill = None + number_format = None + alignment = None + data_type = 'n' + + + def __repr__(self): + return "<EmptyCell>" + +EMPTY_CELL = EmptyCell() diff --git a/.venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py b/.venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py new file mode 100644 index 00000000..373e263e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py @@ -0,0 +1,202 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +RichText definition +""" +from copy import copy +from openpyxl.compat import NUMERIC_TYPES +from openpyxl.cell.text import InlineFont, Text +from openpyxl.descriptors import ( + Strict, + String, + Typed +) + +from openpyxl.xml.functions import Element, whitespace + +class TextBlock(Strict): + """ Represents text string in a specific format + + This class is used as part of constructing a rich text strings. + """ + font = Typed(expected_type=InlineFont) + text = String() + + def __init__(self, font, text): + self.font = font + self.text = text + + + def __eq__(self, other): + return self.text == other.text and self.font == other.font + + + def __str__(self): + """Just retun the text""" + return self.text + + + def __repr__(self): + font = self.font != InlineFont() and self.font or "default" + return f"{self.__class__.__name__} text={self.text}, font={font}" + + + def to_tree(self): + el = Element("r") + el.append(self.font.to_tree(tagname="rPr")) + t = Element("t") + t.text = self.text + whitespace(t) + el.append(t) + return el + +# +# Rich Text class. +# This class behaves just like a list whose members are either simple strings, or TextBlock() instances. +# In addition, it can be initialized in several ways: +# t = CellRFichText([...]) # initialize with a list. +# t = CellRFichText((...)) # initialize with a tuple. +# t = CellRichText(node) # where node is an Element() from either lxml or xml.etree (has a 'tag' element) +class CellRichText(list): + """Represents a rich text string. + + Initialize with a list made of pure strings or :class:`TextBlock` elements + Can index object to access or modify individual rich text elements + it also supports the + and += operators between rich text strings + There are no user methods for this class + + operations which modify the string will generally call an optimization pass afterwards, + that merges text blocks with identical formats, consecutive pure text strings, + and remove empty strings and empty text blocks + """ + + def __init__(self, *args): + if len(args) == 1: + args = args[0] + if isinstance(args, (list, tuple)): + CellRichText._check_rich_text(args) + else: + CellRichText._check_element(args) + args = [args] + else: + CellRichText._check_rich_text(args) + super().__init__(args) + + + @classmethod + def _check_element(cls, value): + if not isinstance(value, (str, TextBlock, NUMERIC_TYPES)): + raise TypeError(f"Illegal CellRichText element {value}") + + + @classmethod + def _check_rich_text(cls, rich_text): + for t in rich_text: + CellRichText._check_element(t) + + @classmethod + def from_tree(cls, node): + text = Text.from_tree(node) + if text.t: + return (text.t.replace('x005F_', ''),) + s = [] + for r in text.r: + t = "" + if r.t: + t = r.t.replace('x005F_', '') + if r.rPr: + s.append(TextBlock(r.rPr, t)) + else: + s.append(t) + return cls(s) + + # Merge TextBlocks with identical formatting + # remove empty elements + def _opt(self): + last_t = None + l = CellRichText(tuple()) + for t in self: + if isinstance(t, str): + if not t: + continue + elif not t.text: + continue + if type(last_t) == type(t): + if isinstance(t, str): + last_t += t + continue + elif last_t.font == t.font: + last_t.text += t.text + continue + if last_t: + l.append(last_t) + last_t = t + if last_t: + # Add remaining TextBlock at end of rich text + l.append(last_t) + super().__setitem__(slice(None), l) + return self + + + def __iadd__(self, arg): + # copy used here to create new TextBlock() so we don't modify the right hand side in _opt() + CellRichText._check_rich_text(arg) + super().__iadd__([copy(e) for e in list(arg)]) + return self._opt() + + + def __add__(self, arg): + return CellRichText([copy(e) for e in list(self) + list(arg)])._opt() + + + def __setitem__(self, indx, val): + CellRichText._check_element(val) + super().__setitem__(indx, val) + self._opt() + + + def append(self, arg): + CellRichText._check_element(arg) + super().append(arg) + + + def extend(self, arg): + CellRichText._check_rich_text(arg) + super().extend(arg) + + + def __repr__(self): + return "CellRichText([{}])".format(', '.join((repr(s) for s in self))) + + + def __str__(self): + return ''.join([str(s) for s in self]) + + + def as_list(self): + """ + Returns a list of the strings contained. + The main reason for this is to make editing easier. + """ + return [str(s) for s in self] + + + def to_tree(self): + """ + Return the full XML representation + """ + container = Element("is") + for obj in self: + if isinstance(obj, TextBlock): + container.append(obj.to_tree()) + + else: + el = Element("r") + t = Element("t") + t.text = obj + whitespace(t) + el.append(t) + container.append(el) + + return container + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/cell/text.py b/.venv/lib/python3.12/site-packages/openpyxl/cell/text.py new file mode 100644 index 00000000..54923dd8 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/cell/text.py @@ -0,0 +1,184 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Richtext definition +""" + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + Integer, + Set, + NoneSet, + Bool, + String, + Sequence, +) +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedString, + NestedText, +) +from openpyxl.styles.fonts import Font + + +class PhoneticProperties(Serialisable): + + tagname = "phoneticPr" + + fontId = Integer() + type = NoneSet(values=(['halfwidthKatakana', 'fullwidthKatakana', + 'Hiragana', 'noConversion'])) + alignment = NoneSet(values=(['noControl', 'left', 'center', 'distributed'])) + + def __init__(self, + fontId=None, + type=None, + alignment=None, + ): + self.fontId = fontId + self.type = type + self.alignment = alignment + + +class PhoneticText(Serialisable): + + tagname = "rPh" + + sb = Integer() + eb = Integer() + t = NestedText(expected_type=str) + text = Alias('t') + + def __init__(self, + sb=None, + eb=None, + t=None, + ): + self.sb = sb + self.eb = eb + self.t = t + + +class InlineFont(Font): + + """ + Font for inline text because, yes what you need are different objects with the same elements but different constraints. + """ + + tagname = "RPrElt" + + rFont = NestedString(allow_none=True) + charset = Font.charset + family = Font.family + b =Font.b + i = Font.i + strike = Font.strike + outline = Font.outline + shadow = Font.shadow + condense = Font.condense + extend = Font.extend + color = Font.color + sz = Font.sz + u = Font.u + vertAlign = Font.vertAlign + scheme = Font.scheme + + __elements__ = ('rFont', 'charset', 'family', 'b', 'i', 'strike', + 'outline', 'shadow', 'condense', 'extend', 'color', 'sz', 'u', + 'vertAlign', 'scheme') + + def __init__(self, + rFont=None, + charset=None, + family=None, + b=None, + i=None, + strike=None, + outline=None, + shadow=None, + condense=None, + extend=None, + color=None, + sz=None, + u=None, + vertAlign=None, + scheme=None, + ): + self.rFont = rFont + self.charset = charset + self.family = family + self.b = b + self.i = i + self.strike = strike + self.outline = outline + self.shadow = shadow + self.condense = condense + self.extend = extend + self.color = color + self.sz = sz + self.u = u + self.vertAlign = vertAlign + self.scheme = scheme + + +class RichText(Serialisable): + + tagname = "RElt" + + rPr = Typed(expected_type=InlineFont, allow_none=True) + font = Alias("rPr") + t = NestedText(expected_type=str, allow_none=True) + text = Alias("t") + + __elements__ = ('rPr', 't') + + def __init__(self, + rPr=None, + t=None, + ): + self.rPr = rPr + self.t = t + + +class Text(Serialisable): + + tagname = "text" + + t = NestedText(allow_none=True, expected_type=str) + plain = Alias("t") + r = Sequence(expected_type=RichText, allow_none=True) + formatted = Alias("r") + rPh = Sequence(expected_type=PhoneticText, allow_none=True) + phonetic = Alias("rPh") + phoneticPr = Typed(expected_type=PhoneticProperties, allow_none=True) + PhoneticProperties = Alias("phoneticPr") + + __elements__ = ('t', 'r', 'rPh', 'phoneticPr') + + def __init__(self, + t=None, + r=(), + rPh=(), + phoneticPr=None, + ): + self.t = t + self.r = r + self.rPh = rPh + self.phoneticPr = phoneticPr + + + @property + def content(self): + """ + Text stripped of all formatting + """ + snippets = [] + if self.plain is not None: + snippets.append(self.plain) + for block in self.formatted: + if block.t is not None: + snippets.append(block.t) + return u"".join(snippets) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py new file mode 100644 index 00000000..1651a993 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py @@ -0,0 +1,105 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import Typed, Alias +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedMinMax, +) +from openpyxl.descriptors.excel import ExtensionList +from .marker import PictureOptions +from .shapes import GraphicalProperties + + +class View3D(Serialisable): + + tagname = "view3D" + + rotX = NestedMinMax(min=-90, max=90, allow_none=True) + x_rotation = Alias('rotX') + hPercent = NestedMinMax(min=5, max=500, allow_none=True) + height_percent = Alias('hPercent') + rotY = NestedInteger(min=-90, max=90, allow_none=True) + y_rotation = Alias('rotY') + depthPercent = NestedInteger(allow_none=True) + rAngAx = NestedBool(allow_none=True) + right_angle_axes = Alias('rAngAx') + perspective = NestedInteger(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('rotX', 'hPercent', 'rotY', 'depthPercent', 'rAngAx', + 'perspective',) + + def __init__(self, + rotX=15, + hPercent=None, + rotY=20, + depthPercent=None, + rAngAx=True, + perspective=None, + extLst=None, + ): + self.rotX = rotX + self.hPercent = hPercent + self.rotY = rotY + self.depthPercent = depthPercent + self.rAngAx = rAngAx + self.perspective = perspective + + +class Surface(Serialisable): + + tagname = "surface" + + thickness = NestedInteger(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + pictureOptions = Typed(expected_type=PictureOptions, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('thickness', 'spPr', 'pictureOptions',) + + def __init__(self, + thickness=None, + spPr=None, + pictureOptions=None, + extLst=None, + ): + self.thickness = thickness + self.spPr = spPr + self.pictureOptions = pictureOptions + + +class _3DBase(Serialisable): + + """ + Base class for 3D charts + """ + + tagname = "ChartBase" + + view3D = Typed(expected_type=View3D, allow_none=True) + floor = Typed(expected_type=Surface, allow_none=True) + sideWall = Typed(expected_type=Surface, allow_none=True) + backWall = Typed(expected_type=Surface, allow_none=True) + + def __init__(self, + view3D=None, + floor=None, + sideWall=None, + backWall=None, + ): + if view3D is None: + view3D = View3D() + self.view3D = view3D + if floor is None: + floor = Surface() + self.floor = floor + if sideWall is None: + sideWall = Surface() + self.sideWall = sideWall + if backWall is None: + backWall = Surface() + self.backWall = backWall + super(_3DBase, self).__init__() diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py new file mode 100644 index 00000000..ecc4d8bf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2010-2024 openpyxl + +from .area_chart import AreaChart, AreaChart3D +from .bar_chart import BarChart, BarChart3D +from .bubble_chart import BubbleChart +from .line_chart import LineChart, LineChart3D +from .pie_chart import ( + PieChart, + PieChart3D, + DoughnutChart, + ProjectedPieChart +) +from .radar_chart import RadarChart +from .scatter_chart import ScatterChart +from .stock_chart import StockChart +from .surface_chart import SurfaceChart, SurfaceChart3D + +from .series_factory import SeriesFactory as Series +from .reference import Reference diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py new file mode 100644 index 00000000..6a613546 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py @@ -0,0 +1,199 @@ +# Copyright (c) 2010-2024 openpyxl + +from collections import OrderedDict +from operator import attrgetter + +from openpyxl.descriptors import ( + Typed, + Integer, + Alias, + MinMax, + Bool, + Set, +) +from openpyxl.descriptors.sequence import ValueSequence +from openpyxl.descriptors.serialisable import Serialisable + +from ._3d import _3DBase +from .data_source import AxDataSource, NumRef +from .layout import Layout +from .legend import Legend +from .reference import Reference +from .series_factory import SeriesFactory +from .series import attribute_mapping +from .shapes import GraphicalProperties +from .title import TitleDescriptor + +class AxId(Serialisable): + + val = Integer() + + def __init__(self, val): + self.val = val + + +def PlotArea(): + from .chartspace import PlotArea + return PlotArea() + + +class ChartBase(Serialisable): + + """ + Base class for all charts + """ + + legend = Typed(expected_type=Legend, allow_none=True) + layout = Typed(expected_type=Layout, allow_none=True) + roundedCorners = Bool(allow_none=True) + axId = ValueSequence(expected_type=int) + visible_cells_only = Bool(allow_none=True) + display_blanks = Set(values=['span', 'gap', 'zero']) + graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True) + + _series_type = "" + ser = () + series = Alias('ser') + title = TitleDescriptor() + anchor = "E15" # default anchor position + width = 15 # in cm, approx 5 rows + height = 7.5 # in cm, approx 14 rows + _id = 1 + _path = "/xl/charts/chart{0}.xml" + style = MinMax(allow_none=True, min=1, max=48) + mime_type = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" + graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True) # mapped to chartspace + + __elements__ = () + + + def __init__(self, axId=(), **kw): + self._charts = [self] + self.title = None + self.layout = None + self.roundedCorners = None + self.legend = Legend() + self.graphical_properties = None + self.style = None + self.plot_area = PlotArea() + self.axId = axId + self.display_blanks = 'gap' + self.pivotSource = None + self.pivotFormats = () + self.visible_cells_only = True + self.idx_base = 0 + self.graphical_properties = None + super().__init__() + + + def __hash__(self): + """ + Just need to check for identity + """ + return id(self) + + def __iadd__(self, other): + """ + Combine the chart with another one + """ + if not isinstance(other, ChartBase): + raise TypeError("Only other charts can be added") + self._charts.append(other) + return self + + + def to_tree(self, namespace=None, tagname=None, idx=None): + self.axId = [id for id in self._axes] + if self.ser is not None: + for s in self.ser: + s.__elements__ = attribute_mapping[self._series_type] + return super().to_tree(tagname, idx) + + + def _reindex(self): + """ + Normalise and rebase series: sort by order and then rebase order + + """ + # sort data series in order and rebase + ds = sorted(self.series, key=attrgetter("order")) + for idx, s in enumerate(ds): + s.order = idx + self.series = ds + + + def _write(self): + from .chartspace import ChartSpace, ChartContainer + self.plot_area.layout = self.layout + + idx_base = self.idx_base + for chart in self._charts: + if chart not in self.plot_area._charts: + chart.idx_base = idx_base + idx_base += len(chart.series) + self.plot_area._charts = self._charts + + container = ChartContainer(plotArea=self.plot_area, legend=self.legend, title=self.title) + if isinstance(chart, _3DBase): + container.view3D = chart.view3D + container.floor = chart.floor + container.sideWall = chart.sideWall + container.backWall = chart.backWall + container.plotVisOnly = self.visible_cells_only + container.dispBlanksAs = self.display_blanks + container.pivotFmts = self.pivotFormats + cs = ChartSpace(chart=container) + cs.style = self.style + cs.roundedCorners = self.roundedCorners + cs.pivotSource = self.pivotSource + cs.spPr = self.graphical_properties + return cs.to_tree() + + + @property + def _axes(self): + x = getattr(self, "x_axis", None) + y = getattr(self, "y_axis", None) + z = getattr(self, "z_axis", None) + return OrderedDict([(axis.axId, axis) for axis in (x, y, z) if axis]) + + + def set_categories(self, labels): + """ + Set the categories / x-axis values + """ + if not isinstance(labels, Reference): + labels = Reference(range_string=labels) + for s in self.ser: + s.cat = AxDataSource(numRef=NumRef(f=labels)) + + + def add_data(self, data, from_rows=False, titles_from_data=False): + """ + Add a range of data in a single pass. + The default is to treat each column as a data series. + """ + if not isinstance(data, Reference): + data = Reference(range_string=data) + + if from_rows: + values = data.rows + + else: + values = data.cols + + for ref in values: + series = SeriesFactory(ref, title_from_data=titles_from_data) + self.series.append(series) + + + def append(self, value): + """Append a data series to the chart""" + l = self.series[:] + l.append(value) + self.series = l + + + @property + def path(self): + return self._path.format(self._id) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py new file mode 100644 index 00000000..d3d98085 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py @@ -0,0 +1,106 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Set, + Bool, + Integer, + Sequence, + Alias, +) + +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedMinMax, + NestedSet, + NestedBool, +) + +from ._chart import ChartBase +from .descriptors import NestedGapAmount +from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines +from .label import DataLabelList +from .series import Series + + +class _AreaChartBase(ChartBase): + + grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked'])) + varyColors = NestedBool(nested=True, allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + dropLines = Typed(expected_type=ChartLines, allow_none=True) + + _series_type = "area" + + __elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines') + + def __init__(self, + grouping="standard", + varyColors=None, + ser=(), + dLbls=None, + dropLines=None, + ): + self.grouping = grouping + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.dropLines = dropLines + super().__init__() + + +class AreaChart(_AreaChartBase): + + tagname = "areaChart" + + grouping = _AreaChartBase.grouping + varyColors = _AreaChartBase.varyColors + ser = _AreaChartBase.ser + dLbls = _AreaChartBase.dLbls + dropLines = _AreaChartBase.dropLines + + # chart properties actually used by containing classes + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _AreaChartBase.__elements__ + ('axId',) + + def __init__(self, + axId=None, + extLst=None, + **kw + ): + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + super().__init__(**kw) + + +class AreaChart3D(AreaChart): + + tagname = "area3DChart" + + grouping = _AreaChartBase.grouping + varyColors = _AreaChartBase.varyColors + ser = _AreaChartBase.ser + dLbls = _AreaChartBase.dLbls + dropLines = _AreaChartBase.dropLines + + gapDepth = NestedGapAmount() + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + z_axis = Typed(expected_type=SeriesAxis, allow_none=True) + + __elements__ = AreaChart.__elements__ + ('gapDepth', ) + + def __init__(self, gapDepth=None, **kw): + self.gapDepth = gapDepth + super(AreaChart3D, self).__init__(**kw) + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.z_axis = SeriesAxis() diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/axis.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/axis.py new file mode 100644 index 00000000..7e99416c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/axis.py @@ -0,0 +1,401 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + NoneSet, + Bool, + Integer, + MinMax, + NoneSet, + Set, + String, + Alias, +) + +from openpyxl.descriptors.excel import ( + ExtensionList, + Percentage, + _explicit_none, +) +from openpyxl.descriptors.nested import ( + NestedValue, + NestedSet, + NestedBool, + NestedNoneSet, + NestedFloat, + NestedInteger, + NestedMinMax, +) +from openpyxl.xml.constants import CHART_NS + +from .descriptors import NumberFormatDescriptor +from .layout import Layout +from .text import Text, RichText +from .shapes import GraphicalProperties +from .title import Title, TitleDescriptor + + +class ChartLines(Serialisable): + + tagname = "chartLines" + + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + + def __init__(self, spPr=None): + self.spPr = spPr + + +class Scaling(Serialisable): + + tagname = "scaling" + + logBase = NestedFloat(allow_none=True) + orientation = NestedSet(values=(['maxMin', 'minMax'])) + max = NestedFloat(allow_none=True) + min = NestedFloat(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('logBase', 'orientation', 'max', 'min',) + + def __init__(self, + logBase=None, + orientation="minMax", + max=None, + min=None, + extLst=None, + ): + self.logBase = logBase + self.orientation = orientation + self.max = max + self.min = min + + +class _BaseAxis(Serialisable): + + axId = NestedInteger(expected_type=int) + scaling = Typed(expected_type=Scaling) + delete = NestedBool(allow_none=True) + axPos = NestedSet(values=(['b', 'l', 'r', 't'])) + majorGridlines = Typed(expected_type=ChartLines, allow_none=True) + minorGridlines = Typed(expected_type=ChartLines, allow_none=True) + title = TitleDescriptor() + numFmt = NumberFormatDescriptor() + number_format = Alias("numFmt") + majorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none) + minorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none) + tickLblPos = NestedNoneSet(values=(['high', 'low', 'nextTo'])) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias('txPr') + crossAx = NestedInteger(expected_type=int) # references other axis + crosses = NestedNoneSet(values=(['autoZero', 'max', 'min'])) + crossesAt = NestedFloat(allow_none=True) + + # crosses & crossesAt are mutually exclusive + + __elements__ = ('axId', 'scaling', 'delete', 'axPos', 'majorGridlines', + 'minorGridlines', 'title', 'numFmt', 'majorTickMark', 'minorTickMark', + 'tickLblPos', 'spPr', 'txPr', 'crossAx', 'crosses', 'crossesAt') + + def __init__(self, + axId=None, + scaling=None, + delete=None, + axPos='l', + majorGridlines=None, + minorGridlines=None, + title=None, + numFmt=None, + majorTickMark=None, + minorTickMark=None, + tickLblPos=None, + spPr=None, + txPr= None, + crossAx=None, + crosses=None, + crossesAt=None, + ): + self.axId = axId + if scaling is None: + scaling = Scaling() + self.scaling = scaling + self.delete = delete + self.axPos = axPos + self.majorGridlines = majorGridlines + self.minorGridlines = minorGridlines + self.title = title + self.numFmt = numFmt + self.majorTickMark = majorTickMark + self.minorTickMark = minorTickMark + self.tickLblPos = tickLblPos + self.spPr = spPr + self.txPr = txPr + self.crossAx = crossAx + self.crosses = crosses + self.crossesAt = crossesAt + + +class DisplayUnitsLabel(Serialisable): + + tagname = "dispUnitsLbl" + + layout = Typed(expected_type=Layout, allow_none=True) + tx = Typed(expected_type=Text, allow_none=True) + text = Alias("tx") + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + txPr = Typed(expected_type=RichText, allow_none=True) + textPropertes = Alias("txPr") + + __elements__ = ('layout', 'tx', 'spPr', 'txPr') + + def __init__(self, + layout=None, + tx=None, + spPr=None, + txPr=None, + ): + self.layout = layout + self.tx = tx + self.spPr = spPr + self.txPr = txPr + + +class DisplayUnitsLabelList(Serialisable): + + tagname = "dispUnits" + + custUnit = NestedFloat(allow_none=True) + builtInUnit = NestedNoneSet(values=(['hundreds', 'thousands', + 'tenThousands', 'hundredThousands', 'millions', 'tenMillions', + 'hundredMillions', 'billions', 'trillions'])) + dispUnitsLbl = Typed(expected_type=DisplayUnitsLabel, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('custUnit', 'builtInUnit', 'dispUnitsLbl',) + + def __init__(self, + custUnit=None, + builtInUnit=None, + dispUnitsLbl=None, + extLst=None, + ): + self.custUnit = custUnit + self.builtInUnit = builtInUnit + self.dispUnitsLbl = dispUnitsLbl + + +class NumericAxis(_BaseAxis): + + tagname = "valAx" + + axId = _BaseAxis.axId + scaling = _BaseAxis.scaling + delete = _BaseAxis.delete + axPos = _BaseAxis.axPos + majorGridlines = _BaseAxis.majorGridlines + minorGridlines = _BaseAxis.minorGridlines + title = _BaseAxis.title + numFmt = _BaseAxis.numFmt + majorTickMark = _BaseAxis.majorTickMark + minorTickMark = _BaseAxis.minorTickMark + tickLblPos = _BaseAxis.tickLblPos + spPr = _BaseAxis.spPr + txPr = _BaseAxis.txPr + crossAx = _BaseAxis.crossAx + crosses = _BaseAxis.crosses + crossesAt = _BaseAxis.crossesAt + + crossBetween = NestedNoneSet(values=(['between', 'midCat'])) + majorUnit = NestedFloat(allow_none=True) + minorUnit = NestedFloat(allow_none=True) + dispUnits = Typed(expected_type=DisplayUnitsLabelList, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _BaseAxis.__elements__ + ('crossBetween', 'majorUnit', + 'minorUnit', 'dispUnits',) + + + def __init__(self, + crossBetween=None, + majorUnit=None, + minorUnit=None, + dispUnits=None, + extLst=None, + **kw + ): + self.crossBetween = crossBetween + self.majorUnit = majorUnit + self.minorUnit = minorUnit + self.dispUnits = dispUnits + kw.setdefault('majorGridlines', ChartLines()) + kw.setdefault('axId', 100) + kw.setdefault('crossAx', 10) + super().__init__(**kw) + + + @classmethod + def from_tree(cls, node): + """ + Special case value axes with no gridlines + """ + self = super().from_tree(node) + gridlines = node.find("{%s}majorGridlines" % CHART_NS) + if gridlines is None: + self.majorGridlines = None + return self + + + +class TextAxis(_BaseAxis): + + tagname = "catAx" + + axId = _BaseAxis.axId + scaling = _BaseAxis.scaling + delete = _BaseAxis.delete + axPos = _BaseAxis.axPos + majorGridlines = _BaseAxis.majorGridlines + minorGridlines = _BaseAxis.minorGridlines + title = _BaseAxis.title + numFmt = _BaseAxis.numFmt + majorTickMark = _BaseAxis.majorTickMark + minorTickMark = _BaseAxis.minorTickMark + tickLblPos = _BaseAxis.tickLblPos + spPr = _BaseAxis.spPr + txPr = _BaseAxis.txPr + crossAx = _BaseAxis.crossAx + crosses = _BaseAxis.crosses + crossesAt = _BaseAxis.crossesAt + + auto = NestedBool(allow_none=True) + lblAlgn = NestedNoneSet(values=(['ctr', 'l', 'r'])) + lblOffset = NestedMinMax(min=0, max=1000) + tickLblSkip = NestedInteger(allow_none=True) + tickMarkSkip = NestedInteger(allow_none=True) + noMultiLvlLbl = NestedBool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _BaseAxis.__elements__ + ('auto', 'lblAlgn', 'lblOffset', + 'tickLblSkip', 'tickMarkSkip', 'noMultiLvlLbl') + + def __init__(self, + auto=None, + lblAlgn=None, + lblOffset=100, + tickLblSkip=None, + tickMarkSkip=None, + noMultiLvlLbl=None, + extLst=None, + **kw + ): + self.auto = auto + self.lblAlgn = lblAlgn + self.lblOffset = lblOffset + self.tickLblSkip = tickLblSkip + self.tickMarkSkip = tickMarkSkip + self.noMultiLvlLbl = noMultiLvlLbl + kw.setdefault('axId', 10) + kw.setdefault('crossAx', 100) + super().__init__(**kw) + + +class DateAxis(TextAxis): + + tagname = "dateAx" + + axId = _BaseAxis.axId + scaling = _BaseAxis.scaling + delete = _BaseAxis.delete + axPos = _BaseAxis.axPos + majorGridlines = _BaseAxis.majorGridlines + minorGridlines = _BaseAxis.minorGridlines + title = _BaseAxis.title + numFmt = _BaseAxis.numFmt + majorTickMark = _BaseAxis.majorTickMark + minorTickMark = _BaseAxis.minorTickMark + tickLblPos = _BaseAxis.tickLblPos + spPr = _BaseAxis.spPr + txPr = _BaseAxis.txPr + crossAx = _BaseAxis.crossAx + crosses = _BaseAxis.crosses + crossesAt = _BaseAxis.crossesAt + + auto = NestedBool(allow_none=True) + lblOffset = NestedInteger(allow_none=True) + baseTimeUnit = NestedNoneSet(values=(['days', 'months', 'years'])) + majorUnit = NestedFloat(allow_none=True) + majorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years'])) + minorUnit = NestedFloat(allow_none=True) + minorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years'])) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _BaseAxis.__elements__ + ('auto', 'lblOffset', + 'baseTimeUnit', 'majorUnit', 'majorTimeUnit', 'minorUnit', + 'minorTimeUnit') + + def __init__(self, + auto=None, + lblOffset=None, + baseTimeUnit=None, + majorUnit=None, + majorTimeUnit=None, + minorUnit=None, + minorTimeUnit=None, + extLst=None, + **kw + ): + self.auto = auto + self.lblOffset = lblOffset + self.baseTimeUnit = baseTimeUnit + self.majorUnit = majorUnit + self.majorTimeUnit = majorTimeUnit + self.minorUnit = minorUnit + self.minorTimeUnit = minorTimeUnit + kw.setdefault('axId', 500) + kw.setdefault('lblOffset', lblOffset) + super().__init__(**kw) + + +class SeriesAxis(_BaseAxis): + + tagname = "serAx" + + axId = _BaseAxis.axId + scaling = _BaseAxis.scaling + delete = _BaseAxis.delete + axPos = _BaseAxis.axPos + majorGridlines = _BaseAxis.majorGridlines + minorGridlines = _BaseAxis.minorGridlines + title = _BaseAxis.title + numFmt = _BaseAxis.numFmt + majorTickMark = _BaseAxis.majorTickMark + minorTickMark = _BaseAxis.minorTickMark + tickLblPos = _BaseAxis.tickLblPos + spPr = _BaseAxis.spPr + txPr = _BaseAxis.txPr + crossAx = _BaseAxis.crossAx + crosses = _BaseAxis.crosses + crossesAt = _BaseAxis.crossesAt + + tickLblSkip = NestedInteger(allow_none=True) + tickMarkSkip = NestedInteger(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _BaseAxis.__elements__ + ('tickLblSkip', 'tickMarkSkip') + + def __init__(self, + tickLblSkip=None, + tickMarkSkip=None, + extLst=None, + **kw + ): + self.tickLblSkip = tickLblSkip + self.tickMarkSkip = tickMarkSkip + kw.setdefault('axId', 1000) + kw.setdefault('crossAx', 10) + super().__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py new file mode 100644 index 00000000..fa08e076 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py @@ -0,0 +1,144 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Integer, + Sequence, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedSet, + NestedBool, + NestedInteger, + NestedMinMax, +) + +from .descriptors import ( + NestedGapAmount, + NestedOverlap, +) +from ._chart import ChartBase +from ._3d import _3DBase +from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines +from .shapes import GraphicalProperties +from .series import Series +from .legend import Legend +from .label import DataLabelList + + +class _BarChartBase(ChartBase): + + barDir = NestedSet(values=(['bar', 'col'])) + type = Alias("barDir") + grouping = NestedSet(values=(['percentStacked', 'clustered', 'standard', + 'stacked'])) + varyColors = NestedBool(nested=True, allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + + __elements__ = ('barDir', 'grouping', 'varyColors', 'ser', 'dLbls') + + _series_type = "bar" + + def __init__(self, + barDir="col", + grouping="clustered", + varyColors=None, + ser=(), + dLbls=None, + **kw + ): + self.barDir = barDir + self.grouping = grouping + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + super().__init__(**kw) + + +class BarChart(_BarChartBase): + + tagname = "barChart" + + barDir = _BarChartBase.barDir + grouping = _BarChartBase.grouping + varyColors = _BarChartBase.varyColors + ser = _BarChartBase.ser + dLbls = _BarChartBase.dLbls + + gapWidth = NestedGapAmount() + overlap = NestedOverlap() + serLines = Typed(expected_type=ChartLines, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + # chart properties actually used by containing classes + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + + __elements__ = _BarChartBase.__elements__ + ('gapWidth', 'overlap', 'serLines', 'axId') + + def __init__(self, + gapWidth=150, + overlap=None, + serLines=None, + extLst=None, + **kw + ): + self.gapWidth = gapWidth + self.overlap = overlap + self.serLines = serLines + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.legend = Legend() + super().__init__(**kw) + + +class BarChart3D(_BarChartBase, _3DBase): + + tagname = "bar3DChart" + + barDir = _BarChartBase.barDir + grouping = _BarChartBase.grouping + varyColors = _BarChartBase.varyColors + ser = _BarChartBase.ser + dLbls = _BarChartBase.dLbls + + view3D = _3DBase.view3D + floor = _3DBase.floor + sideWall = _3DBase.sideWall + backWall = _3DBase.backWall + + gapWidth = NestedGapAmount() + gapDepth = NestedGapAmount() + shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax'])) + serLines = Typed(expected_type=ChartLines, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + z_axis = Typed(expected_type=SeriesAxis, allow_none=True) + + __elements__ = _BarChartBase.__elements__ + ('gapWidth', 'gapDepth', 'shape', 'serLines', 'axId') + + def __init__(self, + gapWidth=150, + gapDepth=150, + shape=None, + serLines=None, + extLst=None, + **kw + ): + self.gapWidth = gapWidth + self.gapDepth = gapDepth + self.shape = shape + self.serLines = serLines + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.z_axis = SeriesAxis() + + super(BarChart3D, self).__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py new file mode 100644 index 00000000..3fca043a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py @@ -0,0 +1,67 @@ +#Autogenerated schema +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Set, + MinMax, + Bool, + Integer, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedMinMax, + NestedBool, +) + +from ._chart import ChartBase +from .axis import TextAxis, NumericAxis +from .series import XYSeries +from .label import DataLabelList + + +class BubbleChart(ChartBase): + + tagname = "bubbleChart" + + varyColors = NestedBool(allow_none=True) + ser = Sequence(expected_type=XYSeries, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + bubble3D = NestedBool(allow_none=True) + bubbleScale = NestedMinMax(min=0, max=300, allow_none=True) + showNegBubbles = NestedBool(allow_none=True) + sizeRepresents = NestedNoneSet(values=(['area', 'w'])) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=NumericAxis) + y_axis = Typed(expected_type=NumericAxis) + + _series_type = "bubble" + + __elements__ = ('varyColors', 'ser', 'dLbls', 'bubble3D', 'bubbleScale', + 'showNegBubbles', 'sizeRepresents', 'axId') + + def __init__(self, + varyColors=None, + ser=(), + dLbls=None, + bubble3D=None, + bubbleScale=None, + showNegBubbles=None, + sizeRepresents=None, + extLst=None, + **kw + ): + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.bubble3D = bubble3D + self.bubbleScale = bubbleScale + self.showNegBubbles = showNegBubbles + self.sizeRepresents = sizeRepresents + self.x_axis = NumericAxis(axId=10, crossAx=20) + self.y_axis = NumericAxis(axId=20, crossAx=10) + super().__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py new file mode 100644 index 00000000..cba213c2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py @@ -0,0 +1,195 @@ + +# Copyright (c) 2010-2024 openpyxl + +""" +Enclosing chart object. The various chart types are actually child objects. +Will probably need to call this indirectly +""" + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Alias, +) +from openpyxl.descriptors.excel import ( + ExtensionList, + Relation +) +from openpyxl.descriptors.nested import ( + NestedBool, + NestedNoneSet, + NestedString, + NestedMinMax, +) +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.xml.constants import CHART_NS + +from openpyxl.drawing.colors import ColorMapping +from .text import RichText +from .shapes import GraphicalProperties +from .legend import Legend +from ._3d import _3DBase +from .plotarea import PlotArea +from .title import Title +from .pivot import ( + PivotFormat, + PivotSource, +) +from .print_settings import PrintSettings + + +class ChartContainer(Serialisable): + + tagname = "chart" + + title = Typed(expected_type=Title, allow_none=True) + autoTitleDeleted = NestedBool(allow_none=True) + pivotFmts = NestedSequence(expected_type=PivotFormat) + view3D = _3DBase.view3D + floor = _3DBase.floor + sideWall = _3DBase.sideWall + backWall = _3DBase.backWall + plotArea = Typed(expected_type=PlotArea, ) + legend = Typed(expected_type=Legend, allow_none=True) + plotVisOnly = NestedBool() + dispBlanksAs = NestedNoneSet(values=(['span', 'gap', 'zero'])) + showDLblsOverMax = NestedBool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('title', 'autoTitleDeleted', 'pivotFmts', 'view3D', + 'floor', 'sideWall', 'backWall', 'plotArea', 'legend', 'plotVisOnly', + 'dispBlanksAs', 'showDLblsOverMax') + + def __init__(self, + title=None, + autoTitleDeleted=None, + pivotFmts=(), + view3D=None, + floor=None, + sideWall=None, + backWall=None, + plotArea=None, + legend=None, + plotVisOnly=True, + dispBlanksAs="gap", + showDLblsOverMax=None, + extLst=None, + ): + self.title = title + self.autoTitleDeleted = autoTitleDeleted + self.pivotFmts = pivotFmts + self.view3D = view3D + self.floor = floor + self.sideWall = sideWall + self.backWall = backWall + if plotArea is None: + plotArea = PlotArea() + self.plotArea = plotArea + self.legend = legend + self.plotVisOnly = plotVisOnly + self.dispBlanksAs = dispBlanksAs + self.showDLblsOverMax = showDLblsOverMax + + +class Protection(Serialisable): + + tagname = "protection" + + chartObject = NestedBool(allow_none=True) + data = NestedBool(allow_none=True) + formatting = NestedBool(allow_none=True) + selection = NestedBool(allow_none=True) + userInterface = NestedBool(allow_none=True) + + __elements__ = ("chartObject", "data", "formatting", "selection", "userInterface") + + def __init__(self, + chartObject=None, + data=None, + formatting=None, + selection=None, + userInterface=None, + ): + self.chartObject = chartObject + self.data = data + self.formatting = formatting + self.selection = selection + self.userInterface = userInterface + + +class ExternalData(Serialisable): + + tagname = "externalData" + + autoUpdate = NestedBool(allow_none=True) + id = String() # Needs namespace + + def __init__(self, + autoUpdate=None, + id=None + ): + self.autoUpdate = autoUpdate + self.id = id + + +class ChartSpace(Serialisable): + + tagname = "chartSpace" + + date1904 = NestedBool(allow_none=True) + lang = NestedString(allow_none=True) + roundedCorners = NestedBool(allow_none=True) + style = NestedMinMax(allow_none=True, min=1, max=48) + clrMapOvr = Typed(expected_type=ColorMapping, allow_none=True) + pivotSource = Typed(expected_type=PivotSource, allow_none=True) + protection = Typed(expected_type=Protection, allow_none=True) + chart = Typed(expected_type=ChartContainer) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphical_properties = Alias("spPr") + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias("txPr") + externalData = Typed(expected_type=ExternalData, allow_none=True) + printSettings = Typed(expected_type=PrintSettings, allow_none=True) + userShapes = Relation() + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('date1904', 'lang', 'roundedCorners', 'style', + 'clrMapOvr', 'pivotSource', 'protection', 'chart', 'spPr', 'txPr', + 'externalData', 'printSettings', 'userShapes') + + def __init__(self, + date1904=None, + lang=None, + roundedCorners=None, + style=None, + clrMapOvr=None, + pivotSource=None, + protection=None, + chart=None, + spPr=None, + txPr=None, + externalData=None, + printSettings=None, + userShapes=None, + extLst=None, + ): + self.date1904 = date1904 + self.lang = lang + self.roundedCorners = roundedCorners + self.style = style + self.clrMapOvr = clrMapOvr + self.pivotSource = pivotSource + self.protection = protection + self.chart = chart + self.spPr = spPr + self.txPr = txPr + self.externalData = externalData + self.printSettings = printSettings + self.userShapes = userShapes + + + def to_tree(self, tagname=None, idx=None, namespace=None): + tree = super().to_tree() + tree.set("xmlns", CHART_NS) + return tree diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py new file mode 100644 index 00000000..c38eafb2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py @@ -0,0 +1,246 @@ +""" +Collection of utility primitives for charts. +""" + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Bool, + Typed, + Alias, + String, + Integer, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedString, + NestedText, + NestedInteger, +) + + +class NumFmt(Serialisable): + + formatCode = String() + sourceLinked = Bool() + + def __init__(self, + formatCode=None, + sourceLinked=False + ): + self.formatCode = formatCode + self.sourceLinked = sourceLinked + + +class NumberValueDescriptor(NestedText): + """ + Data should be numerical but isn't always :-/ + """ + + allow_none = True + + def __set__(self, instance, value): + if value == "#N/A": + self.expected_type = str + else: + self.expected_type = float + super().__set__(instance, value) + + +class NumVal(Serialisable): + + idx = Integer() + formatCode = NestedText(allow_none=True, expected_type=str) + v = NumberValueDescriptor() + + def __init__(self, + idx=None, + formatCode=None, + v=None, + ): + self.idx = idx + self.formatCode = formatCode + self.v = v + + +class NumData(Serialisable): + + formatCode = NestedText(expected_type=str, allow_none=True) + ptCount = NestedInteger(allow_none=True) + pt = Sequence(expected_type=NumVal) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('formatCode', 'ptCount', 'pt') + + def __init__(self, + formatCode=None, + ptCount=None, + pt=(), + extLst=None, + ): + self.formatCode = formatCode + self.ptCount = ptCount + self.pt = pt + + +class NumRef(Serialisable): + + f = NestedText(expected_type=str) + ref = Alias('f') + numCache = Typed(expected_type=NumData, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('f', 'numCache') + + def __init__(self, + f=None, + numCache=None, + extLst=None, + ): + self.f = f + self.numCache = numCache + + +class StrVal(Serialisable): + + tagname = "strVal" + + idx = Integer() + v = NestedText(expected_type=str) + + def __init__(self, + idx=0, + v=None, + ): + self.idx = idx + self.v = v + + +class StrData(Serialisable): + + tagname = "strData" + + ptCount = NestedInteger(allow_none=True) + pt = Sequence(expected_type=StrVal) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('ptCount', 'pt') + + def __init__(self, + ptCount=None, + pt=(), + extLst=None, + ): + self.ptCount = ptCount + self.pt = pt + + +class StrRef(Serialisable): + + tagname = "strRef" + + f = NestedText(expected_type=str, allow_none=True) + strCache = Typed(expected_type=StrData, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('f', 'strCache') + + def __init__(self, + f=None, + strCache=None, + extLst=None, + ): + self.f = f + self.strCache = strCache + + +class NumDataSource(Serialisable): + + numRef = Typed(expected_type=NumRef, allow_none=True) + numLit = Typed(expected_type=NumData, allow_none=True) + + + def __init__(self, + numRef=None, + numLit=None, + ): + self.numRef = numRef + self.numLit = numLit + + +class Level(Serialisable): + + tagname = "lvl" + + pt = Sequence(expected_type=StrVal) + + __elements__ = ('pt',) + + def __init__(self, + pt=(), + ): + self.pt = pt + + +class MultiLevelStrData(Serialisable): + + tagname = "multiLvlStrData" + + ptCount = Integer(allow_none=True) + lvl = Sequence(expected_type=Level) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('ptCount', 'lvl',) + + def __init__(self, + ptCount=None, + lvl=(), + extLst=None, + ): + self.ptCount = ptCount + self.lvl = lvl + + +class MultiLevelStrRef(Serialisable): + + tagname = "multiLvlStrRef" + + f = NestedText(expected_type=str) + multiLvlStrCache = Typed(expected_type=MultiLevelStrData, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('multiLvlStrCache', 'f') + + def __init__(self, + f=None, + multiLvlStrCache=None, + extLst=None, + ): + self.f = f + self.multiLvlStrCache = multiLvlStrCache + + +class AxDataSource(Serialisable): + + tagname = "cat" + + numRef = Typed(expected_type=NumRef, allow_none=True) + numLit = Typed(expected_type=NumData, allow_none=True) + strRef = Typed(expected_type=StrRef, allow_none=True) + strLit = Typed(expected_type=StrData, allow_none=True) + multiLvlStrRef = Typed(expected_type=MultiLevelStrRef, allow_none=True) + + def __init__(self, + numRef=None, + numLit=None, + strRef=None, + strLit=None, + multiLvlStrRef=None, + ): + if not any([numLit, numRef, strRef, strLit, multiLvlStrRef]): + raise TypeError("A data source must be provided") + self.numRef = numRef + self.numLit = numLit + self.strRef = strRef + self.strLit = strLit + self.multiLvlStrRef = multiLvlStrRef diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py new file mode 100644 index 00000000..6bc94348 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py @@ -0,0 +1,43 @@ +# Copyright (c) 2010-2024 openpyxl + + + +from openpyxl.descriptors.nested import ( + NestedMinMax + ) + +from openpyxl.descriptors import Typed + +from .data_source import NumFmt + +""" +Utility descriptors for the chart module. +For convenience but also clarity. +""" + +class NestedGapAmount(NestedMinMax): + + allow_none = True + min = 0 + max = 500 + + +class NestedOverlap(NestedMinMax): + + allow_none = True + min = -100 + max = 100 + + +class NumberFormatDescriptor(Typed): + """ + Allow direct assignment of format code + """ + + expected_type = NumFmt + allow_none = True + + def __set__(self, instance, value): + if isinstance(value, str): + value = NumFmt(value) + super().__set__(instance, value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py new file mode 100644 index 00000000..6ae24451 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py @@ -0,0 +1,62 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + Set, + Alias +) + +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedSet, + NestedBool, + NestedFloat, +) + +from .data_source import NumDataSource +from .shapes import GraphicalProperties + + +class ErrorBars(Serialisable): + + tagname = "errBars" + + errDir = NestedNoneSet(values=(['x', 'y'])) + direction = Alias("errDir") + errBarType = NestedSet(values=(['both', 'minus', 'plus'])) + style = Alias("errBarType") + errValType = NestedSet(values=(['cust', 'fixedVal', 'percentage', 'stdDev', 'stdErr'])) + size = Alias("errValType") + noEndCap = NestedBool(nested=True, allow_none=True) + plus = Typed(expected_type=NumDataSource, allow_none=True) + minus = Typed(expected_type=NumDataSource, allow_none=True) + val = NestedFloat(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('errDir','errBarType', 'errValType', 'noEndCap','minus', 'plus', 'val', 'spPr') + + + def __init__(self, + errDir=None, + errBarType="both", + errValType="fixedVal", + noEndCap=None, + plus=None, + minus=None, + val=None, + spPr=None, + extLst=None, + ): + self.errDir = errDir + self.errBarType = errBarType + self.errValType = errValType + self.noEndCap = noEndCap + self.plus = plus + self.minus = minus + self.val = val + self.spPr = spPr diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/label.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/label.py new file mode 100644 index 00000000..d6eacb16 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/label.py @@ -0,0 +1,127 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence, + Alias, + Typed +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedBool, + NestedString, + NestedInteger, + ) + +from .shapes import GraphicalProperties +from .text import RichText + + +class _DataLabelBase(Serialisable): + + numFmt = NestedString(allow_none=True, attribute="formatCode") + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias('txPr') + dLblPos = NestedNoneSet(values=['bestFit', 'b', 'ctr', 'inBase', 'inEnd', + 'l', 'outEnd', 'r', 't']) + position = Alias('dLblPos') + showLegendKey = NestedBool(allow_none=True) + showVal = NestedBool(allow_none=True) + showCatName = NestedBool(allow_none=True) + showSerName = NestedBool(allow_none=True) + showPercent = NestedBool(allow_none=True) + showBubbleSize = NestedBool(allow_none=True) + showLeaderLines = NestedBool(allow_none=True) + separator = NestedString(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ("numFmt", "spPr", "txPr", "dLblPos", "showLegendKey", + "showVal", "showCatName", "showSerName", "showPercent", "showBubbleSize", + "showLeaderLines", "separator") + + def __init__(self, + numFmt=None, + spPr=None, + txPr=None, + dLblPos=None, + showLegendKey=None, + showVal=None, + showCatName=None, + showSerName=None, + showPercent=None, + showBubbleSize=None, + showLeaderLines=None, + separator=None, + extLst=None, + ): + self.numFmt = numFmt + self.spPr = spPr + self.txPr = txPr + self.dLblPos = dLblPos + self.showLegendKey = showLegendKey + self.showVal = showVal + self.showCatName = showCatName + self.showSerName = showSerName + self.showPercent = showPercent + self.showBubbleSize = showBubbleSize + self.showLeaderLines = showLeaderLines + self.separator = separator + + +class DataLabel(_DataLabelBase): + + tagname = "dLbl" + + idx = NestedInteger() + + numFmt = _DataLabelBase.numFmt + spPr = _DataLabelBase.spPr + txPr = _DataLabelBase.txPr + dLblPos = _DataLabelBase.dLblPos + showLegendKey = _DataLabelBase.showLegendKey + showVal = _DataLabelBase.showVal + showCatName = _DataLabelBase.showCatName + showSerName = _DataLabelBase.showSerName + showPercent = _DataLabelBase.showPercent + showBubbleSize = _DataLabelBase.showBubbleSize + showLeaderLines = _DataLabelBase.showLeaderLines + separator = _DataLabelBase.separator + extLst = _DataLabelBase.extLst + + __elements__ = ("idx",) + _DataLabelBase.__elements__ + + def __init__(self, idx=0, **kw ): + self.idx = idx + super().__init__(**kw) + + +class DataLabelList(_DataLabelBase): + + tagname = "dLbls" + + dLbl = Sequence(expected_type=DataLabel, allow_none=True) + + delete = NestedBool(allow_none=True) + numFmt = _DataLabelBase.numFmt + spPr = _DataLabelBase.spPr + txPr = _DataLabelBase.txPr + dLblPos = _DataLabelBase.dLblPos + showLegendKey = _DataLabelBase.showLegendKey + showVal = _DataLabelBase.showVal + showCatName = _DataLabelBase.showCatName + showSerName = _DataLabelBase.showSerName + showPercent = _DataLabelBase.showPercent + showBubbleSize = _DataLabelBase.showBubbleSize + showLeaderLines = _DataLabelBase.showLeaderLines + separator = _DataLabelBase.separator + extLst = _DataLabelBase.extLst + + __elements__ = ("delete", "dLbl",) + _DataLabelBase.__elements__ + + def __init__(self, dLbl=(), delete=None, **kw): + self.dLbl = dLbl + self.delete = delete + super().__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/layout.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/layout.py new file mode 100644 index 00000000..f2f65530 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/layout.py @@ -0,0 +1,74 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + NoneSet, + Float, + Typed, + Alias, +) + +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedSet, + NestedMinMax, +) + +class ManualLayout(Serialisable): + + tagname = "manualLayout" + + layoutTarget = NestedNoneSet(values=(['inner', 'outer'])) + xMode = NestedNoneSet(values=(['edge', 'factor'])) + yMode = NestedNoneSet(values=(['edge', 'factor'])) + wMode = NestedSet(values=(['edge', 'factor'])) + hMode = NestedSet(values=(['edge', 'factor'])) + x = NestedMinMax(min=-1, max=1, allow_none=True) + y = NestedMinMax(min=-1, max=1, allow_none=True) + w = NestedMinMax(min=0, max=1, allow_none=True) + width = Alias('w') + h = NestedMinMax(min=0, max=1, allow_none=True) + height = Alias('h') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('layoutTarget', 'xMode', 'yMode', 'wMode', 'hMode', 'x', + 'y', 'w', 'h') + + def __init__(self, + layoutTarget=None, + xMode=None, + yMode=None, + wMode="factor", + hMode="factor", + x=None, + y=None, + w=None, + h=None, + extLst=None, + ): + self.layoutTarget = layoutTarget + self.xMode = xMode + self.yMode = yMode + self.wMode = wMode + self.hMode = hMode + self.x = x + self.y = y + self.w = w + self.h = h + + +class Layout(Serialisable): + + tagname = "layout" + + manualLayout = Typed(expected_type=ManualLayout, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('manualLayout',) + + def __init__(self, + manualLayout=None, + extLst=None, + ): + self.manualLayout = manualLayout diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/legend.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/legend.py new file mode 100644 index 00000000..1f7c802b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/legend.py @@ -0,0 +1,75 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedBool, + NestedSet, + NestedInteger +) + +from .layout import Layout +from .shapes import GraphicalProperties +from .text import RichText + + +class LegendEntry(Serialisable): + + tagname = "legendEntry" + + idx = NestedInteger() + delete = NestedBool() + txPr = Typed(expected_type=RichText, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('idx', 'delete', 'txPr') + + def __init__(self, + idx=0, + delete=False, + txPr=None, + extLst=None, + ): + self.idx = idx + self.delete = delete + self.txPr = txPr + + +class Legend(Serialisable): + + tagname = "legend" + + legendPos = NestedSet(values=(['b', 'tr', 'l', 'r', 't'])) + position = Alias('legendPos') + legendEntry = Sequence(expected_type=LegendEntry) + layout = Typed(expected_type=Layout, allow_none=True) + overlay = NestedBool(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias('txPr') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('legendPos', 'legendEntry', 'layout', 'overlay', 'spPr', 'txPr',) + + def __init__(self, + legendPos="r", + legendEntry=(), + layout=None, + overlay=None, + spPr=None, + txPr=None, + extLst=None, + ): + self.legendPos = legendPos + self.legendEntry = legendEntry + self.layout = layout + self.overlay = overlay + self.spPr = spPr + self.txPr = txPr diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py new file mode 100644 index 00000000..0aa3ad5b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py @@ -0,0 +1,129 @@ +#Autogenerated schema +from openpyxl.descriptors import ( + Typed, + Sequence, + Alias, + ) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedSet, + NestedBool, +) + +from ._chart import ChartBase +from .updown_bars import UpDownBars +from .descriptors import NestedGapAmount +from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines, _BaseAxis +from .label import DataLabelList +from .series import Series + + +class _LineChartBase(ChartBase): + + grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked'])) + varyColors = NestedBool(allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + dropLines = Typed(expected_type=ChartLines, allow_none=True) + + _series_type = "line" + + __elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines') + + def __init__(self, + grouping="standard", + varyColors=None, + ser=(), + dLbls=None, + dropLines=None, + **kw + ): + self.grouping = grouping + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.dropLines = dropLines + super().__init__(**kw) + + +class LineChart(_LineChartBase): + + tagname = "lineChart" + + grouping = _LineChartBase.grouping + varyColors = _LineChartBase.varyColors + ser = _LineChartBase.ser + dLbls = _LineChartBase.dLbls + dropLines =_LineChartBase.dropLines + + hiLowLines = Typed(expected_type=ChartLines, allow_none=True) + upDownBars = Typed(expected_type=UpDownBars, allow_none=True) + marker = NestedBool(allow_none=True) + smooth = NestedBool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=_BaseAxis) + y_axis = Typed(expected_type=NumericAxis) + + __elements__ = _LineChartBase.__elements__ + ('hiLowLines', 'upDownBars', 'marker', 'smooth', 'axId') + + def __init__(self, + hiLowLines=None, + upDownBars=None, + marker=None, + smooth=None, + extLst=None, + **kw + ): + self.hiLowLines = hiLowLines + self.upDownBars = upDownBars + self.marker = marker + self.smooth = smooth + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + + super().__init__(**kw) + + +class LineChart3D(_LineChartBase): + + tagname = "line3DChart" + + grouping = _LineChartBase.grouping + varyColors = _LineChartBase.varyColors + ser = _LineChartBase.ser + dLbls = _LineChartBase.dLbls + dropLines =_LineChartBase.dropLines + + gapDepth = NestedGapAmount() + hiLowLines = Typed(expected_type=ChartLines, allow_none=True) + upDownBars = Typed(expected_type=UpDownBars, allow_none=True) + marker = NestedBool(allow_none=True) + smooth = NestedBool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + z_axis = Typed(expected_type=SeriesAxis) + + __elements__ = _LineChartBase.__elements__ + ('gapDepth', 'hiLowLines', + 'upDownBars', 'marker', 'smooth', 'axId') + + def __init__(self, + gapDepth=None, + hiLowLines=None, + upDownBars=None, + marker=None, + smooth=None, + **kw + ): + self.gapDepth = gapDepth + self.hiLowLines = hiLowLines + self.upDownBars = upDownBars + self.marker = marker + self.smooth = smooth + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.z_axis = SeriesAxis() + super(LineChart3D, self).__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/marker.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/marker.py new file mode 100644 index 00000000..61e2641d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/marker.py @@ -0,0 +1,90 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias, +) + +from openpyxl.descriptors.excel import( + ExtensionList, + _explicit_none, +) + +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedMinMax, + NestedNoneSet, +) + +from .layout import Layout +from .picture import PictureOptions +from .shapes import * +from .text import * +from .error_bar import * + + +class Marker(Serialisable): + + tagname = "marker" + + symbol = NestedNoneSet(values=(['circle', 'dash', 'diamond', 'dot', 'picture', + 'plus', 'square', 'star', 'triangle', 'x', 'auto']), + to_tree=_explicit_none) + size = NestedMinMax(min=2, max=72, allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('symbol', 'size', 'spPr') + + def __init__(self, + symbol=None, + size=None, + spPr=None, + extLst=None, + ): + self.symbol = symbol + self.size = size + if spPr is None: + spPr = GraphicalProperties() + self.spPr = spPr + + +class DataPoint(Serialisable): + + tagname = "dPt" + + idx = NestedInteger() + invertIfNegative = NestedBool(allow_none=True) + marker = Typed(expected_type=Marker, allow_none=True) + bubble3D = NestedBool(allow_none=True) + explosion = NestedInteger(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + pictureOptions = Typed(expected_type=PictureOptions, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('idx', 'invertIfNegative', 'marker', 'bubble3D', + 'explosion', 'spPr', 'pictureOptions') + + def __init__(self, + idx=None, + invertIfNegative=None, + marker=None, + bubble3D=None, + explosion=None, + spPr=None, + pictureOptions=None, + extLst=None, + ): + self.idx = idx + self.invertIfNegative = invertIfNegative + self.marker = marker + self.bubble3D = bubble3D + self.explosion = explosion + if spPr is None: + spPr = GraphicalProperties() + self.spPr = spPr + self.pictureOptions = pictureOptions diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/picture.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/picture.py new file mode 100644 index 00000000..8c917d8c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/picture.py @@ -0,0 +1,35 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable + +from openpyxl.descriptors.nested import ( + NestedBool, + NestedFloat, + NestedMinMax, + NestedNoneSet, +) + +class PictureOptions(Serialisable): + + tagname = "pictureOptions" + + applyToFront = NestedBool(allow_none=True, nested=True) + applyToSides = NestedBool(allow_none=True, nested=True) + applyToEnd = NestedBool(allow_none=True, nested=True) + pictureFormat = NestedNoneSet(values=(['stretch', 'stack', 'stackScale']), nested=True) + pictureStackUnit = NestedFloat(allow_none=True, nested=True) + + __elements__ = ('applyToFront', 'applyToSides', 'applyToEnd', 'pictureFormat', 'pictureStackUnit') + + def __init__(self, + applyToFront=None, + applyToSides=None, + applyToEnd=None, + pictureFormat=None, + pictureStackUnit=None, + ): + self.applyToFront = applyToFront + self.applyToSides = applyToSides + self.applyToEnd = applyToEnd + self.pictureFormat = pictureFormat + self.pictureStackUnit = pictureStackUnit diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py new file mode 100644 index 00000000..6bb67e1e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py @@ -0,0 +1,177 @@ +#Autogenerated schema +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + MinMax, + Integer, + NoneSet, + Float, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList, Percentage +from openpyxl.descriptors.nested import ( + NestedBool, + NestedMinMax, + NestedInteger, + NestedFloat, + NestedNoneSet, + NestedSet, +) +from openpyxl.descriptors.sequence import ValueSequence + +from ._chart import ChartBase +from .axis import ChartLines +from .descriptors import NestedGapAmount +from .series import Series +from .label import DataLabelList + + +class _PieChartBase(ChartBase): + + varyColors = NestedBool(allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + + _series_type = "pie" + + __elements__ = ('varyColors', 'ser', 'dLbls') + + def __init__(self, + varyColors=True, + ser=(), + dLbls=None, + ): + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + super().__init__() + + + +class PieChart(_PieChartBase): + + tagname = "pieChart" + + varyColors = _PieChartBase.varyColors + ser = _PieChartBase.ser + dLbls = _PieChartBase.dLbls + + firstSliceAng = NestedMinMax(min=0, max=360) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _PieChartBase.__elements__ + ('firstSliceAng', ) + + def __init__(self, + firstSliceAng=0, + extLst=None, + **kw + ): + self.firstSliceAng = firstSliceAng + super().__init__(**kw) + + +class PieChart3D(_PieChartBase): + + tagname = "pie3DChart" + + varyColors = _PieChartBase.varyColors + ser = _PieChartBase.ser + dLbls = _PieChartBase.dLbls + + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _PieChartBase.__elements__ + + +class DoughnutChart(_PieChartBase): + + tagname = "doughnutChart" + + varyColors = _PieChartBase.varyColors + ser = _PieChartBase.ser + dLbls = _PieChartBase.dLbls + + firstSliceAng = NestedMinMax(min=0, max=360) + holeSize = NestedMinMax(min=1, max=90, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _PieChartBase.__elements__ + ('firstSliceAng', 'holeSize') + + def __init__(self, + firstSliceAng=0, + holeSize=10, + extLst=None, + **kw + ): + self.firstSliceAng = firstSliceAng + self.holeSize = holeSize + super().__init__(**kw) + + +class CustomSplit(Serialisable): + + tagname = "custSplit" + + secondPiePt = ValueSequence(expected_type=int) + + __elements__ = ('secondPiePt',) + + def __init__(self, + secondPiePt=(), + ): + self.secondPiePt = secondPiePt + + +class ProjectedPieChart(_PieChartBase): + + """ + From the spec 21.2.2.126 + + This element contains the pie of pie or bar of pie series on this + chart. Only the first series shall be displayed. The splitType element + shall determine whether the splitPos and custSplit elements apply. + """ + + tagname = "ofPieChart" + + varyColors = _PieChartBase.varyColors + ser = _PieChartBase.ser + dLbls = _PieChartBase.dLbls + + ofPieType = NestedSet(values=(['pie', 'bar'])) + type = Alias('ofPieType') + gapWidth = NestedGapAmount() + splitType = NestedNoneSet(values=(['auto', 'cust', 'percent', 'pos', 'val'])) + splitPos = NestedFloat(allow_none=True) + custSplit = Typed(expected_type=CustomSplit, allow_none=True) + secondPieSize = NestedMinMax(min=5, max=200, allow_none=True) + serLines = Typed(expected_type=ChartLines, allow_none=True) + join_lines = Alias('serLines') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _PieChartBase.__elements__ + ('ofPieType', 'gapWidth', + 'splitType', 'splitPos', 'custSplit', 'secondPieSize', 'serLines') + + def __init__(self, + ofPieType="pie", + gapWidth=None, + splitType="auto", + splitPos=None, + custSplit=None, + secondPieSize=75, + serLines=None, + extLst=None, + **kw + ): + self.ofPieType = ofPieType + self.gapWidth = gapWidth + self.splitType = splitType + self.splitPos = splitPos + self.custSplit = custSplit + self.secondPieSize = secondPieSize + if serLines is None: + self.serLines = ChartLines() + super().__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py new file mode 100644 index 00000000..937fd294 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py @@ -0,0 +1,65 @@ + +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, +) +from openpyxl.descriptors.nested import NestedInteger, NestedText +from openpyxl.descriptors.excel import ExtensionList + +from .label import DataLabel +from .marker import Marker +from .shapes import GraphicalProperties +from .text import RichText + + +class PivotSource(Serialisable): + + tagname = "pivotSource" + + name = NestedText(expected_type=str) + fmtId = NestedInteger(expected_type=int) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('name', 'fmtId') + + def __init__(self, + name=None, + fmtId=None, + extLst=None, + ): + self.name = name + self.fmtId = fmtId + + +class PivotFormat(Serialisable): + + tagname = "pivotFmt" + + idx = NestedInteger(nested=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + txPr = Typed(expected_type=RichText, allow_none=True) + TextBody = Alias("txPr") + marker = Typed(expected_type=Marker, allow_none=True) + dLbl = Typed(expected_type=DataLabel, allow_none=True) + DataLabel = Alias("dLbl") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('idx', 'spPr', 'txPr', 'marker', 'dLbl') + + def __init__(self, + idx=0, + spPr=None, + txPr=None, + marker=None, + dLbl=None, + extLst=None, + ): + self.idx = idx + self.spPr = spPr + self.txPr = txPr + self.marker = marker + self.dLbl = dLbl diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py new file mode 100644 index 00000000..268bfbc4 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py @@ -0,0 +1,162 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias, +) +from openpyxl.descriptors.excel import ( + ExtensionList, +) +from openpyxl.descriptors.sequence import ( + MultiSequence, + MultiSequencePart, +) +from openpyxl.descriptors.nested import ( + NestedBool, +) + +from ._3d import _3DBase +from .area_chart import AreaChart, AreaChart3D +from .bar_chart import BarChart, BarChart3D +from .bubble_chart import BubbleChart +from .line_chart import LineChart, LineChart3D +from .pie_chart import PieChart, PieChart3D, ProjectedPieChart, DoughnutChart +from .radar_chart import RadarChart +from .scatter_chart import ScatterChart +from .stock_chart import StockChart +from .surface_chart import SurfaceChart, SurfaceChart3D +from .layout import Layout +from .shapes import GraphicalProperties +from .text import RichText + +from .axis import ( + NumericAxis, + TextAxis, + SeriesAxis, + DateAxis, +) + + +class DataTable(Serialisable): + + tagname = "dTable" + + showHorzBorder = NestedBool(allow_none=True) + showVertBorder = NestedBool(allow_none=True) + showOutline = NestedBool(allow_none=True) + showKeys = NestedBool(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('showHorzBorder', 'showVertBorder', 'showOutline', + 'showKeys', 'spPr', 'txPr') + + def __init__(self, + showHorzBorder=None, + showVertBorder=None, + showOutline=None, + showKeys=None, + spPr=None, + txPr=None, + extLst=None, + ): + self.showHorzBorder = showHorzBorder + self.showVertBorder = showVertBorder + self.showOutline = showOutline + self.showKeys = showKeys + self.spPr = spPr + self.txPr = txPr + + +class PlotArea(Serialisable): + + tagname = "plotArea" + + layout = Typed(expected_type=Layout, allow_none=True) + dTable = Typed(expected_type=DataTable, allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + # at least one chart + _charts = MultiSequence() + areaChart = MultiSequencePart(expected_type=AreaChart, store="_charts") + area3DChart = MultiSequencePart(expected_type=AreaChart3D, store="_charts") + lineChart = MultiSequencePart(expected_type=LineChart, store="_charts") + line3DChart = MultiSequencePart(expected_type=LineChart3D, store="_charts") + stockChart = MultiSequencePart(expected_type=StockChart, store="_charts") + radarChart = MultiSequencePart(expected_type=RadarChart, store="_charts") + scatterChart = MultiSequencePart(expected_type=ScatterChart, store="_charts") + pieChart = MultiSequencePart(expected_type=PieChart, store="_charts") + pie3DChart = MultiSequencePart(expected_type=PieChart3D, store="_charts") + doughnutChart = MultiSequencePart(expected_type=DoughnutChart, store="_charts") + barChart = MultiSequencePart(expected_type=BarChart, store="_charts") + bar3DChart = MultiSequencePart(expected_type=BarChart3D, store="_charts") + ofPieChart = MultiSequencePart(expected_type=ProjectedPieChart, store="_charts") + surfaceChart = MultiSequencePart(expected_type=SurfaceChart, store="_charts") + surface3DChart = MultiSequencePart(expected_type=SurfaceChart3D, store="_charts") + bubbleChart = MultiSequencePart(expected_type=BubbleChart, store="_charts") + + # axes + _axes = MultiSequence() + valAx = MultiSequencePart(expected_type=NumericAxis, store="_axes") + catAx = MultiSequencePart(expected_type=TextAxis, store="_axes") + dateAx = MultiSequencePart(expected_type=DateAxis, store="_axes") + serAx = MultiSequencePart(expected_type=SeriesAxis, store="_axes") + + __elements__ = ('layout', '_charts', '_axes', 'dTable', 'spPr') + + def __init__(self, + layout=None, + dTable=None, + spPr=None, + _charts=(), + _axes=(), + extLst=None, + ): + self.layout = layout + self.dTable = dTable + self.spPr = spPr + self._charts = _charts + self._axes = _axes + + + def to_tree(self, tagname=None, idx=None, namespace=None): + axIds = {ax.axId for ax in self._axes} + for chart in self._charts: + for id, axis in chart._axes.items(): + if id not in axIds: + setattr(self, axis.tagname, axis) + axIds.add(id) + + return super().to_tree(tagname) + + + @classmethod + def from_tree(cls, node): + self = super().from_tree(node) + axes = dict((axis.axId, axis) for axis in self._axes) + for chart in self._charts: + if isinstance(chart, (ScatterChart, BubbleChart)): + x, y = (axes[axId] for axId in chart.axId) + chart.x_axis = x + chart.y_axis = y + continue + + for axId in chart.axId: + axis = axes.get(axId) + if axis is None and isinstance(chart, _3DBase): + # Series Axis can be optional + chart.z_axis = None + continue + if axis.tagname in ("catAx", "dateAx"): + chart.x_axis = axis + elif axis.tagname == "valAx": + chart.y_axis = axis + elif axis.tagname == "serAx": + chart.z_axis = axis + + return self diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py new file mode 100644 index 00000000..65137310 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py @@ -0,0 +1,57 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Float, + Typed, + Alias, +) + +from openpyxl.worksheet.page import PrintPageSetup +from openpyxl.worksheet.header_footer import HeaderFooter + + +class PageMargins(Serialisable): + """ + Identical to openpyxl.worksheet.page.Pagemargins but element names are different :-/ + """ + tagname = "pageMargins" + + l = Float() + left = Alias('l') + r = Float() + right = Alias('r') + t = Float() + top = Alias('t') + b = Float() + bottom = Alias('b') + header = Float() + footer = Float() + + def __init__(self, l=0.75, r=0.75, t=1, b=1, header=0.5, footer=0.5): + self.l = l + self.r = r + self.t = t + self.b = b + self.header = header + self.footer = footer + + +class PrintSettings(Serialisable): + + tagname = "printSettings" + + headerFooter = Typed(expected_type=HeaderFooter, allow_none=True) + pageMargins = Typed(expected_type=PageMargins, allow_none=True) + pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True) + + __elements__ = ("headerFooter", "pageMargins", "pageMargins") + + def __init__(self, + headerFooter=None, + pageMargins=None, + pageSetup=None, + ): + self.headerFooter = headerFooter + self.pageMargins = pageMargins + self.pageSetup = pageSetup diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py new file mode 100644 index 00000000..fa3aa0da --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py @@ -0,0 +1,55 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence, + Typed, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedSet +) + +from ._chart import ChartBase +from .axis import TextAxis, NumericAxis +from .series import Series +from .label import DataLabelList + + +class RadarChart(ChartBase): + + tagname = "radarChart" + + radarStyle = NestedSet(values=(['standard', 'marker', 'filled'])) + type = Alias("radarStyle") + varyColors = NestedBool(nested=True, allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + _series_type = "radar" + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + + __elements__ = ('radarStyle', 'varyColors', 'ser', 'dLbls', 'axId') + + def __init__(self, + radarStyle="standard", + varyColors=None, + ser=(), + dLbls=None, + extLst=None, + **kw + ): + self.radarStyle = radarStyle + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + super().__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/reader.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/reader.py new file mode 100644 index 00000000..0ef719f9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/reader.py @@ -0,0 +1,32 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Read a chart +""" + +def read_chart(chartspace): + cs = chartspace + plot = cs.chart.plotArea + + chart = plot._charts[0] + chart._charts = plot._charts + + chart.title = cs.chart.title + chart.display_blanks = cs.chart.dispBlanksAs + chart.visible_cells_only = cs.chart.plotVisOnly + chart.layout = plot.layout + chart.legend = cs.chart.legend + + # 3d attributes + chart.floor = cs.chart.floor + chart.sideWall = cs.chart.sideWall + chart.backWall = cs.chart.backWall + chart.pivotSource = cs.pivotSource + chart.pivotFormats = cs.chart.pivotFmts + chart.idx_base = min((s.idx for s in chart.series), default=0) + chart._reindex() + + # Border, fill, etc. + chart.graphical_properties = cs.graphical_properties + + return chart diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/reference.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/reference.py new file mode 100644 index 00000000..dc102791 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/reference.py @@ -0,0 +1,124 @@ +# Copyright (c) 2010-2024 openpyxl + +from itertools import chain + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + MinMax, + Typed, + String, + Strict, +) +from openpyxl.worksheet.worksheet import Worksheet +from openpyxl.utils import ( + get_column_letter, + range_to_tuple, + quote_sheetname +) + + +class DummyWorksheet: + + + def __init__(self, title): + self.title = title + + +class Reference(Strict): + + """ + Normalise cell range references + """ + + min_row = MinMax(min=1, max=1000000, expected_type=int) + max_row = MinMax(min=1, max=1000000, expected_type=int) + min_col = MinMax(min=1, max=16384, expected_type=int) + max_col = MinMax(min=1, max=16384, expected_type=int) + range_string = String(allow_none=True) + + def __init__(self, + worksheet=None, + min_col=None, + min_row=None, + max_col=None, + max_row=None, + range_string=None + ): + if range_string is not None: + sheetname, boundaries = range_to_tuple(range_string) + min_col, min_row, max_col, max_row = boundaries + worksheet = DummyWorksheet(sheetname) + + self.worksheet = worksheet + self.min_col = min_col + self.min_row = min_row + if max_col is None: + max_col = min_col + self.max_col = max_col + if max_row is None: + max_row = min_row + self.max_row = max_row + + + def __repr__(self): + return str(self) + + + def __str__(self): + fmt = u"{0}!${1}${2}:${3}${4}" + if (self.min_col == self.max_col + and self.min_row == self.max_row): + fmt = u"{0}!${1}${2}" + return fmt.format(self.sheetname, + get_column_letter(self.min_col), self.min_row, + get_column_letter(self.max_col), self.max_row + ) + + + __str__ = __str__ + + + + def __len__(self): + if self.min_row == self.max_row: + return 1 + self.max_col - self.min_col + return 1 + self.max_row - self.min_row + + + def __eq__(self, other): + return str(self) == str(other) + + + @property + def rows(self): + """ + Return all rows in the range + """ + for row in range(self.min_row, self.max_row+1): + yield Reference(self.worksheet, self.min_col, row, self.max_col, row) + + + @property + def cols(self): + """ + Return all columns in the range + """ + for col in range(self.min_col, self.max_col+1): + yield Reference(self.worksheet, col, self.min_row, col, self.max_row) + + + def pop(self): + """ + Return and remove the first cell + """ + cell = "{0}{1}".format(get_column_letter(self.min_col), self.min_row) + if self.min_row == self.max_row: + self.min_col += 1 + else: + self.min_row += 1 + return cell + + + @property + def sheetname(self): + return quote_sheetname(self.worksheet.title) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py new file mode 100644 index 00000000..2699239e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py @@ -0,0 +1,53 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Sequence, + Alias +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedBool, +) + +from ._chart import ChartBase +from .axis import NumericAxis, TextAxis +from .series import XYSeries +from .label import DataLabelList + + +class ScatterChart(ChartBase): + + tagname = "scatterChart" + + scatterStyle = NestedNoneSet(values=(['line', 'lineMarker', 'marker', 'smooth', 'smoothMarker'])) + varyColors = NestedBool(allow_none=True) + ser = Sequence(expected_type=XYSeries, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=(NumericAxis, TextAxis)) + y_axis = Typed(expected_type=NumericAxis) + + _series_type = "scatter" + + __elements__ = ('scatterStyle', 'varyColors', 'ser', 'dLbls', 'axId',) + + def __init__(self, + scatterStyle=None, + varyColors=None, + ser=(), + dLbls=None, + extLst=None, + **kw + ): + self.scatterStyle = scatterStyle + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.x_axis = NumericAxis(axId=10, crossAx=20) + self.y_axis = NumericAxis(axId=20, crossAx=10) + super().__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/series.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/series.py new file mode 100644 index 00000000..f1403a6c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/series.py @@ -0,0 +1,197 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Integer, + Bool, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedBool, + NestedNoneSet, + NestedText, +) + +from .shapes import GraphicalProperties +from .data_source import ( + AxDataSource, + NumDataSource, + NumRef, + StrRef, +) +from .error_bar import ErrorBars +from .label import DataLabelList +from .marker import DataPoint, PictureOptions, Marker +from .trendline import Trendline + +attribute_mapping = { + 'area': ('idx', 'order', 'tx', 'spPr', 'pictureOptions', 'dPt', 'dLbls', 'errBars', + 'trendline', 'cat', 'val',), + 'bar':('idx', 'order','tx', 'spPr', 'invertIfNegative', 'pictureOptions', 'dPt', + 'dLbls', 'trendline', 'errBars', 'cat', 'val', 'shape'), + 'bubble':('idx','order', 'tx', 'spPr', 'invertIfNegative', 'dPt', 'dLbls', + 'trendline', 'errBars', 'xVal', 'yVal', 'bubbleSize', 'bubble3D'), + 'line':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline', + 'errBars', 'cat', 'val', 'smooth'), + 'pie':('idx', 'order', 'tx', 'spPr', 'explosion', 'dPt', 'dLbls', 'cat', 'val'), + 'radar':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'cat', 'val'), + 'scatter':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline', + 'errBars', 'xVal', 'yVal', 'smooth'), + 'surface':('idx', 'order', 'tx', 'spPr', 'cat', 'val'), + } + + +class SeriesLabel(Serialisable): + + tagname = "tx" + + strRef = Typed(expected_type=StrRef, allow_none=True) + v = NestedText(expected_type=str, allow_none=True) + value = Alias('v') + + __elements__ = ('strRef', 'v') + + def __init__(self, + strRef=None, + v=None): + self.strRef = strRef + self.v = v + + +class Series(Serialisable): + + """ + Generic series object. Should not be instantiated directly. + User the chart.Series factory instead. + """ + + tagname = "ser" + + idx = NestedInteger() + order = NestedInteger() + tx = Typed(expected_type=SeriesLabel, allow_none=True) + title = Alias('tx') + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + + # area chart + pictureOptions = Typed(expected_type=PictureOptions, allow_none=True) + dPt = Sequence(expected_type=DataPoint, allow_none=True) + data_points = Alias("dPt") + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + labels = Alias("dLbls") + trendline = Typed(expected_type=Trendline, allow_none=True) + errBars = Typed(expected_type=ErrorBars, allow_none=True) + cat = Typed(expected_type=AxDataSource, allow_none=True) + identifiers = Alias("cat") + val = Typed(expected_type=NumDataSource, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + #bar chart + invertIfNegative = NestedBool(allow_none=True) + shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax'])) + + #bubble chart + xVal = Typed(expected_type=AxDataSource, allow_none=True) + yVal = Typed(expected_type=NumDataSource, allow_none=True) + bubbleSize = Typed(expected_type=NumDataSource, allow_none=True) + zVal = Alias("bubbleSize") + bubble3D = NestedBool(allow_none=True) + + #line chart + marker = Typed(expected_type=Marker, allow_none=True) + smooth = NestedBool(allow_none=True) + + #pie chart + explosion = NestedInteger(allow_none=True) + + __elements__ = () + + + def __init__(self, + idx=0, + order=0, + tx=None, + spPr=None, + pictureOptions=None, + dPt=(), + dLbls=None, + trendline=None, + errBars=None, + cat=None, + val=None, + invertIfNegative=None, + shape=None, + xVal=None, + yVal=None, + bubbleSize=None, + bubble3D=None, + marker=None, + smooth=None, + explosion=None, + extLst=None, + ): + self.idx = idx + self.order = order + self.tx = tx + if spPr is None: + spPr = GraphicalProperties() + self.spPr = spPr + self.pictureOptions = pictureOptions + self.dPt = dPt + self.dLbls = dLbls + self.trendline = trendline + self.errBars = errBars + self.cat = cat + self.val = val + self.invertIfNegative = invertIfNegative + self.shape = shape + self.xVal = xVal + self.yVal = yVal + self.bubbleSize = bubbleSize + self.bubble3D = bubble3D + if marker is None: + marker = Marker() + self.marker = marker + self.smooth = smooth + self.explosion = explosion + + + def to_tree(self, tagname=None, idx=None): + """The index can need rebasing""" + if idx is not None: + if self.order == self.idx: + self.order = idx # rebase the order if the index has been rebased + self.idx = idx + return super().to_tree(tagname) + + +class XYSeries(Series): + + """Dedicated series for charts that have x and y series""" + + idx = Series.idx + order = Series.order + tx = Series.tx + spPr = Series.spPr + + dPt = Series.dPt + dLbls = Series.dLbls + trendline = Series.trendline + errBars = Series.errBars + xVal = Series.xVal + yVal = Series.yVal + + invertIfNegative = Series.invertIfNegative + + bubbleSize = Series.bubbleSize + bubble3D = Series.bubble3D + + marker = Series.marker + smooth = Series.smooth diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py new file mode 100644 index 00000000..90b368d9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py @@ -0,0 +1,41 @@ +# Copyright (c) 2010-2024 openpyxl + +from .data_source import NumDataSource, NumRef, AxDataSource +from .reference import Reference +from .series import Series, XYSeries, SeriesLabel, StrRef +from openpyxl.utils import rows_from_range, quote_sheetname + + +def SeriesFactory(values, xvalues=None, zvalues=None, title=None, title_from_data=False): + """ + Convenience Factory for creating chart data series. + """ + + if not isinstance(values, Reference): + values = Reference(range_string=values) + + if title_from_data: + cell = values.pop() + title = u"{0}!{1}".format(values.sheetname, cell) + title = SeriesLabel(strRef=StrRef(title)) + elif title is not None: + title = SeriesLabel(v=title) + + source = NumDataSource(numRef=NumRef(f=values)) + if xvalues is not None: + if not isinstance(xvalues, Reference): + xvalues = Reference(range_string=xvalues) + series = XYSeries() + series.yVal = source + series.xVal = AxDataSource(numRef=NumRef(f=xvalues)) + if zvalues is not None: + if not isinstance(zvalues, Reference): + zvalues = Reference(range_string=zvalues) + series.zVal = NumDataSource(NumRef(f=zvalues)) + else: + series = Series() + series.val = source + + if title is not None: + series.title = title + return series diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py new file mode 100644 index 00000000..7736c1ad --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py @@ -0,0 +1,89 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias +) +from openpyxl.descriptors.nested import ( + EmptyTag +) +from openpyxl.drawing.colors import ColorChoiceDescriptor +from openpyxl.drawing.fill import * +from openpyxl.drawing.line import LineProperties +from openpyxl.drawing.geometry import ( + Shape3D, + Scene3D, + Transform2D, + CustomGeometry2D, + PresetGeometry2D, +) + + +class GraphicalProperties(Serialisable): + + """ + Somewhat vaguely 21.2.2.197 says this: + + This element specifies the formatting for the parent chart element. The + custGeom, prstGeom, scene3d, and xfrm elements are not supported. The + bwMode attribute is not supported. + + This doesn't leave much. And the element is used in different places. + """ + + tagname = "spPr" + + bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray', + 'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden'] + ) + ) + + xfrm = Typed(expected_type=Transform2D, allow_none=True) + transform = Alias('xfrm') + custGeom = Typed(expected_type=CustomGeometry2D, allow_none=True) # either or + prstGeom = Typed(expected_type=PresetGeometry2D, allow_none=True) + + # fills one of + noFill = EmptyTag(namespace=DRAWING_NS) + solidFill = ColorChoiceDescriptor() + gradFill = Typed(expected_type=GradientFillProperties, allow_none=True) + pattFill = Typed(expected_type=PatternFillProperties, allow_none=True) + + ln = Typed(expected_type=LineProperties, allow_none=True) + line = Alias('ln') + scene3d = Typed(expected_type=Scene3D, allow_none=True) + sp3d = Typed(expected_type=Shape3D, allow_none=True) + shape3D = Alias('sp3d') + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ('xfrm', 'prstGeom', 'noFill', 'solidFill', 'gradFill', 'pattFill', + 'ln', 'scene3d', 'sp3d') + + def __init__(self, + bwMode=None, + xfrm=None, + noFill=None, + solidFill=None, + gradFill=None, + pattFill=None, + ln=None, + scene3d=None, + custGeom=None, + prstGeom=None, + sp3d=None, + extLst=None, + ): + self.bwMode = bwMode + self.xfrm = xfrm + self.noFill = noFill + self.solidFill = solidFill + self.gradFill = gradFill + self.pattFill = pattFill + if ln is None: + ln = LineProperties() + self.ln = ln + self.custGeom = custGeom + self.prstGeom = prstGeom + self.scene3d = scene3d + self.sp3d = sp3d diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py new file mode 100644 index 00000000..119c7901 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py @@ -0,0 +1,54 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Sequence, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList + +from ._chart import ChartBase +from .axis import TextAxis, NumericAxis, ChartLines +from .updown_bars import UpDownBars +from .label import DataLabelList +from .series import Series + + +class StockChart(ChartBase): + + tagname = "stockChart" + + ser = Sequence(expected_type=Series) #min 3, max4 + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias('dLbls') + dropLines = Typed(expected_type=ChartLines, allow_none=True) + hiLowLines = Typed(expected_type=ChartLines, allow_none=True) + upDownBars = Typed(expected_type=UpDownBars, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + + _series_type = "line" + + __elements__ = ('ser', 'dLbls', 'dropLines', 'hiLowLines', 'upDownBars', + 'axId') + + def __init__(self, + ser=(), + dLbls=None, + dropLines=None, + hiLowLines=None, + upDownBars=None, + extLst=None, + **kw + ): + self.ser = ser + self.dLbls = dLbls + self.dropLines = dropLines + self.hiLowLines = hiLowLines + self.upDownBars = upDownBars + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + super().__init__(**kw) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py new file mode 100644 index 00000000..5f388e14 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py @@ -0,0 +1,119 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + Bool, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedBool, +) + +from ._chart import ChartBase +from ._3d import _3DBase +from .axis import TextAxis, NumericAxis, SeriesAxis +from .shapes import GraphicalProperties +from .series import Series + + +class BandFormat(Serialisable): + + tagname = "bandFmt" + + idx = NestedInteger() + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + + __elements__ = ('idx', 'spPr') + + def __init__(self, + idx=0, + spPr=None, + ): + self.idx = idx + self.spPr = spPr + + +class BandFormatList(Serialisable): + + tagname = "bandFmts" + + bandFmt = Sequence(expected_type=BandFormat, allow_none=True) + + __elements__ = ('bandFmt',) + + def __init__(self, + bandFmt=(), + ): + self.bandFmt = bandFmt + + +class _SurfaceChartBase(ChartBase): + + wireframe = NestedBool(allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + bandFmts = Typed(expected_type=BandFormatList, allow_none=True) + + _series_type = "surface" + + __elements__ = ('wireframe', 'ser', 'bandFmts') + + def __init__(self, + wireframe=None, + ser=(), + bandFmts=None, + **kw + ): + self.wireframe = wireframe + self.ser = ser + self.bandFmts = bandFmts + super().__init__(**kw) + + +class SurfaceChart3D(_SurfaceChartBase, _3DBase): + + tagname = "surface3DChart" + + wireframe = _SurfaceChartBase.wireframe + ser = _SurfaceChartBase.ser + bandFmts = _SurfaceChartBase.bandFmts + + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + z_axis = Typed(expected_type=SeriesAxis) + + __elements__ = _SurfaceChartBase.__elements__ + ('axId',) + + def __init__(self, **kw): + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.z_axis = SeriesAxis() + super(SurfaceChart3D, self).__init__(**kw) + + +class SurfaceChart(SurfaceChart3D): + + tagname = "surfaceChart" + + wireframe = _SurfaceChartBase.wireframe + ser = _SurfaceChartBase.ser + bandFmts = _SurfaceChartBase.bandFmts + + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = SurfaceChart3D.__elements__ + + def __init__(self, **kw): + super().__init__(**kw) + self.y_axis.delete = True + self.view3D.x_rotation = 90 + self.view3D.y_rotation = 0 + self.view3D.perspective = False + self.view3D.right_angle_axes = False diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/text.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/text.py new file mode 100644 index 00000000..bd034c24 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/text.py @@ -0,0 +1,78 @@ +# Copyright (c) 2010-2024 openpyxl +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias, + Sequence, +) + + +from openpyxl.drawing.text import ( + RichTextProperties, + ListStyle, + Paragraph, +) + +from .data_source import StrRef + + +class RichText(Serialisable): + + """ + From the specification: 21.2.2.216 + + This element specifies text formatting. The lstStyle element is not supported. + """ + + tagname = "rich" + + bodyPr = Typed(expected_type=RichTextProperties) + properties = Alias("bodyPr") + lstStyle = Typed(expected_type=ListStyle, allow_none=True) + p = Sequence(expected_type=Paragraph) + paragraphs = Alias('p') + + __elements__ = ("bodyPr", "lstStyle", "p") + + def __init__(self, + bodyPr=None, + lstStyle=None, + p=None, + ): + if bodyPr is None: + bodyPr = RichTextProperties() + self.bodyPr = bodyPr + self.lstStyle = lstStyle + if p is None: + p = [Paragraph()] + self.p = p + + +class Text(Serialisable): + + """ + The value can be either a cell reference or a text element + If both are present then the reference will be used. + """ + + tagname = "tx" + + strRef = Typed(expected_type=StrRef, allow_none=True) + rich = Typed(expected_type=RichText, allow_none=True) + + __elements__ = ("strRef", "rich") + + def __init__(self, + strRef=None, + rich=None + ): + self.strRef = strRef + if rich is None: + rich = RichText() + self.rich = rich + + + def to_tree(self, tagname=None, idx=None, namespace=None): + if self.strRef and self.rich: + self.rich = None # can only have one + return super().to_tree(tagname, idx, namespace) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/title.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/title.py new file mode 100644 index 00000000..10f79d7a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/title.py @@ -0,0 +1,76 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias, +) + +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import NestedBool + +from .text import Text, RichText +from .layout import Layout +from .shapes import GraphicalProperties + +from openpyxl.drawing.text import ( + Paragraph, + RegularTextRun, + LineBreak, + ParagraphProperties, + CharacterProperties, +) + + +class Title(Serialisable): + tagname = "title" + + tx = Typed(expected_type=Text, allow_none=True) + text = Alias('tx') + layout = Typed(expected_type=Layout, allow_none=True) + overlay = NestedBool(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + body = Alias('txPr') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('tx', 'layout', 'overlay', 'spPr', 'txPr') + + def __init__(self, + tx=None, + layout=None, + overlay=None, + spPr=None, + txPr=None, + extLst=None, + ): + if tx is None: + tx = Text() + self.tx = tx + self.layout = layout + self.overlay = overlay + self.spPr = spPr + self.txPr = txPr + + + +def title_maker(text): + title = Title() + paraprops = ParagraphProperties() + paraprops.defRPr = CharacterProperties() + paras = [Paragraph(r=[RegularTextRun(t=s)], pPr=paraprops) for s in text.split("\n")] + + title.tx.rich.paragraphs = paras + return title + + +class TitleDescriptor(Typed): + + expected_type = Title + allow_none = True + + def __set__(self, instance, value): + if isinstance(value, str): + value = title_maker(value) + super().__set__(instance, value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py new file mode 100644 index 00000000..bf6d2366 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py @@ -0,0 +1,98 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Alias +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedFloat, + NestedSet +) + +from .data_source import NumFmt +from .shapes import GraphicalProperties +from .text import RichText, Text +from .layout import Layout + + +class TrendlineLabel(Serialisable): + + tagname = "trendlineLbl" + + layout = Typed(expected_type=Layout, allow_none=True) + tx = Typed(expected_type=Text, allow_none=True) + numFmt = Typed(expected_type=NumFmt, allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias("txPr") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('layout', 'tx', 'numFmt', 'spPr', 'txPr') + + def __init__(self, + layout=None, + tx=None, + numFmt=None, + spPr=None, + txPr=None, + extLst=None, + ): + self.layout = layout + self.tx = tx + self.numFmt = numFmt + self.spPr = spPr + self.txPr = txPr + + +class Trendline(Serialisable): + + tagname = "trendline" + + name = String(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + trendlineType = NestedSet(values=(['exp', 'linear', 'log', 'movingAvg', 'poly', 'power'])) + order = NestedInteger(allow_none=True) + period = NestedInteger(allow_none=True) + forward = NestedFloat(allow_none=True) + backward = NestedFloat(allow_none=True) + intercept = NestedFloat(allow_none=True) + dispRSqr = NestedBool(allow_none=True) + dispEq = NestedBool(allow_none=True) + trendlineLbl = Typed(expected_type=TrendlineLabel, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('spPr', 'trendlineType', 'order', 'period', 'forward', + 'backward', 'intercept', 'dispRSqr', 'dispEq', 'trendlineLbl') + + def __init__(self, + name=None, + spPr=None, + trendlineType='linear', + order=None, + period=None, + forward=None, + backward=None, + intercept=None, + dispRSqr=None, + dispEq=None, + trendlineLbl=None, + extLst=None, + ): + self.name = name + self.spPr = spPr + self.trendlineType = trendlineType + self.order = order + self.period = period + self.forward = forward + self.backward = backward + self.intercept = intercept + self.dispRSqr = dispRSqr + self.dispEq = dispEq + self.trendlineLbl = trendlineLbl diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py new file mode 100644 index 00000000..6de7ab8a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py @@ -0,0 +1,31 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import Typed +from openpyxl.descriptors.excel import ExtensionList + +from .shapes import GraphicalProperties +from .axis import ChartLines +from .descriptors import NestedGapAmount + + +class UpDownBars(Serialisable): + + tagname = "upbars" + + gapWidth = NestedGapAmount() + upBars = Typed(expected_type=ChartLines, allow_none=True) + downBars = Typed(expected_type=ChartLines, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('gapWidth', 'upBars', 'downBars') + + def __init__(self, + gapWidth=150, + upBars=None, + downBars=None, + extLst=None, + ): + self.gapWidth = gapWidth + self.upBars = upBars + self.downBars = downBars diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/__init__.py new file mode 100644 index 00000000..17266761 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2010-2024 openpyxl + +from .chartsheet import Chartsheet diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/chartsheet.py b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/chartsheet.py new file mode 100644 index 00000000..21adbb43 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/chartsheet.py @@ -0,0 +1,107 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors import Typed, Set, Alias +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.drawing.spreadsheet_drawing import ( + AbsoluteAnchor, + SpreadsheetDrawing, +) +from openpyxl.worksheet.page import ( + PageMargins, + PrintPageSetup +) +from openpyxl.worksheet.drawing import Drawing +from openpyxl.worksheet.header_footer import HeaderFooter +from openpyxl.workbook.child import _WorkbookChild +from openpyxl.xml.constants import SHEET_MAIN_NS, REL_NS + +from .relation import DrawingHF, SheetBackgroundPicture +from .properties import ChartsheetProperties +from .protection import ChartsheetProtection +from .views import ChartsheetViewList +from .custom import CustomChartsheetViews +from .publish import WebPublishItems + + +class Chartsheet(_WorkbookChild, Serialisable): + + tagname = "chartsheet" + _default_title = "Chart" + _rel_type = "chartsheet" + _path = "/xl/chartsheets/sheet{0}.xml" + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" + + sheetPr = Typed(expected_type=ChartsheetProperties, allow_none=True) + sheetViews = Typed(expected_type=ChartsheetViewList) + sheetProtection = Typed(expected_type=ChartsheetProtection, allow_none=True) + customSheetViews = Typed(expected_type=CustomChartsheetViews, allow_none=True) + pageMargins = Typed(expected_type=PageMargins, allow_none=True) + pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True) + drawing = Typed(expected_type=Drawing, allow_none=True) + drawingHF = Typed(expected_type=DrawingHF, allow_none=True) + picture = Typed(expected_type=SheetBackgroundPicture, allow_none=True) + webPublishItems = Typed(expected_type=WebPublishItems, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + sheet_state = Set(values=('visible', 'hidden', 'veryHidden')) + headerFooter = Typed(expected_type=HeaderFooter) + HeaderFooter = Alias('headerFooter') + + __elements__ = ( + 'sheetPr', 'sheetViews', 'sheetProtection', 'customSheetViews', + 'pageMargins', 'pageSetup', 'headerFooter', 'drawing', 'drawingHF', + 'picture', 'webPublishItems') + + __attrs__ = () + + def __init__(self, + sheetPr=None, + sheetViews=None, + sheetProtection=None, + customSheetViews=None, + pageMargins=None, + pageSetup=None, + headerFooter=None, + drawing=None, + drawingHF=None, + picture=None, + webPublishItems=None, + extLst=None, + parent=None, + title="", + sheet_state='visible', + ): + super().__init__(parent, title) + self._charts = [] + self.sheetPr = sheetPr + if sheetViews is None: + sheetViews = ChartsheetViewList() + self.sheetViews = sheetViews + self.sheetProtection = sheetProtection + self.customSheetViews = customSheetViews + self.pageMargins = pageMargins + self.pageSetup = pageSetup + if headerFooter is not None: + self.headerFooter = headerFooter + self.drawing = Drawing("rId1") + self.drawingHF = drawingHF + self.picture = picture + self.webPublishItems = webPublishItems + self.sheet_state = sheet_state + + + def add_chart(self, chart): + chart.anchor = AbsoluteAnchor() + self._charts.append(chart) + + + def to_tree(self): + self._drawing = SpreadsheetDrawing() + self._drawing.charts = self._charts + tree = super().to_tree() + if not self.headerFooter: + el = tree.find('headerFooter') + tree.remove(el) + tree.set("xmlns", SHEET_MAIN_NS) + return tree diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/custom.py b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/custom.py new file mode 100644 index 00000000..01fcd254 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/custom.py @@ -0,0 +1,61 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.worksheet.header_footer import HeaderFooter + +from openpyxl.descriptors import ( + Bool, + Integer, + Set, + Typed, + Sequence +) +from openpyxl.descriptors.excel import Guid +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.worksheet.page import ( + PageMargins, + PrintPageSetup +) + + +class CustomChartsheetView(Serialisable): + tagname = "customSheetView" + + guid = Guid() + scale = Integer() + state = Set(values=(['visible', 'hidden', 'veryHidden'])) + zoomToFit = Bool(allow_none=True) + pageMargins = Typed(expected_type=PageMargins, allow_none=True) + pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True) + headerFooter = Typed(expected_type=HeaderFooter, allow_none=True) + + __elements__ = ('pageMargins', 'pageSetup', 'headerFooter') + + def __init__(self, + guid=None, + scale=None, + state='visible', + zoomToFit=None, + pageMargins=None, + pageSetup=None, + headerFooter=None, + ): + self.guid = guid + self.scale = scale + self.state = state + self.zoomToFit = zoomToFit + self.pageMargins = pageMargins + self.pageSetup = pageSetup + self.headerFooter = headerFooter + + +class CustomChartsheetViews(Serialisable): + tagname = "customSheetViews" + + customSheetView = Sequence(expected_type=CustomChartsheetView, allow_none=True) + + __elements__ = ('customSheetView',) + + def __init__(self, + customSheetView=None, + ): + self.customSheetView = customSheetView diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/properties.py b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/properties.py new file mode 100644 index 00000000..bff6b3b3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/properties.py @@ -0,0 +1,28 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Bool, + String, + Typed +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.styles import Color + + +class ChartsheetProperties(Serialisable): + tagname = "sheetPr" + + published = Bool(allow_none=True) + codeName = String(allow_none=True) + tabColor = Typed(expected_type=Color, allow_none=True) + + __elements__ = ('tabColor',) + + def __init__(self, + published=None, + codeName=None, + tabColor=None, + ): + self.published = published + self.codeName = codeName + self.tabColor = tabColor diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/protection.py b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/protection.py new file mode 100644 index 00000000..f76a306b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/protection.py @@ -0,0 +1,41 @@ +import hashlib + +from openpyxl.descriptors import (Bool, Integer, String) +from openpyxl.descriptors.excel import Base64Binary +from openpyxl.descriptors.serialisable import Serialisable + +from openpyxl.worksheet.protection import ( + hash_password, + _Protected +) + + +class ChartsheetProtection(Serialisable, _Protected): + tagname = "sheetProtection" + + algorithmName = String(allow_none=True) + hashValue = Base64Binary(allow_none=True) + saltValue = Base64Binary(allow_none=True) + spinCount = Integer(allow_none=True) + content = Bool(allow_none=True) + objects = Bool(allow_none=True) + + __attrs__ = ("content", "objects", "password", "hashValue", "spinCount", "saltValue", "algorithmName") + + def __init__(self, + content=None, + objects=None, + hashValue=None, + spinCount=None, + saltValue=None, + algorithmName=None, + password=None, + ): + self.content = content + self.objects = objects + self.hashValue = hashValue + self.spinCount = spinCount + self.saltValue = saltValue + self.algorithmName = algorithmName + if password is not None: + self.password = password diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/publish.py b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/publish.py new file mode 100644 index 00000000..4f5714e8 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/publish.py @@ -0,0 +1,58 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Bool, + Integer, + String, + Set, + Sequence +) +from openpyxl.descriptors.serialisable import Serialisable + + +class WebPublishItem(Serialisable): + tagname = "webPublishItem" + + id = Integer() + divId = String() + sourceType = Set(values=(['sheet', 'printArea', 'autoFilter', 'range', 'chart', 'pivotTable', 'query', 'label'])) + sourceRef = String() + sourceObject = String(allow_none=True) + destinationFile = String() + title = String(allow_none=True) + autoRepublish = Bool(allow_none=True) + + def __init__(self, + id=None, + divId=None, + sourceType=None, + sourceRef=None, + sourceObject=None, + destinationFile=None, + title=None, + autoRepublish=None, + ): + self.id = id + self.divId = divId + self.sourceType = sourceType + self.sourceRef = sourceRef + self.sourceObject = sourceObject + self.destinationFile = destinationFile + self.title = title + self.autoRepublish = autoRepublish + + +class WebPublishItems(Serialisable): + tagname = "WebPublishItems" + + count = Integer(allow_none=True) + webPublishItem = Sequence(expected_type=WebPublishItem, ) + + __elements__ = ('webPublishItem',) + + def __init__(self, + count=None, + webPublishItem=None, + ): + self.count = len(webPublishItem) + self.webPublishItem = webPublishItem diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/relation.py b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/relation.py new file mode 100644 index 00000000..47f5f3d9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/relation.py @@ -0,0 +1,97 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Integer, + Alias +) +from openpyxl.descriptors.excel import Relation +from openpyxl.descriptors.serialisable import Serialisable + + +class SheetBackgroundPicture(Serialisable): + tagname = "picture" + id = Relation() + + def __init__(self, id): + self.id = id + + +class DrawingHF(Serialisable): + id = Relation() + lho = Integer(allow_none=True) + leftHeaderOddPages = Alias('lho') + lhe = Integer(allow_none=True) + leftHeaderEvenPages = Alias('lhe') + lhf = Integer(allow_none=True) + leftHeaderFirstPage = Alias('lhf') + cho = Integer(allow_none=True) + centerHeaderOddPages = Alias('cho') + che = Integer(allow_none=True) + centerHeaderEvenPages = Alias('che') + chf = Integer(allow_none=True) + centerHeaderFirstPage = Alias('chf') + rho = Integer(allow_none=True) + rightHeaderOddPages = Alias('rho') + rhe = Integer(allow_none=True) + rightHeaderEvenPages = Alias('rhe') + rhf = Integer(allow_none=True) + rightHeaderFirstPage = Alias('rhf') + lfo = Integer(allow_none=True) + leftFooterOddPages = Alias('lfo') + lfe = Integer(allow_none=True) + leftFooterEvenPages = Alias('lfe') + lff = Integer(allow_none=True) + leftFooterFirstPage = Alias('lff') + cfo = Integer(allow_none=True) + centerFooterOddPages = Alias('cfo') + cfe = Integer(allow_none=True) + centerFooterEvenPages = Alias('cfe') + cff = Integer(allow_none=True) + centerFooterFirstPage = Alias('cff') + rfo = Integer(allow_none=True) + rightFooterOddPages = Alias('rfo') + rfe = Integer(allow_none=True) + rightFooterEvenPages = Alias('rfe') + rff = Integer(allow_none=True) + rightFooterFirstPage = Alias('rff') + + def __init__(self, + id=None, + lho=None, + lhe=None, + lhf=None, + cho=None, + che=None, + chf=None, + rho=None, + rhe=None, + rhf=None, + lfo=None, + lfe=None, + lff=None, + cfo=None, + cfe=None, + cff=None, + rfo=None, + rfe=None, + rff=None, + ): + self.id = id + self.lho = lho + self.lhe = lhe + self.lhf = lhf + self.cho = cho + self.che = che + self.chf = chf + self.rho = rho + self.rhe = rhe + self.rhf = rhf + self.lfo = lfo + self.lfe = lfe + self.lff = lff + self.cfo = cfo + self.cfe = cfe + self.cff = cff + self.rfo = rfo + self.rfe = rfe + self.rff = rff diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/views.py b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/views.py new file mode 100644 index 00000000..59289222 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/chartsheet/views.py @@ -0,0 +1,51 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Bool, + Integer, + Typed, + Sequence +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.serialisable import Serialisable + + +class ChartsheetView(Serialisable): + tagname = "sheetView" + + tabSelected = Bool(allow_none=True) + zoomScale = Integer(allow_none=True) + workbookViewId = Integer() + zoomToFit = Bool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + tabSelected=None, + zoomScale=None, + workbookViewId=0, + zoomToFit=True, + extLst=None, + ): + self.tabSelected = tabSelected + self.zoomScale = zoomScale + self.workbookViewId = workbookViewId + self.zoomToFit = zoomToFit + + +class ChartsheetViewList(Serialisable): + tagname = "sheetViews" + + sheetView = Sequence(expected_type=ChartsheetView, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('sheetView',) + + def __init__(self, + sheetView=None, + extLst=None, + ): + if sheetView is None: + sheetView = [ChartsheetView()] + self.sheetView = sheetView diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py new file mode 100644 index 00000000..288bdf1d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .comments import Comment diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/author.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/author.py new file mode 100644 index 00000000..9155fa5a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/author.py @@ -0,0 +1,21 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence, + Alias +) + + +class AuthorList(Serialisable): + + tagname = "authors" + + author = Sequence(expected_type=str) + authors = Alias("author") + + def __init__(self, + author=(), + ): + self.author = author diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py new file mode 100644 index 00000000..67dccc55 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py @@ -0,0 +1,211 @@ +# Copyright (c) 2010-2024 openpyxl + +## Incomplete! +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + Set, + String, + Bool, +) +from openpyxl.descriptors.excel import Guid, ExtensionList +from openpyxl.descriptors.sequence import NestedSequence + +from openpyxl.utils.indexed_list import IndexedList +from openpyxl.xml.constants import SHEET_MAIN_NS + +from openpyxl.cell.text import Text +from .author import AuthorList +from .comments import Comment +from .shape_writer import ShapeWriter + + +class Properties(Serialisable): + + 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) + altText = String(allow_none=True) + textHAlign = Set(values=(['left', 'center', 'right', 'justify', 'distributed'])) + textVAlign = Set(values=(['top', 'center', 'bottom', 'justify', 'distributed'])) + lockText = Bool(allow_none=True) + justLastX = Bool(allow_none=True) + autoScale = Bool(allow_none=True) + rowHidden = Bool(allow_none=True) + colHidden = Bool(allow_none=True) + # anchor = Typed(expected_type=ObjectAnchor, ) + + __elements__ = ('anchor',) + + def __init__(self, + locked=None, + defaultSize=None, + _print=None, + disabled=None, + uiObject=None, + autoFill=None, + autoLine=None, + altText=None, + textHAlign=None, + textVAlign=None, + lockText=None, + justLastX=None, + autoScale=None, + rowHidden=None, + colHidden=None, + anchor=None, + ): + self.locked = locked + self.defaultSize = defaultSize + self._print = _print + self.disabled = disabled + self.uiObject = uiObject + self.autoFill = autoFill + self.autoLine = autoLine + self.altText = altText + self.textHAlign = textHAlign + self.textVAlign = textVAlign + self.lockText = lockText + self.justLastX = justLastX + self.autoScale = autoScale + self.rowHidden = rowHidden + self.colHidden = colHidden + self.anchor = anchor + + +class CommentRecord(Serialisable): + + tagname = "comment" + + ref = String() + authorId = Integer() + guid = Guid(allow_none=True) + shapeId = Integer(allow_none=True) + text = Typed(expected_type=Text) + commentPr = Typed(expected_type=Properties, allow_none=True) + author = String(allow_none=True) + + __elements__ = ('text', 'commentPr') + __attrs__ = ('ref', 'authorId', 'guid', 'shapeId') + + def __init__(self, + ref="", + authorId=0, + guid=None, + shapeId=0, + text=None, + commentPr=None, + author=None, + height=79, + width=144 + ): + self.ref = ref + self.authorId = authorId + self.guid = guid + self.shapeId = shapeId + if text is None: + text = Text() + self.text = text + self.commentPr = commentPr + self.author = author + self.height = height + self.width = width + + + @classmethod + def from_cell(cls, cell): + """ + Class method to convert cell comment + """ + comment = cell._comment + ref = cell.coordinate + self = cls(ref=ref, author=comment.author) + self.text.t = comment.content + self.height = comment.height + self.width = comment.width + return self + + + @property + def content(self): + """ + Remove all inline formatting and stuff + """ + return self.text.content + + +class CommentSheet(Serialisable): + + tagname = "comments" + + authors = Typed(expected_type=AuthorList) + commentList = NestedSequence(expected_type=CommentRecord, count=0) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + _id = None + _path = "/xl/comments/comment{0}.xml" + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" + _rel_type = "comments" + _rel_id = None + + __elements__ = ('authors', 'commentList') + + def __init__(self, + authors=None, + commentList=None, + extLst=None, + ): + self.authors = authors + self.commentList = commentList + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def comments(self): + """ + Return a dictionary of comments keyed by coord + """ + authors = self.authors.author + + for c in self.commentList: + yield c.ref, Comment(c.content, authors[c.authorId], c.height, c.width) + + + @classmethod + def from_comments(cls, comments): + """ + Create a comment sheet from a list of comments for a particular worksheet + """ + authors = IndexedList() + + # dedupe authors and get indexes + for comment in comments: + comment.authorId = authors.add(comment.author) + + return cls(authors=AuthorList(authors), commentList=comments) + + + def write_shapes(self, vml=None): + """ + Create the VML for comments + """ + sw = ShapeWriter(self.comments) + return sw.write(vml) + + + @property + def path(self): + """ + Return path within the archive + """ + return self._path.format(self._id) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/comments.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/comments.py new file mode 100644 index 00000000..192bbc46 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/comments.py @@ -0,0 +1,62 @@ +# Copyright (c) 2010-2024 openpyxl + + +class Comment: + + _parent = None + + def __init__(self, text, author, height=79, width=144): + self.content = text + self.author = author + self.height = height + self.width = width + + + @property + def parent(self): + return self._parent + + + def __eq__(self, other): + return ( + self.content == other.content + and self.author == other.author + ) + + def __repr__(self): + return "Comment: {0} by {1}".format(self.content, self.author) + + + def __copy__(self): + """Create a detached copy of this comment.""" + clone = self.__class__(self.content, self.author, self.height, self.width) + return clone + + + def bind(self, cell): + """ + Bind comment to a particular cell + """ + if cell is not None and self._parent is not None and self._parent != cell: + fmt = "Comment already assigned to {0} in worksheet {1}. Cannot assign a comment to more than one cell" + raise AttributeError(fmt.format(cell.coordinate, cell.parent.title)) + self._parent = cell + + + def unbind(self): + """ + Unbind a comment from a cell + """ + self._parent = None + + + @property + def text(self): + """ + Any comment text stripped of all formatting. + """ + return self.content + + @text.setter + def text(self, value): + self.content = value diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py new file mode 100644 index 00000000..cebfbc3d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py @@ -0,0 +1,112 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.functions import ( + Element, + SubElement, + tostring, +) + +from openpyxl.utils import coordinate_to_tuple + +vmlns = "urn:schemas-microsoft-com:vml" +officens = "urn:schemas-microsoft-com:office:office" +excelns = "urn:schemas-microsoft-com:office:excel" + + +class ShapeWriter: + """ + Create VML for comments + """ + + vml = None + vml_path = None + + + def __init__(self, comments): + self.comments = comments + + + def add_comment_shapetype(self, root): + shape_layout = SubElement(root, "{%s}shapelayout" % officens, + {"{%s}ext" % vmlns: "edit"}) + SubElement(shape_layout, + "{%s}idmap" % officens, + {"{%s}ext" % vmlns: "edit", "data": "1"}) + shape_type = SubElement(root, + "{%s}shapetype" % vmlns, + {"id": "_x0000_t202", + "coordsize": "21600,21600", + "{%s}spt" % officens: "202", + "path": "m,l,21600r21600,l21600,xe"}) + SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"}) + SubElement(shape_type, + "{%s}path" % vmlns, + {"gradientshapeok": "t", + "{%s}connecttype" % officens: "rect"}) + + + def add_comment_shape(self, root, idx, coord, height, width): + row, col = coordinate_to_tuple(coord) + row -= 1 + col -= 1 + shape = _shape_factory(row, col, height, width) + + shape.set('id', "_x0000_s%04d" % idx) + root.append(shape) + + + def write(self, root): + + if not hasattr(root, "findall"): + root = Element("xml") + + # Remove any existing comment shapes + comments = root.findall("{%s}shape[@type='#_x0000_t202']" % vmlns) + for c in comments: + root.remove(c) + + # check whether comments shape type already exists + shape_types = root.find("{%s}shapetype[@id='_x0000_t202']" % vmlns) + if shape_types is None: + self.add_comment_shapetype(root) + + for idx, (coord, comment) in enumerate(self.comments, 1026): + self.add_comment_shape(root, idx, coord, comment.height, comment.width) + + return tostring(root) + + +def _shape_factory(row, column, height, width): + style = ("position:absolute; " + "margin-left:59.25pt;" + "margin-top:1.5pt;" + "width:{width}px;" + "height:{height}px;" + "z-index:1;" + "visibility:hidden").format(height=height, + width=width) + attrs = { + "type": "#_x0000_t202", + "style": style, + "fillcolor": "#ffffe1", + "{%s}insetmode" % officens: "auto" + } + shape = Element("{%s}shape" % vmlns, attrs) + + SubElement(shape, "{%s}fill" % vmlns, + {"color2": "#ffffe1"}) + SubElement(shape, "{%s}shadow" % vmlns, + {"color": "black", "obscured": "t"}) + SubElement(shape, "{%s}path" % vmlns, + {"{%s}connecttype" % officens: "none"}) + textbox = SubElement(shape, "{%s}textbox" % vmlns, + {"style": "mso-direction-alt:auto"}) + SubElement(textbox, "div", {"style": "text-align:left"}) + client_data = SubElement(shape, "{%s}ClientData" % excelns, + {"ObjectType": "Note"}) + SubElement(client_data, "{%s}MoveWithCells" % excelns) + SubElement(client_data, "{%s}SizeWithCells" % excelns) + SubElement(client_data, "{%s}AutoFill" % excelns).text = "False" + SubElement(client_data, "{%s}Row" % excelns).text = str(row) + SubElement(client_data, "{%s}Column" % excelns).text = str(column) + return shape diff --git a/.venv/lib/python3.12/site-packages/openpyxl/compat/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/compat/__init__.py new file mode 100644 index 00000000..dac09096 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/compat/__init__.py @@ -0,0 +1,54 @@ +# Copyright (c) 2010-2024 openpyxl + +from .numbers import NUMERIC_TYPES +from .strings import safe_string + +import warnings +from functools import wraps +import inspect + + +class DummyCode: + + pass + + +# from https://github.com/tantale/deprecated/blob/master/deprecated/__init__.py +# with an enhancement to update docstrings of deprecated functions +string_types = (type(b''), type(u'')) +def deprecated(reason): + + if isinstance(reason, string_types): + + def decorator(func1): + + if inspect.isclass(func1): + fmt1 = "Call to deprecated class {name} ({reason})." + else: + fmt1 = "Call to deprecated function {name} ({reason})." + + @wraps(func1) + def new_func1(*args, **kwargs): + #warnings.simplefilter('default', DeprecationWarning) + warnings.warn( + fmt1.format(name=func1.__name__, reason=reason), + category=DeprecationWarning, + stacklevel=2 + ) + return func1(*args, **kwargs) + + # Enhance docstring with a deprecation note + deprecationNote = "\n\n.. note::\n Deprecated: " + reason + if new_func1.__doc__: + new_func1.__doc__ += deprecationNote + else: + new_func1.__doc__ = deprecationNote + return new_func1 + + return decorator + + elif inspect.isclass(reason) or inspect.isfunction(reason): + raise TypeError("Reason for deprecation must be supplied") + + else: + raise TypeError(repr(type(reason))) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/compat/abc.py b/.venv/lib/python3.12/site-packages/openpyxl/compat/abc.py new file mode 100644 index 00000000..36a47f3f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/compat/abc.py @@ -0,0 +1,8 @@ +# Copyright (c) 2010-2024 openpyxl + + +try: + from abc import ABC +except ImportError: + from abc import ABCMeta + ABC = ABCMeta('ABC', (object, ), {}) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/compat/numbers.py b/.venv/lib/python3.12/site-packages/openpyxl/compat/numbers.py new file mode 100644 index 00000000..7d583451 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/compat/numbers.py @@ -0,0 +1,43 @@ +# Copyright (c) 2010-2024 openpyxl + +from decimal import Decimal + +NUMERIC_TYPES = (int, float, Decimal) + + +try: + import numpy + NUMPY = True +except ImportError: + NUMPY = False + + +if NUMPY: + NUMERIC_TYPES = NUMERIC_TYPES + (numpy.short, + numpy.ushort, + numpy.intc, + numpy.uintc, + numpy.int_, + numpy.uint, + numpy.longlong, + numpy.ulonglong, + numpy.half, + numpy.float16, + numpy.single, + numpy.double, + numpy.longdouble, + numpy.int8, + numpy.int16, + numpy.int32, + numpy.int64, + numpy.uint8, + numpy.uint16, + numpy.uint32, + numpy.uint64, + numpy.intp, + numpy.uintp, + numpy.float32, + numpy.float64, + numpy.bool_, + numpy.floating, + numpy.integer) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/compat/product.py b/.venv/lib/python3.12/site-packages/openpyxl/compat/product.py new file mode 100644 index 00000000..68fdae9f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/compat/product.py @@ -0,0 +1,17 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +math.prod equivalent for < Python 3.8 +""" + +import functools +import operator + +def product(sequence): + return functools.reduce(operator.mul, sequence) + + +try: + from math import prod +except ImportError: + prod = product diff --git a/.venv/lib/python3.12/site-packages/openpyxl/compat/singleton.py b/.venv/lib/python3.12/site-packages/openpyxl/compat/singleton.py new file mode 100644 index 00000000..1fe6a908 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/compat/singleton.py @@ -0,0 +1,40 @@ +# Copyright (c) 2010-2024 openpyxl + +import weakref + + +class Singleton(type): + """ + Singleton metaclass + Based on Python Cookbook 3rd Edition Recipe 9.13 + Only one instance of a class can exist. Does not work with __slots__ + """ + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self.__instance = None + + def __call__(self, *args, **kw): + if self.__instance is None: + self.__instance = super().__call__(*args, **kw) + return self.__instance + + +class Cached(type): + """ + Caching metaclass + Child classes will only create new instances of themselves if + one doesn't already exist. Does not work with __slots__ + """ + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self.__cache = weakref.WeakValueDictionary() + + def __call__(self, *args): + if args in self.__cache: + return self.__cache[args] + + obj = super().__call__(*args) + self.__cache[args] = obj + return obj diff --git a/.venv/lib/python3.12/site-packages/openpyxl/compat/strings.py b/.venv/lib/python3.12/site-packages/openpyxl/compat/strings.py new file mode 100644 index 00000000..2cc9d60e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/compat/strings.py @@ -0,0 +1,25 @@ +# Copyright (c) 2010-2024 openpyxl + +from datetime import datetime +from math import isnan, isinf +import sys + +VER = sys.version_info + +from .numbers import NUMERIC_TYPES + + +def safe_string(value): + """Safely and consistently format numeric values""" + if isinstance(value, NUMERIC_TYPES): + if isnan(value) or isinf(value): + value = "" + else: + value = "%.16g" % value + elif value is None: + value = "none" + elif isinstance(value, datetime): + value = value.isoformat() + elif not isinstance(value, str): + value = str(value) + return value diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py new file mode 100644 index 00000000..df86a3c7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py @@ -0,0 +1,58 @@ +# Copyright (c) 2010-2024 openpyxl + +from .base import * +from .sequence import Sequence + + +class MetaStrict(type): + + def __new__(cls, clsname, bases, methods): + for k, v in methods.items(): + if isinstance(v, Descriptor): + v.name = k + return type.__new__(cls, clsname, bases, methods) + + +class Strict(metaclass=MetaStrict): + + pass + + +class MetaSerialisable(type): + + def __new__(cls, clsname, bases, methods): + attrs = [] + nested = [] + elements = [] + namespaced = [] + for k, v in methods.items(): + if isinstance(v, Descriptor): + ns= getattr(v, 'namespace', None) + if ns: + namespaced.append((k, "{%s}%s" % (ns, k))) + if getattr(v, 'nested', False): + nested.append(k) + elements.append(k) + elif isinstance(v, Sequence): + elements.append(k) + elif isinstance(v, Typed): + if hasattr(v.expected_type, 'to_tree'): + elements.append(k) + elif isinstance(v.expected_type, tuple): + if any((hasattr(el, "to_tree") for el in v.expected_type)): + # don't bind elements as attrs + continue + else: + attrs.append(k) + else: + if not isinstance(v, Alias): + attrs.append(k) + + if methods.get('__attrs__') is None: + methods['__attrs__'] = tuple(attrs) + methods['__namespaced__'] = tuple(namespaced) + if methods.get('__nested__') is None: + methods['__nested__'] = tuple(sorted(nested)) + if methods.get('__elements__') is None: + methods['__elements__'] = tuple(sorted(elements)) + return MetaStrict.__new__(cls, clsname, bases, methods) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py new file mode 100644 index 00000000..f1e86ed3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py @@ -0,0 +1,272 @@ +# Copyright (c) 2010-2024 openpyxl + + +""" +Based on Python Cookbook 3rd Edition, 8.13 +http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130 +""" + +import datetime +import re + +from openpyxl import DEBUG +from openpyxl.utils.datetime import from_ISO8601 + +from .namespace import namespaced + +class Descriptor: + + def __init__(self, name=None, **kw): + self.name = name + for k, v in kw.items(): + setattr(self, k, v) + + def __set__(self, instance, value): + instance.__dict__[self.name] = value + + +class Typed(Descriptor): + """Values must of a particular type""" + + expected_type = type(None) + allow_none = False + nested = False + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self.__doc__ = f"Values must be of type {self.expected_type}" + + def __set__(self, instance, value): + if not isinstance(value, self.expected_type): + if (not self.allow_none + or (self.allow_none and value is not None)): + msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but value is {type(value)}" + if DEBUG: + msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but {value} is {type(value)}" + raise TypeError(msg) + super().__set__(instance, value) + + def __repr__(self): + return self.__doc__ + + +def _convert(expected_type, value): + """ + Check value is of or can be converted to expected type. + """ + if not isinstance(value, expected_type): + try: + value = expected_type(value) + except: + raise TypeError('expected ' + str(expected_type)) + return value + + +class Convertible(Typed): + """Values must be convertible to a particular type""" + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + super().__set__(instance, value) + + +class Max(Convertible): + """Values must be less than a `max` value""" + + expected_type = float + allow_none = False + + def __init__(self, **kw): + if 'max' not in kw and not hasattr(self, 'max'): + raise TypeError('missing max value') + super().__init__(**kw) + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + if value > self.max: + raise ValueError('Max value is {0}'.format(self.max)) + super().__set__(instance, value) + + +class Min(Convertible): + """Values must be greater than a `min` value""" + + expected_type = float + allow_none = False + + def __init__(self, **kw): + if 'min' not in kw and not hasattr(self, 'min'): + raise TypeError('missing min value') + super().__init__(**kw) + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + if value < self.min: + raise ValueError('Min value is {0}'.format(self.min)) + super().__set__(instance, value) + + +class MinMax(Min, Max): + """Values must be greater than `min` value and less than a `max` one""" + pass + + +class Set(Descriptor): + """Value can only be from a set of know values""" + + def __init__(self, name=None, **kw): + if not 'values' in kw: + raise TypeError("missing set of values") + kw['values'] = set(kw['values']) + super().__init__(name, **kw) + self.__doc__ = "Value must be one of {0}".format(self.values) + + def __set__(self, instance, value): + if value not in self.values: + raise ValueError(self.__doc__) + super().__set__(instance, value) + + +class NoneSet(Set): + + """'none' will be treated as None""" + + def __init__(self, name=None, **kw): + super().__init__(name, **kw) + self.values.add(None) + + def __set__(self, instance, value): + if value == 'none': + value = None + super().__set__(instance, value) + + +class Integer(Convertible): + + expected_type = int + + +class Float(Convertible): + + expected_type = float + + +class Bool(Convertible): + + expected_type = bool + + def __set__(self, instance, value): + if isinstance(value, str): + if value in ('false', 'f', '0'): + value = False + super().__set__(instance, value) + + +class String(Typed): + + expected_type = str + + +class Text(String, Convertible): + + pass + + +class ASCII(Typed): + + expected_type = bytes + + +class Tuple(Typed): + + expected_type = tuple + + +class Length(Descriptor): + + def __init__(self, name=None, **kw): + if "length" not in kw: + raise TypeError("value length must be supplied") + super().__init__(**kw) + + + def __set__(self, instance, value): + if len(value) != self.length: + raise ValueError("Value must be length {0}".format(self.length)) + super().__set__(instance, value) + + +class Default(Typed): + """ + When called returns an instance of the expected type. + Additional default values can be passed in to the descriptor + """ + + def __init__(self, name=None, **kw): + if "defaults" not in kw: + kw['defaults'] = {} + super().__init__(**kw) + + def __call__(self): + return self.expected_type() + + +class Alias(Descriptor): + """ + Aliases can be used when either the desired attribute name is not allowed + or confusing in Python (eg. "type") or a more descriptive name is desired + (eg. "underline" for "u") + """ + + def __init__(self, alias): + self.alias = alias + + def __set__(self, instance, value): + setattr(instance, self.alias, value) + + def __get__(self, instance, cls): + return getattr(instance, self.alias) + + +class MatchPattern(Descriptor): + """Values must match a regex pattern """ + allow_none = False + + def __init__(self, name=None, **kw): + if 'pattern' not in kw and not hasattr(self, 'pattern'): + raise TypeError('missing pattern value') + + super().__init__(name, **kw) + self.test_pattern = re.compile(self.pattern, re.VERBOSE) + + + def __set__(self, instance, value): + + if value is None and not self.allow_none: + raise ValueError("Value must not be none") + + if ((self.allow_none and value is not None) + or not self.allow_none): + if not self.test_pattern.match(value): + raise ValueError('Value does not match pattern {0}'.format(self.pattern)) + + super().__set__(instance, value) + + +class DateTime(Typed): + + expected_type = datetime.datetime + + def __set__(self, instance, value): + if value is not None and isinstance(value, str): + try: + value = from_ISO8601(value) + except ValueError: + raise ValueError("Value must be ISO datetime format") + super().__set__(instance, value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py new file mode 100644 index 00000000..4b1839f5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py @@ -0,0 +1,41 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Utility list for top level containers that contain one type of element + +Provides the necessary API to read and write XML +""" + +from openpyxl.xml.functions import Element + + +class ElementList(list): + + + @property + def tagname(self): + raise NotImplementedError + + + @property + def expected_type(self): + raise NotImplementedError + + + @classmethod + def from_tree(cls, tree): + l = [cls.expected_type.from_tree(el) for el in tree] + return cls(l) + + + def to_tree(self): + container = Element(self.tagname) + for el in self: + container.append(el.to_tree()) + return container + + + def append(self, value): + if not isinstance(value, self.expected_type): + raise TypeError(f"Value must of type {self.expected_type} {type(value)} provided") + super().append(value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py new file mode 100644 index 00000000..d8aa2028 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py @@ -0,0 +1,112 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Excel specific descriptors +""" + +from openpyxl.xml.constants import REL_NS +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element + +from . import ( + MatchPattern, + MinMax, + Integer, + String, + Sequence, +) +from .serialisable import Serialisable + + +class HexBinary(MatchPattern): + + pattern = "[0-9a-fA-F]+$" + + +class UniversalMeasure(MatchPattern): + + pattern = r"[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)" + + +class TextPoint(MinMax): + """ + Size in hundredths of points. + In theory other units of measurement can be used but these are unbounded + """ + expected_type = int + + min = -400000 + max = 400000 + + +Coordinate = Integer + + +class Percentage(MinMax): + + pattern = r"((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%" # strict + min = -1000000 + max = 1000000 + + def __set__(self, instance, value): + if isinstance(value, str) and "%" in value: + value = value.replace("%", "") + value = int(float(value) * 1000) + super().__set__(instance, value) + + +class Extension(Serialisable): + + uri = String() + + def __init__(self, + uri=None, + ): + self.uri = uri + + +class ExtensionList(Serialisable): + + ext = Sequence(expected_type=Extension) + + def __init__(self, + ext=(), + ): + self.ext = ext + + +class Relation(String): + + namespace = REL_NS + allow_none = True + + +class Base64Binary(MatchPattern): + # http://www.w3.org/TR/xmlschema11-2/#nt-Base64Binary + pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$" + + +class Guid(MatchPattern): + # https://msdn.microsoft.com/en-us/library/dd946381(v=office.12).aspx + pattern = r"{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}" + + +class CellRange(MatchPattern): + + pattern = r"^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$" + allow_none = True + + def __set__(self, instance, value): + + if value is not None: + value = value.upper() + super().__set__(instance, value) + + +def _explicit_none(tagname, value, namespace=None): + """ + Override serialisation because explicit none required + """ + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return Element(tagname, val=safe_string(value)) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py new file mode 100644 index 00000000..93cc9e41 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py @@ -0,0 +1,12 @@ +# Copyright (c) 2010-2024 openpyxl + + +def namespaced(obj, tagname, namespace=None): + """ + Utility to create a namespaced tag for an object + """ + + namespace = getattr(obj, "namespace", None) or namespace + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return tagname diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py new file mode 100644 index 00000000..bda63a2d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py @@ -0,0 +1,129 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Generic serialisable classes +""" +from .base import ( + Convertible, + Bool, + Descriptor, + NoneSet, + MinMax, + Set, + Float, + Integer, + String, + ) +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element, localname, whitespace + + +class Nested(Descriptor): + + nested = True + attribute = "val" + + def __set__(self, instance, value): + if hasattr(value, "tag"): + tag = localname(value) + if tag != self.name: + raise ValueError("Tag does not match attribute") + + value = self.from_tree(value) + super().__set__(instance, value) + + + def from_tree(self, node): + return node.get(self.attribute) + + + def to_tree(self, tagname=None, value=None, namespace=None): + namespace = getattr(self, "namespace", namespace) + if value is not None: + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + value = safe_string(value) + return Element(tagname, {self.attribute:value}) + + +class NestedValue(Nested, Convertible): + """ + Nested tag storing the value on the 'val' attribute + """ + pass + + +class NestedText(NestedValue): + """ + Represents any nested tag with the value as the contents of the tag + """ + + + def from_tree(self, node): + return node.text + + + def to_tree(self, tagname=None, value=None, namespace=None): + namespace = getattr(self, "namespace", namespace) + if value is not None: + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + el = Element(tagname) + el.text = safe_string(value) + whitespace(el) + return el + + +class NestedFloat(NestedValue, Float): + + pass + + +class NestedInteger(NestedValue, Integer): + + pass + + +class NestedString(NestedValue, String): + + pass + + +class NestedBool(NestedValue, Bool): + + + def from_tree(self, node): + return node.get("val", True) + + +class NestedNoneSet(Nested, NoneSet): + + pass + + +class NestedSet(Nested, Set): + + pass + + +class NestedMinMax(Nested, MinMax): + + pass + + +class EmptyTag(Nested, Bool): + + """ + Boolean if a tag exists or not. + """ + + def from_tree(self, node): + return True + + + def to_tree(self, tagname=None, value=None, namespace=None): + if value: + namespace = getattr(self, "namespace", namespace) + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return Element(tagname) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py new file mode 100644 index 00000000..d77116b2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py @@ -0,0 +1,136 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element +from openpyxl.utils.indexed_list import IndexedList + +from .base import Descriptor, Alias, _convert +from .namespace import namespaced + + +class Sequence(Descriptor): + """ + A sequence (list or tuple) that may only contain objects of the declared + type + """ + + expected_type = type(None) + seq_types = (list, tuple) + idx_base = 0 + unique = False + container = list + + + def __set__(self, instance, seq): + if not isinstance(seq, self.seq_types): + raise TypeError("Value must be a sequence") + seq = self.container(_convert(self.expected_type, value) for value in seq) + if self.unique: + seq = IndexedList(seq) + + super().__set__(instance, seq) + + + def to_tree(self, tagname, obj, namespace=None): + """ + Convert the sequence represented by the descriptor to an XML element + """ + for idx, v in enumerate(obj, self.idx_base): + if hasattr(v, "to_tree"): + el = v.to_tree(tagname, idx) + else: + tagname = namespaced(obj, tagname, namespace) + el = Element(tagname) + el.text = safe_string(v) + yield el + + +class UniqueSequence(Sequence): + """ + Use a set to keep values unique + """ + seq_types = (list, tuple, set) + container = set + + +class ValueSequence(Sequence): + """ + A sequence of primitive types that are stored as a single attribute. + "val" is the default attribute + """ + + attribute = "val" + + + def to_tree(self, tagname, obj, namespace=None): + tagname = namespaced(self, tagname, namespace) + for v in obj: + yield Element(tagname, {self.attribute:safe_string(v)}) + + + def from_tree(self, node): + + return node.get(self.attribute) + + +class NestedSequence(Sequence): + """ + Wrap a sequence in an containing object + """ + + count = False + + def to_tree(self, tagname, obj, namespace=None): + tagname = namespaced(self, tagname, namespace) + container = Element(tagname) + if self.count: + container.set('count', str(len(obj))) + for v in obj: + container.append(v.to_tree()) + return container + + + def from_tree(self, node): + return [self.expected_type.from_tree(el) for el in node] + + +class MultiSequence(Sequence): + """ + Sequences can contain objects with different tags + """ + + def __set__(self, instance, seq): + if not isinstance(seq, (tuple, list)): + raise ValueError("Value must be a sequence") + seq = list(seq) + Descriptor.__set__(self, instance, seq) + + + def to_tree(self, tagname, obj, namespace=None): + """ + Convert the sequence represented by the descriptor to an XML element + """ + for v in obj: + el = v.to_tree(namespace=namespace) + yield el + + +class MultiSequencePart(Alias): + """ + Allow a multisequence to be built up from parts + + Excluded from the instance __elements__ or __attrs__ as is effectively an Alias + """ + + def __init__(self, expected_type, store): + self.expected_type = expected_type + self.store = store + + + def __set__(self, instance, value): + value = _convert(self.expected_type, value) + instance.__dict__[self.store].append(value) + + + def __get__(self, instance, cls): + return self diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py new file mode 100644 index 00000000..1bc9ef0d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py @@ -0,0 +1,240 @@ +# Copyright (c) 2010-2024 openpyxl + +from copy import copy +from keyword import kwlist +KEYWORDS = frozenset(kwlist) + +from . import Descriptor +from . import MetaSerialisable +from .sequence import ( + Sequence, + NestedSequence, + MultiSequencePart, +) +from .namespace import namespaced + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import ( + Element, + localname, +) + +seq_types = (list, tuple) + +class Serialisable(metaclass=MetaSerialisable): + """ + Objects can serialise to XML their attributes and child objects. + The following class attributes are created by the metaclass at runtime: + __attrs__ = attributes + __nested__ = single-valued child treated as an attribute + __elements__ = child elements + """ + + __attrs__ = None + __nested__ = None + __elements__ = None + __namespaced__ = None + + idx_base = 0 + + @property + def tagname(self): + raise(NotImplementedError) + + namespace = None + + @classmethod + def from_tree(cls, node): + """ + Create object from XML + """ + # strip known namespaces from attributes + attrib = dict(node.attrib) + for key, ns in cls.__namespaced__: + if ns in attrib: + attrib[key] = attrib[ns] + del attrib[ns] + + # strip attributes with unknown namespaces + for key in list(attrib): + if key.startswith('{'): + del attrib[key] + elif key in KEYWORDS: + attrib["_" + key] = attrib[key] + del attrib[key] + elif "-" in key: + n = key.replace("-", "_") + attrib[n] = attrib[key] + del attrib[key] + + if node.text and "attr_text" in cls.__attrs__: + attrib["attr_text"] = node.text + + for el in node: + tag = localname(el) + if tag in KEYWORDS: + tag = "_" + tag + desc = getattr(cls, tag, None) + if desc is None or isinstance(desc, property): + continue + + if hasattr(desc, 'from_tree'): + #descriptor manages conversion + obj = desc.from_tree(el) + else: + if hasattr(desc.expected_type, "from_tree"): + #complex type + obj = desc.expected_type.from_tree(el) + else: + #primitive + obj = el.text + + if isinstance(desc, NestedSequence): + attrib[tag] = obj + elif isinstance(desc, Sequence): + attrib.setdefault(tag, []) + attrib[tag].append(obj) + elif isinstance(desc, MultiSequencePart): + attrib.setdefault(desc.store, []) + attrib[desc.store].append(obj) + else: + attrib[tag] = obj + + return cls(**attrib) + + + def to_tree(self, tagname=None, idx=None, namespace=None): + + if tagname is None: + tagname = self.tagname + + # keywords have to be masked + if tagname.startswith("_"): + tagname = tagname[1:] + + tagname = namespaced(self, tagname, namespace) + namespace = getattr(self, "namespace", namespace) + + attrs = dict(self) + for key, ns in self.__namespaced__: + if key in attrs: + attrs[ns] = attrs[key] + del attrs[key] + + el = Element(tagname, attrs) + if "attr_text" in self.__attrs__: + el.text = safe_string(getattr(self, "attr_text")) + + for child_tag in self.__elements__: + desc = getattr(self.__class__, child_tag, None) + obj = getattr(self, child_tag) + if hasattr(desc, "namespace") and hasattr(obj, 'namespace'): + obj.namespace = desc.namespace + + if isinstance(obj, seq_types): + if isinstance(desc, NestedSequence): + # wrap sequence in container + if not obj: + continue + nodes = [desc.to_tree(child_tag, obj, namespace)] + elif isinstance(desc, Sequence): + # sequence + desc.idx_base = self.idx_base + nodes = (desc.to_tree(child_tag, obj, namespace)) + else: # property + nodes = (v.to_tree(child_tag, namespace) for v in obj) + for node in nodes: + el.append(node) + else: + if child_tag in self.__nested__: + node = desc.to_tree(child_tag, obj, namespace) + elif obj is None: + continue + else: + node = obj.to_tree(child_tag) + if node is not None: + el.append(node) + return el + + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if attr.startswith("_"): + attr = attr[1:] + elif attr != "attr_text" and "_" in attr: + desc = getattr(self.__class__, attr) + if getattr(desc, "hyphenated", False): + attr = attr.replace("_", "-") + if attr != "attr_text" and value is not None: + yield attr, safe_string(value) + + + def __eq__(self, other): + if not self.__class__ == other.__class__: + return False + elif not dict(self) == dict(other): + return False + for el in self.__elements__: + if getattr(self, el) != getattr(other, el): + return False + return True + + + def __ne__(self, other): + return not self == other + + + def __repr__(self): + s = u"<{0}.{1} object>\nParameters:".format( + self.__module__, + self.__class__.__name__ + ) + args = [] + for k in self.__attrs__ + self.__elements__: + v = getattr(self, k) + if isinstance(v, Descriptor): + v = None + args.append(u"{0}={1}".format(k, repr(v))) + args = u", ".join(args) + + return u"\n".join([s, args]) + + + def __hash__(self): + fields = [] + for attr in self.__attrs__ + self.__elements__: + val = getattr(self, attr) + if isinstance(val, list): + val = tuple(val) + fields.append(val) + + return hash(tuple(fields)) + + + def __add__(self, other): + if type(self) != type(other): + raise TypeError("Cannot combine instances of different types") + vals = {} + for attr in self.__attrs__: + vals[attr] = getattr(self, attr) or getattr(other, attr) + for el in self.__elements__: + a = getattr(self, el) + b = getattr(other, el) + if a and b: + vals[el] = a + b + else: + vals[el] = a or b + return self.__class__(**vals) + + + def __copy__(self): + # serialise to xml and back to avoid shallow copies + xml = self.to_tree(tagname="dummy") + cp = self.__class__.from_tree(xml) + # copy any non-persisted attributed + for k in self.__dict__: + if k not in self.__attrs__ + self.__elements__: + v = copy(getattr(self, k)) + setattr(cp, k, v) + return cp diff --git a/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py new file mode 100644 index 00000000..cadc1ef3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py @@ -0,0 +1,18 @@ +# Metaclass for mixing slots and descriptors +# From "Programming in Python 3" by Mark Summerfield Ch.8 p. 383 + +class AutoSlotProperties(type): + + def __new__(mcl, classname, bases, dictionary): + slots = list(dictionary.get("__slots__", [])) + for getter_name in [key for key in dictionary if key.startswith("get_")]: + name = getter_name + slots.append("__" + name) + getter = dictionary.pop(getter_name) + setter = dictionary.get(setter_name, None) + if (setter is not None + and isinstance(setter, collections.Callable)): + del dictionary[setter_name] + dictionary[name] = property(getter. setter) + dictionary["__slots__"] = tuple(slots) + return super().__new__(mcl, classname, bases, dictionary) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/__init__.py new file mode 100644 index 00000000..02f05876 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .drawing import Drawing diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/colors.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/colors.py new file mode 100644 index 00000000..19fa5e84 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/colors.py @@ -0,0 +1,435 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + Integer, + Set, + MinMax, +) +from openpyxl.descriptors.excel import Percentage +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedValue, + NestedInteger, + EmptyTag, +) + +from openpyxl.styles.colors import RGB +from openpyxl.xml.constants import DRAWING_NS + +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +PRESET_COLORS = [ + 'aliceBlue', 'antiqueWhite', 'aqua', 'aquamarine', + 'azure', 'beige', 'bisque', 'black', 'blanchedAlmond', 'blue', + 'blueViolet', 'brown', 'burlyWood', 'cadetBlue', 'chartreuse', + 'chocolate', 'coral', 'cornflowerBlue', 'cornsilk', 'crimson', 'cyan', + 'darkBlue', 'darkCyan', 'darkGoldenrod', 'darkGray', 'darkGrey', + 'darkGreen', 'darkKhaki', 'darkMagenta', 'darkOliveGreen', 'darkOrange', + 'darkOrchid', 'darkRed', 'darkSalmon', 'darkSeaGreen', 'darkSlateBlue', + 'darkSlateGray', 'darkSlateGrey', 'darkTurquoise', 'darkViolet', + 'dkBlue', 'dkCyan', 'dkGoldenrod', 'dkGray', 'dkGrey', 'dkGreen', + 'dkKhaki', 'dkMagenta', 'dkOliveGreen', 'dkOrange', 'dkOrchid', 'dkRed', + 'dkSalmon', 'dkSeaGreen', 'dkSlateBlue', 'dkSlateGray', 'dkSlateGrey', + 'dkTurquoise', 'dkViolet', 'deepPink', 'deepSkyBlue', 'dimGray', + 'dimGrey', 'dodgerBlue', 'firebrick', 'floralWhite', 'forestGreen', + 'fuchsia', 'gainsboro', 'ghostWhite', 'gold', 'goldenrod', 'gray', + 'grey', 'green', 'greenYellow', 'honeydew', 'hotPink', 'indianRed', + 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderBlush', 'lawnGreen', + 'lemonChiffon', 'lightBlue', 'lightCoral', 'lightCyan', + 'lightGoldenrodYellow', 'lightGray', 'lightGrey', 'lightGreen', + 'lightPink', 'lightSalmon', 'lightSeaGreen', 'lightSkyBlue', + 'lightSlateGray', 'lightSlateGrey', 'lightSteelBlue', 'lightYellow', + 'ltBlue', 'ltCoral', 'ltCyan', 'ltGoldenrodYellow', 'ltGray', 'ltGrey', + 'ltGreen', 'ltPink', 'ltSalmon', 'ltSeaGreen', 'ltSkyBlue', + 'ltSlateGray', 'ltSlateGrey', 'ltSteelBlue', 'ltYellow', 'lime', + 'limeGreen', 'linen', 'magenta', 'maroon', 'medAquamarine', 'medBlue', + 'medOrchid', 'medPurple', 'medSeaGreen', 'medSlateBlue', + 'medSpringGreen', 'medTurquoise', 'medVioletRed', 'mediumAquamarine', + 'mediumBlue', 'mediumOrchid', 'mediumPurple', 'mediumSeaGreen', + 'mediumSlateBlue', 'mediumSpringGreen', 'mediumTurquoise', + 'mediumVioletRed', 'midnightBlue', 'mintCream', 'mistyRose', 'moccasin', + 'navajoWhite', 'navy', 'oldLace', 'olive', 'oliveDrab', 'orange', + 'orangeRed', 'orchid', 'paleGoldenrod', 'paleGreen', 'paleTurquoise', + 'paleVioletRed', 'papayaWhip', 'peachPuff', 'peru', 'pink', 'plum', + 'powderBlue', 'purple', 'red', 'rosyBrown', 'royalBlue', 'saddleBrown', + 'salmon', 'sandyBrown', 'seaGreen', 'seaShell', 'sienna', 'silver', + 'skyBlue', 'slateBlue', 'slateGray', 'slateGrey', 'snow', 'springGreen', + 'steelBlue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', + 'wheat', 'white', 'whiteSmoke', 'yellow', 'yellowGreen' + ] + + +SCHEME_COLORS= ['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', 'accent3', + 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr', 'dk1', 'lt1', + 'dk2', 'lt2' + ] + + +class Transform(Serialisable): + + pass + + +class SystemColor(Serialisable): + + tagname = "sysClr" + namespace = DRAWING_NS + + # color transform options + tint = NestedInteger(allow_none=True) + shade = NestedInteger(allow_none=True) + comp = Typed(expected_type=Transform, allow_none=True) + inv = Typed(expected_type=Transform, allow_none=True) + gray = Typed(expected_type=Transform, allow_none=True) + alpha = NestedInteger(allow_none=True) + alphaOff = NestedInteger(allow_none=True) + alphaMod = NestedInteger(allow_none=True) + hue = NestedInteger(allow_none=True) + hueOff = NestedInteger(allow_none=True) + hueMod = NestedInteger(allow_none=True) + sat = NestedInteger(allow_none=True) + satOff = NestedInteger(allow_none=True) + satMod = NestedInteger(allow_none=True) + lum = NestedInteger(allow_none=True) + lumOff = NestedInteger(allow_none=True) + lumMod = NestedInteger(allow_none=True) + red = NestedInteger(allow_none=True) + redOff = NestedInteger(allow_none=True) + redMod = NestedInteger(allow_none=True) + green = NestedInteger(allow_none=True) + greenOff = NestedInteger(allow_none=True) + greenMod = NestedInteger(allow_none=True) + blue = NestedInteger(allow_none=True) + blueOff = NestedInteger(allow_none=True) + blueMod = NestedInteger(allow_none=True) + gamma = Typed(expected_type=Transform, allow_none=True) + invGamma = Typed(expected_type=Transform, allow_none=True) + + val = Set(values=( ['scrollBar', 'background', 'activeCaption', + 'inactiveCaption', 'menu', 'window', 'windowFrame', 'menuText', + 'windowText', 'captionText', 'activeBorder', 'inactiveBorder', + 'appWorkspace', 'highlight', 'highlightText', 'btnFace', 'btnShadow', + 'grayText', 'btnText', 'inactiveCaptionText', 'btnHighlight', + '3dDkShadow', '3dLight', 'infoText', 'infoBk', 'hotLight', + 'gradientActiveCaption', 'gradientInactiveCaption', 'menuHighlight', + 'menuBar'] ) + ) + lastClr = RGB(allow_none=True) + + __elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', "alpha", + "alphaOff", "alphaMod", "hue", "hueOff", "hueMod", "hueOff", "sat", + "satOff", "satMod", "lum", "lumOff", "lumMod", "red", "redOff", "redMod", + "green", "greenOff", "greenMod", "blue", "blueOff", "blueMod", "gamma", + "invGamma") + + def __init__(self, + val="windowText", + lastClr=None, + tint=None, + shade=None, + comp=None, + inv=None, + gray=None, + alpha=None, + alphaOff=None, + alphaMod=None, + hue=None, + hueOff=None, + hueMod=None, + sat=None, + satOff=None, + satMod=None, + lum=None, + lumOff=None, + lumMod=None, + red=None, + redOff=None, + redMod=None, + green=None, + greenOff=None, + greenMod=None, + blue=None, + blueOff=None, + blueMod=None, + gamma=None, + invGamma=None + ): + self.val = val + self.lastClr = lastClr + self.tint = tint + self.shade = shade + self.comp = comp + self.inv = inv + self.gray = gray + self.alpha = alpha + self.alphaOff = alphaOff + self.alphaMod = alphaMod + self.hue = hue + self.hueOff = hueOff + self.hueMod = hueMod + self.sat = sat + self.satOff = satOff + self.satMod = satMod + self.lum = lum + self.lumOff = lumOff + self.lumMod = lumMod + self.red = red + self.redOff = redOff + self.redMod = redMod + self.green = green + self.greenOff = greenOff + self.greenMod = greenMod + self.blue = blue + self.blueOff = blueOff + self.blueMod = blueMod + self.gamma = gamma + self.invGamma = invGamma + + +class HSLColor(Serialisable): + + tagname = "hslClr" + + hue = Integer() + sat = MinMax(min=0, max=100) + lum = MinMax(min=0, max=100) + + #TODO add color transform options + + def __init__(self, + hue=None, + sat=None, + lum=None, + ): + self.hue = hue + self.sat = sat + self.lum = lum + + + +class RGBPercent(Serialisable): + + tagname = "rgbClr" + + r = MinMax(min=0, max=100) + g = MinMax(min=0, max=100) + b = MinMax(min=0, max=100) + + #TODO add color transform options + + def __init__(self, + r=None, + g=None, + b=None, + ): + self.r = r + self.g = g + self.b = b + + +class SchemeColor(Serialisable): + + tagname = "schemeClr" + namespace = DRAWING_NS + + tint = NestedInteger(allow_none=True) + shade = NestedInteger(allow_none=True) + comp = EmptyTag(allow_none=True) + inv = NestedInteger(allow_none=True) + gray = NestedInteger(allow_none=True) + alpha = NestedInteger(allow_none=True) + alphaOff = NestedInteger(allow_none=True) + alphaMod = NestedInteger(allow_none=True) + hue = NestedInteger(allow_none=True) + hueOff = NestedInteger(allow_none=True) + hueMod = NestedInteger(allow_none=True) + sat = NestedInteger(allow_none=True) + satOff = NestedInteger(allow_none=True) + satMod = NestedInteger(allow_none=True) + lum = NestedInteger(allow_none=True) + lumOff = NestedInteger(allow_none=True) + lumMod = NestedInteger(allow_none=True) + red = NestedInteger(allow_none=True) + redOff = NestedInteger(allow_none=True) + redMod = NestedInteger(allow_none=True) + green = NestedInteger(allow_none=True) + greenOff = NestedInteger(allow_none=True) + greenMod = NestedInteger(allow_none=True) + blue = NestedInteger(allow_none=True) + blueOff = NestedInteger(allow_none=True) + blueMod = NestedInteger(allow_none=True) + gamma = EmptyTag(allow_none=True) + invGamma = EmptyTag(allow_none=True) + val = Set(values=(['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', + 'accent3', 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr', + 'dk1', 'lt1', 'dk2', 'lt2'])) + + __elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', 'alpha', + 'alphaOff', 'alphaMod', 'hue', 'hueOff', 'hueMod', 'sat', 'satOff', + 'satMod', 'lum', 'lumMod', 'lumOff', 'red', 'redOff', 'redMod', 'green', + 'greenOff', 'greenMod', 'blue', 'blueOff', 'blueMod', 'gamma', + 'invGamma') + + def __init__(self, + tint=None, + shade=None, + comp=None, + inv=None, + gray=None, + alpha=None, + alphaOff=None, + alphaMod=None, + hue=None, + hueOff=None, + hueMod=None, + sat=None, + satOff=None, + satMod=None, + lum=None, + lumOff=None, + lumMod=None, + red=None, + redOff=None, + redMod=None, + green=None, + greenOff=None, + greenMod=None, + blue=None, + blueOff=None, + blueMod=None, + gamma=None, + invGamma=None, + val=None, + ): + self.tint = tint + self.shade = shade + self.comp = comp + self.inv = inv + self.gray = gray + self.alpha = alpha + self.alphaOff = alphaOff + self.alphaMod = alphaMod + self.hue = hue + self.hueOff = hueOff + self.hueMod = hueMod + self.sat = sat + self.satOff = satOff + self.satMod = satMod + self.lum = lum + self.lumOff = lumOff + self.lumMod = lumMod + self.red = red + self.redOff = redOff + self.redMod = redMod + self.green = green + self.greenOff = greenOff + self.greenMod = greenMod + self.blue = blue + self.blueOff = blueOff + self.blueMod = blueMod + self.gamma = gamma + self.invGamma = invGamma + self.val = val + +class ColorChoice(Serialisable): + + tagname = "colorChoice" + namespace = DRAWING_NS + + scrgbClr = Typed(expected_type=RGBPercent, allow_none=True) + RGBPercent = Alias('scrgbClr') + srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform + RGB = Alias('srgbClr') + hslClr = Typed(expected_type=HSLColor, allow_none=True) + sysClr = Typed(expected_type=SystemColor, allow_none=True) + schemeClr = Typed(expected_type=SchemeColor, allow_none=True) + prstClr = NestedNoneSet(values=PRESET_COLORS) + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + scrgbClr=None, + srgbClr=None, + hslClr=None, + sysClr=None, + schemeClr=None, + prstClr=None, + ): + self.scrgbClr = scrgbClr + self.srgbClr = srgbClr + self.hslClr = hslClr + self.sysClr = sysClr + self.schemeClr = schemeClr + self.prstClr = prstClr + +_COLOR_SET = ('dk1', 'lt1', 'dk2', 'lt2', 'accent1', 'accent2', 'accent3', + 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink') + + +class ColorMapping(Serialisable): + + tagname = "clrMapOvr" + + bg1 = Set(values=_COLOR_SET) + tx1 = Set(values=_COLOR_SET) + bg2 = Set(values=_COLOR_SET) + tx2 = Set(values=_COLOR_SET) + accent1 = Set(values=_COLOR_SET) + accent2 = Set(values=_COLOR_SET) + accent3 = Set(values=_COLOR_SET) + accent4 = Set(values=_COLOR_SET) + accent5 = Set(values=_COLOR_SET) + accent6 = Set(values=_COLOR_SET) + hlink = Set(values=_COLOR_SET) + folHlink = Set(values=_COLOR_SET) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + bg1="lt1", + tx1="dk1", + bg2="lt2", + tx2="dk2", + accent1="accent1", + accent2="accent2", + accent3="accent3", + accent4="accent4", + accent5="accent5", + accent6="accent6", + hlink="hlink", + folHlink="folHlink", + extLst=None, + ): + self.bg1 = bg1 + self.tx1 = tx1 + self.bg2 = bg2 + self.tx2 = tx2 + self.accent1 = accent1 + self.accent2 = accent2 + self.accent3 = accent3 + self.accent4 = accent4 + self.accent5 = accent5 + self.accent6 = accent6 + self.hlink = hlink + self.folHlink = folHlink + self.extLst = extLst + + +class ColorChoiceDescriptor(Typed): + """ + Objects can choose from 7 different kinds of color system. + Assume RGBHex if a string is passed in. + """ + + expected_type = ColorChoice + allow_none = True + + def __set__(self, instance, value): + if isinstance(value, str): + value = ColorChoice(srgbClr=value) + else: + if hasattr(self, "namespace") and value is not None: + value.namespace = self.namespace + super().__set__(instance, value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/connector.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/connector.py new file mode 100644 index 00000000..d25bcf71 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/connector.py @@ -0,0 +1,144 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Integer, + String, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList +from openpyxl.chart.shapes import GraphicalProperties +from openpyxl.chart.text import RichText + +from .properties import ( + NonVisualDrawingProps, + NonVisualDrawingShapeProps, +) +from .geometry import ShapeStyle + +class Connection(Serialisable): + + id = Integer() + idx = Integer() + + def __init__(self, + id=None, + idx=None, + ): + self.id = id + self.idx = idx + + +class ConnectorLocking(Serialisable): + + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + extLst=None, + ): + self.extLst = extLst + + +class NonVisualConnectorProperties(Serialisable): + + cxnSpLocks = Typed(expected_type=ConnectorLocking, allow_none=True) + stCxn = Typed(expected_type=Connection, allow_none=True) + endCxn = Typed(expected_type=Connection, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + cxnSpLocks=None, + stCxn=None, + endCxn=None, + extLst=None, + ): + self.cxnSpLocks = cxnSpLocks + self.stCxn = stCxn + self.endCxn = endCxn + self.extLst = extLst + + +class ConnectorNonVisual(Serialisable): + + cNvPr = Typed(expected_type=NonVisualDrawingProps, ) + cNvCxnSpPr = Typed(expected_type=NonVisualConnectorProperties, ) + + __elements__ = ("cNvPr", "cNvCxnSpPr",) + + def __init__(self, + cNvPr=None, + cNvCxnSpPr=None, + ): + self.cNvPr = cNvPr + self.cNvCxnSpPr = cNvCxnSpPr + + +class ConnectorShape(Serialisable): + + tagname = "cxnSp" + + nvCxnSpPr = Typed(expected_type=ConnectorNonVisual) + spPr = Typed(expected_type=GraphicalProperties) + style = Typed(expected_type=ShapeStyle, allow_none=True) + macro = String(allow_none=True) + fPublished = Bool(allow_none=True) + + def __init__(self, + nvCxnSpPr=None, + spPr=None, + style=None, + macro=None, + fPublished=None, + ): + self.nvCxnSpPr = nvCxnSpPr + self.spPr = spPr + self.style = style + self.macro = macro + self.fPublished = fPublished + + +class ShapeMeta(Serialisable): + + tagname = "nvSpPr" + + cNvPr = Typed(expected_type=NonVisualDrawingProps) + cNvSpPr = Typed(expected_type=NonVisualDrawingShapeProps) + + def __init__(self, cNvPr=None, cNvSpPr=None): + self.cNvPr = cNvPr + self.cNvSpPr = cNvSpPr + + +class Shape(Serialisable): + + macro = String(allow_none=True) + textlink = String(allow_none=True) + fPublished = Bool(allow_none=True) + fLocksText = Bool(allow_none=True) + nvSpPr = Typed(expected_type=ShapeMeta, allow_none=True) + meta = Alias("nvSpPr") + spPr = Typed(expected_type=GraphicalProperties) + graphicalProperties = Alias("spPr") + style = Typed(expected_type=ShapeStyle, allow_none=True) + txBody = Typed(expected_type=RichText, allow_none=True) + + def __init__(self, + macro=None, + textlink=None, + fPublished=None, + fLocksText=None, + nvSpPr=None, + spPr=None, + style=None, + txBody=None, + ): + self.macro = macro + self.textlink = textlink + self.fPublished = fPublished + self.fLocksText = fLocksText + self.nvSpPr = nvSpPr + self.spPr = spPr + self.style = style + self.txBody = txBody diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/drawing.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/drawing.py new file mode 100644 index 00000000..45acdfe5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/drawing.py @@ -0,0 +1,92 @@ + +# Copyright (c) 2010-2024 openpyxl + +import math + +from openpyxl.utils.units import pixels_to_EMU + + +class Drawing: + """ a drawing object - eg container for shapes or charts + we assume user specifies dimensions in pixels; units are + converted to EMU in the drawing part + """ + + count = 0 + + def __init__(self): + + self.name = '' + self.description = '' + self.coordinates = ((1, 2), (16, 8)) + self.left = 0 + self.top = 0 + self._width = 21 # default in px + self._height = 192 #default in px + self.resize_proportional = False + self.rotation = 0 + self.anchortype = "absolute" + self.anchorcol = 0 # left cell + self.anchorrow = 0 # top row + + + @property + def width(self): + return self._width + + + @width.setter + def width(self, w): + if self.resize_proportional and w: + ratio = self._height / self._width + self._height = round(ratio * w) + self._width = w + + + @property + def height(self): + return self._height + + + @height.setter + def height(self, h): + if self.resize_proportional and h: + ratio = self._width / self._height + self._width = round(ratio * h) + self._height = h + + + def set_dimension(self, w=0, h=0): + + xratio = w / self._width + yratio = h / self._height + + if self.resize_proportional and w and h: + if (xratio * self._height) < h: + self._height = math.ceil(xratio * self._height) + self._width = w + else: + self._width = math.ceil(yratio * self._width) + self._height = h + + + @property + def anchor(self): + from .spreadsheet_drawing import ( + OneCellAnchor, + TwoCellAnchor, + AbsoluteAnchor) + if self.anchortype == "absolute": + anchor = AbsoluteAnchor() + anchor.pos.x = pixels_to_EMU(self.left) + anchor.pos.y = pixels_to_EMU(self.top) + + elif self.anchortype == "oneCell": + anchor = OneCellAnchor() + anchor._from.col = self.anchorcol + anchor._from.row = self.anchorrow + + anchor.ext.width = pixels_to_EMU(self._width) + anchor.ext.height = pixels_to_EMU(self._height) + + return anchor diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/effect.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/effect.py new file mode 100644 index 00000000..9edae342 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/effect.py @@ -0,0 +1,407 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Set, + Bool, + Integer, + Float, +) + +from .colors import ColorChoice + + +class TintEffect(Serialisable): + + tagname = "tint" + + hue = Integer() + amt = Integer() + + def __init__(self, + hue=0, + amt=0, + ): + self.hue = hue + self.amt = amt + + +class LuminanceEffect(Serialisable): + + tagname = "lum" + + bright = Integer() #Pct ? + contrast = Integer() #Pct# + + def __init__(self, + bright=0, + contrast=0, + ): + self.bright = bright + self.contrast = contrast + + +class HSLEffect(Serialisable): + + hue = Integer() + sat = Integer() + lum = Integer() + + def __init__(self, + hue=None, + sat=None, + lum=None, + ): + self.hue = hue + self.sat = sat + self.lum = lum + + +class GrayscaleEffect(Serialisable): + + tagname = "grayscl" + + +class FillOverlayEffect(Serialisable): + + blend = Set(values=(['over', 'mult', 'screen', 'darken', 'lighten'])) + + def __init__(self, + blend=None, + ): + self.blend = blend + + +class DuotoneEffect(Serialisable): + + pass + +class ColorReplaceEffect(Serialisable): + + pass + +class Color(Serialisable): + + pass + +class ColorChangeEffect(Serialisable): + + useA = Bool(allow_none=True) + clrFrom = Typed(expected_type=Color, ) + clrTo = Typed(expected_type=Color, ) + + def __init__(self, + useA=None, + clrFrom=None, + clrTo=None, + ): + self.useA = useA + self.clrFrom = clrFrom + self.clrTo = clrTo + + +class BlurEffect(Serialisable): + + rad = Float() + grow = Bool(allow_none=True) + + def __init__(self, + rad=None, + grow=None, + ): + self.rad = rad + self.grow = grow + + +class BiLevelEffect(Serialisable): + + thresh = Integer() + + def __init__(self, + thresh=None, + ): + self.thresh = thresh + + +class AlphaReplaceEffect(Serialisable): + + a = Integer() + + def __init__(self, + a=None, + ): + self.a = a + + +class AlphaModulateFixedEffect(Serialisable): + + amt = Integer() + + def __init__(self, + amt=None, + ): + self.amt = amt + + +class EffectContainer(Serialisable): + + type = Set(values=(['sib', 'tree'])) + name = String(allow_none=True) + + def __init__(self, + type=None, + name=None, + ): + self.type = type + self.name = name + + +class AlphaModulateEffect(Serialisable): + + cont = Typed(expected_type=EffectContainer, ) + + def __init__(self, + cont=None, + ): + self.cont = cont + + +class AlphaInverseEffect(Serialisable): + + pass + +class AlphaFloorEffect(Serialisable): + + pass + +class AlphaCeilingEffect(Serialisable): + + pass + +class AlphaBiLevelEffect(Serialisable): + + thresh = Integer() + + def __init__(self, + thresh=None, + ): + self.thresh = thresh + + +class GlowEffect(ColorChoice): + + rad = Float() + # uses element group EG_ColorChoice + scrgbClr = ColorChoice.scrgbClr + srgbClr = ColorChoice.srgbClr + hslClr = ColorChoice.hslClr + sysClr = ColorChoice.sysClr + schemeClr = ColorChoice.schemeClr + prstClr = ColorChoice.prstClr + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + rad=None, + **kw + ): + self.rad = rad + super().__init__(**kw) + + +class InnerShadowEffect(ColorChoice): + + blurRad = Float() + dist = Float() + dir = Integer() + # uses element group EG_ColorChoice + scrgbClr = ColorChoice.scrgbClr + srgbClr = ColorChoice.srgbClr + hslClr = ColorChoice.hslClr + sysClr = ColorChoice.sysClr + schemeClr = ColorChoice.schemeClr + prstClr = ColorChoice.prstClr + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + blurRad=None, + dist=None, + dir=None, + **kw + ): + self.blurRad = blurRad + self.dist = dist + self.dir = dir + super().__init__(**kw) + + +class OuterShadow(ColorChoice): + + tagname = "outerShdw" + + blurRad = Float(allow_none=True) + dist = Float(allow_none=True) + dir = Integer(allow_none=True) + sx = Integer(allow_none=True) + sy = Integer(allow_none=True) + kx = Integer(allow_none=True) + ky = Integer(allow_none=True) + algn = Set(values=['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br']) + rotWithShape = Bool(allow_none=True) + # uses element group EG_ColorChoice + scrgbClr = ColorChoice.scrgbClr + srgbClr = ColorChoice.srgbClr + hslClr = ColorChoice.hslClr + sysClr = ColorChoice.sysClr + schemeClr = ColorChoice.schemeClr + prstClr = ColorChoice.prstClr + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + blurRad=None, + dist=None, + dir=None, + sx=None, + sy=None, + kx=None, + ky=None, + algn=None, + rotWithShape=None, + **kw + ): + self.blurRad = blurRad + self.dist = dist + self.dir = dir + self.sx = sx + self.sy = sy + self.kx = kx + self.ky = ky + self.algn = algn + self.rotWithShape = rotWithShape + super().__init__(**kw) + + +class PresetShadowEffect(ColorChoice): + + prst = Set(values=(['shdw1', 'shdw2', 'shdw3', 'shdw4', 'shdw5', 'shdw6', + 'shdw7', 'shdw8', 'shdw9', 'shdw10', 'shdw11', 'shdw12', 'shdw13', + 'shdw14', 'shdw15', 'shdw16', 'shdw17', 'shdw18', 'shdw19', 'shdw20'])) + dist = Float() + dir = Integer() + # uses element group EG_ColorChoice + scrgbClr = ColorChoice.scrgbClr + srgbClr = ColorChoice.srgbClr + hslClr = ColorChoice.hslClr + sysClr = ColorChoice.sysClr + schemeClr = ColorChoice.schemeClr + prstClr = ColorChoice.prstClr + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + prst=None, + dist=None, + dir=None, + **kw + ): + self.prst = prst + self.dist = dist + self.dir = dir + super().__init__(**kw) + + +class ReflectionEffect(Serialisable): + + blurRad = Float() + stA = Integer() + stPos = Integer() + endA = Integer() + endPos = Integer() + dist = Float() + dir = Integer() + fadeDir = Integer() + sx = Integer() + sy = Integer() + kx = Integer() + ky = Integer() + algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br'])) + rotWithShape = Bool(allow_none=True) + + def __init__(self, + blurRad=None, + stA=None, + stPos=None, + endA=None, + endPos=None, + dist=None, + dir=None, + fadeDir=None, + sx=None, + sy=None, + kx=None, + ky=None, + algn=None, + rotWithShape=None, + ): + self.blurRad = blurRad + self.stA = stA + self.stPos = stPos + self.endA = endA + self.endPos = endPos + self.dist = dist + self.dir = dir + self.fadeDir = fadeDir + self.sx = sx + self.sy = sy + self.kx = kx + self.ky = ky + self.algn = algn + self.rotWithShape = rotWithShape + + +class SoftEdgesEffect(Serialisable): + + rad = Float() + + def __init__(self, + rad=None, + ): + self.rad = rad + + +class EffectList(Serialisable): + + blur = Typed(expected_type=BlurEffect, allow_none=True) + fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True) + glow = Typed(expected_type=GlowEffect, allow_none=True) + innerShdw = Typed(expected_type=InnerShadowEffect, allow_none=True) + outerShdw = Typed(expected_type=OuterShadow, allow_none=True) + prstShdw = Typed(expected_type=PresetShadowEffect, allow_none=True) + reflection = Typed(expected_type=ReflectionEffect, allow_none=True) + softEdge = Typed(expected_type=SoftEdgesEffect, allow_none=True) + + __elements__ = ('blur', 'fillOverlay', 'glow', 'innerShdw', 'outerShdw', + 'prstShdw', 'reflection', 'softEdge') + + def __init__(self, + blur=None, + fillOverlay=None, + glow=None, + innerShdw=None, + outerShdw=None, + prstShdw=None, + reflection=None, + softEdge=None, + ): + self.blur = blur + self.fillOverlay = fillOverlay + self.glow = glow + self.innerShdw = innerShdw + self.outerShdw = outerShdw + self.prstShdw = prstShdw + self.reflection = reflection + self.softEdge = softEdge diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/fill.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/fill.py new file mode 100644 index 00000000..580e0db2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/fill.py @@ -0,0 +1,425 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Bool, + Integer, + Set, + NoneSet, + Typed, + MinMax, +) +from openpyxl.descriptors.excel import ( + Relation, + Percentage, +) +from openpyxl.descriptors.nested import NestedNoneSet, NestedValue +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList +from openpyxl.xml.constants import DRAWING_NS + +from .colors import ( + ColorChoice, + HSLColor, + SystemColor, + SchemeColor, + PRESET_COLORS, + RGBPercent, +) + +from .effect import ( + AlphaBiLevelEffect, + AlphaCeilingEffect, + AlphaFloorEffect, + AlphaInverseEffect, + AlphaModulateEffect, + AlphaModulateFixedEffect, + AlphaReplaceEffect, + BiLevelEffect, + BlurEffect, + ColorChangeEffect, + ColorReplaceEffect, + DuotoneEffect, + FillOverlayEffect, + GrayscaleEffect, + HSLEffect, + LuminanceEffect, + TintEffect, +) + +""" +Fill elements from drawing main schema +""" + +class PatternFillProperties(Serialisable): + + tagname = "pattFill" + namespace = DRAWING_NS + + prst = NoneSet(values=(['pct5', 'pct10', 'pct20', 'pct25', 'pct30', + 'pct40', 'pct50', 'pct60', 'pct70', 'pct75', 'pct80', 'pct90', 'horz', + 'vert', 'ltHorz', 'ltVert', 'dkHorz', 'dkVert', 'narHorz', 'narVert', + 'dashHorz', 'dashVert', 'cross', 'dnDiag', 'upDiag', 'ltDnDiag', + 'ltUpDiag', 'dkDnDiag', 'dkUpDiag', 'wdDnDiag', 'wdUpDiag', 'dashDnDiag', + 'dashUpDiag', 'diagCross', 'smCheck', 'lgCheck', 'smGrid', 'lgGrid', + 'dotGrid', 'smConfetti', 'lgConfetti', 'horzBrick', 'diagBrick', + 'solidDmnd', 'openDmnd', 'dotDmnd', 'plaid', 'sphere', 'weave', 'divot', + 'shingle', 'wave', 'trellis', 'zigZag'])) + preset = Alias("prst") + fgClr = Typed(expected_type=ColorChoice, allow_none=True) + foreground = Alias("fgClr") + bgClr = Typed(expected_type=ColorChoice, allow_none=True) + background = Alias("bgClr") + + __elements__ = ("fgClr", "bgClr") + + def __init__(self, + prst=None, + fgClr=None, + bgClr=None, + ): + self.prst = prst + self.fgClr = fgClr + self.bgClr = bgClr + + +class RelativeRect(Serialisable): + + tagname = "rect" + namespace = DRAWING_NS + + l = Percentage(allow_none=True) + left = Alias('l') + t = Percentage(allow_none=True) + top = Alias('t') + r = Percentage(allow_none=True) + right = Alias('r') + b = Percentage(allow_none=True) + bottom = Alias('b') + + def __init__(self, + l=None, + t=None, + r=None, + b=None, + ): + self.l = l + self.t = t + self.r = r + self.b = b + + +class StretchInfoProperties(Serialisable): + + tagname = "stretch" + namespace = DRAWING_NS + + fillRect = Typed(expected_type=RelativeRect, allow_none=True) + + def __init__(self, + fillRect=RelativeRect(), + ): + self.fillRect = fillRect + + +class GradientStop(Serialisable): + + tagname = "gs" + namespace = DRAWING_NS + + pos = MinMax(min=0, max=100000, allow_none=True) + # Color Choice Group + scrgbClr = Typed(expected_type=RGBPercent, allow_none=True) + RGBPercent = Alias('scrgbClr') + srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform + RGB = Alias('srgbClr') + hslClr = Typed(expected_type=HSLColor, allow_none=True) + sysClr = Typed(expected_type=SystemColor, allow_none=True) + schemeClr = Typed(expected_type=SchemeColor, allow_none=True) + prstClr = NestedNoneSet(values=PRESET_COLORS) + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + pos=None, + scrgbClr=None, + srgbClr=None, + hslClr=None, + sysClr=None, + schemeClr=None, + prstClr=None, + ): + if pos is None: + pos = 0 + self.pos = pos + + self.scrgbClr = scrgbClr + self.srgbClr = srgbClr + self.hslClr = hslClr + self.sysClr = sysClr + self.schemeClr = schemeClr + self.prstClr = prstClr + + +class LinearShadeProperties(Serialisable): + + tagname = "lin" + namespace = DRAWING_NS + + ang = Integer() + scaled = Bool(allow_none=True) + + def __init__(self, + ang=None, + scaled=None, + ): + self.ang = ang + self.scaled = scaled + + +class PathShadeProperties(Serialisable): + + tagname = "path" + namespace = DRAWING_NS + + path = Set(values=(['shape', 'circle', 'rect'])) + fillToRect = Typed(expected_type=RelativeRect, allow_none=True) + + def __init__(self, + path=None, + fillToRect=None, + ): + self.path = path + self.fillToRect = fillToRect + + +class GradientFillProperties(Serialisable): + + tagname = "gradFill" + namespace = DRAWING_NS + + flip = NoneSet(values=(['x', 'y', 'xy'])) + rotWithShape = Bool(allow_none=True) + + gsLst = NestedSequence(expected_type=GradientStop, count=False) + stop_list = Alias("gsLst") + + lin = Typed(expected_type=LinearShadeProperties, allow_none=True) + linear = Alias("lin") + path = Typed(expected_type=PathShadeProperties, allow_none=True) + + tileRect = Typed(expected_type=RelativeRect, allow_none=True) + + __elements__ = ('gsLst', 'lin', 'path', 'tileRect') + + def __init__(self, + flip=None, + rotWithShape=None, + gsLst=(), + lin=None, + path=None, + tileRect=None, + ): + self.flip = flip + self.rotWithShape = rotWithShape + self.gsLst = gsLst + self.lin = lin + self.path = path + self.tileRect = tileRect + + +class SolidColorFillProperties(Serialisable): + + tagname = "solidFill" + + # uses element group EG_ColorChoice + scrgbClr = Typed(expected_type=RGBPercent, allow_none=True) + RGBPercent = Alias('scrgbClr') + srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform + RGB = Alias('srgbClr') + hslClr = Typed(expected_type=HSLColor, allow_none=True) + sysClr = Typed(expected_type=SystemColor, allow_none=True) + schemeClr = Typed(expected_type=SchemeColor, allow_none=True) + prstClr = NestedNoneSet(values=PRESET_COLORS) + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + scrgbClr=None, + srgbClr=None, + hslClr=None, + sysClr=None, + schemeClr=None, + prstClr=None, + ): + self.scrgbClr = scrgbClr + self.srgbClr = srgbClr + self.hslClr = hslClr + self.sysClr = sysClr + self.schemeClr = schemeClr + self.prstClr = prstClr + + +class Blip(Serialisable): + + tagname = "blip" + namespace = DRAWING_NS + + # Using attribute groupAG_Blob + cstate = NoneSet(values=(['email', 'screen', 'print', 'hqprint'])) + embed = Relation() # rId + link = Relation() # hyperlink + noGrp = Bool(allow_none=True) + noSelect = Bool(allow_none=True) + noRot = Bool(allow_none=True) + noChangeAspect = Bool(allow_none=True) + noMove = Bool(allow_none=True) + noResize = Bool(allow_none=True) + noEditPoints = Bool(allow_none=True) + noAdjustHandles = Bool(allow_none=True) + noChangeArrowheads = Bool(allow_none=True) + noChangeShapeType = Bool(allow_none=True) + # some elements are choice + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + alphaBiLevel = Typed(expected_type=AlphaBiLevelEffect, allow_none=True) + alphaCeiling = Typed(expected_type=AlphaCeilingEffect, allow_none=True) + alphaFloor = Typed(expected_type=AlphaFloorEffect, allow_none=True) + alphaInv = Typed(expected_type=AlphaInverseEffect, allow_none=True) + alphaMod = Typed(expected_type=AlphaModulateEffect, allow_none=True) + alphaModFix = Typed(expected_type=AlphaModulateFixedEffect, allow_none=True) + alphaRepl = Typed(expected_type=AlphaReplaceEffect, allow_none=True) + biLevel = Typed(expected_type=BiLevelEffect, allow_none=True) + blur = Typed(expected_type=BlurEffect, allow_none=True) + clrChange = Typed(expected_type=ColorChangeEffect, allow_none=True) + clrRepl = Typed(expected_type=ColorReplaceEffect, allow_none=True) + duotone = Typed(expected_type=DuotoneEffect, allow_none=True) + fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True) + grayscl = Typed(expected_type=GrayscaleEffect, allow_none=True) + hsl = Typed(expected_type=HSLEffect, allow_none=True) + lum = Typed(expected_type=LuminanceEffect, allow_none=True) + tint = Typed(expected_type=TintEffect, allow_none=True) + + __elements__ = ('alphaBiLevel', 'alphaCeiling', 'alphaFloor', 'alphaInv', + 'alphaMod', 'alphaModFix', 'alphaRepl', 'biLevel', 'blur', 'clrChange', + 'clrRepl', 'duotone', 'fillOverlay', 'grayscl', 'hsl', 'lum', 'tint') + + def __init__(self, + cstate=None, + embed=None, + link=None, + noGrp=None, + noSelect=None, + noRot=None, + noChangeAspect=None, + noMove=None, + noResize=None, + noEditPoints=None, + noAdjustHandles=None, + noChangeArrowheads=None, + noChangeShapeType=None, + extLst=None, + alphaBiLevel=None, + alphaCeiling=None, + alphaFloor=None, + alphaInv=None, + alphaMod=None, + alphaModFix=None, + alphaRepl=None, + biLevel=None, + blur=None, + clrChange=None, + clrRepl=None, + duotone=None, + fillOverlay=None, + grayscl=None, + hsl=None, + lum=None, + tint=None, + ): + self.cstate = cstate + self.embed = embed + self.link = link + self.noGrp = noGrp + self.noSelect = noSelect + self.noRot = noRot + self.noChangeAspect = noChangeAspect + self.noMove = noMove + self.noResize = noResize + self.noEditPoints = noEditPoints + self.noAdjustHandles = noAdjustHandles + self.noChangeArrowheads = noChangeArrowheads + self.noChangeShapeType = noChangeShapeType + self.extLst = extLst + self.alphaBiLevel = alphaBiLevel + self.alphaCeiling = alphaCeiling + self.alphaFloor = alphaFloor + self.alphaInv = alphaInv + self.alphaMod = alphaMod + self.alphaModFix = alphaModFix + self.alphaRepl = alphaRepl + self.biLevel = biLevel + self.blur = blur + self.clrChange = clrChange + self.clrRepl = clrRepl + self.duotone = duotone + self.fillOverlay = fillOverlay + self.grayscl = grayscl + self.hsl = hsl + self.lum = lum + self.tint = tint + + +class TileInfoProperties(Serialisable): + + tx = Integer(allow_none=True) + ty = Integer(allow_none=True) + sx = Integer(allow_none=True) + sy = Integer(allow_none=True) + flip = NoneSet(values=(['x', 'y', 'xy'])) + algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br'])) + + def __init__(self, + tx=None, + ty=None, + sx=None, + sy=None, + flip=None, + algn=None, + ): + self.tx = tx + self.ty = ty + self.sx = sx + self.sy = sy + self.flip = flip + self.algn = algn + + +class BlipFillProperties(Serialisable): + + tagname = "blipFill" + + dpi = Integer(allow_none=True) + rotWithShape = Bool(allow_none=True) + + blip = Typed(expected_type=Blip, allow_none=True) + srcRect = Typed(expected_type=RelativeRect, allow_none=True) + tile = Typed(expected_type=TileInfoProperties, allow_none=True) + stretch = Typed(expected_type=StretchInfoProperties, allow_none=True) + + __elements__ = ("blip", "srcRect", "tile", "stretch") + + def __init__(self, + dpi=None, + rotWithShape=None, + blip=None, + tile=None, + stretch=StretchInfoProperties(), + srcRect=None, + ): + self.dpi = dpi + self.rotWithShape = rotWithShape + self.blip = blip + self.tile = tile + self.stretch = stretch + self.srcRect = srcRect diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/geometry.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/geometry.py new file mode 100644 index 00000000..2cc7ca63 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/geometry.py @@ -0,0 +1,584 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + Integer, + Bool, + MinMax, + Set, + NoneSet, + String, + Alias, +) +from openpyxl.descriptors.excel import Coordinate, Percentage +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList +from .line import LineProperties + +from openpyxl.styles.colors import Color +from openpyxl.xml.constants import DRAWING_NS + + +class Point2D(Serialisable): + + tagname = "off" + namespace = DRAWING_NS + + x = Coordinate() + y = Coordinate() + + def __init__(self, + x=None, + y=None, + ): + self.x = x + self.y = y + + +class PositiveSize2D(Serialisable): + + tagname = "ext" + namespace = DRAWING_NS + + """ + Dimensions in EMUs + """ + + cx = Integer() + width = Alias('cx') + cy = Integer() + height = Alias('cy') + + def __init__(self, + cx=None, + cy=None, + ): + self.cx = cx + self.cy = cy + + +class Transform2D(Serialisable): + + tagname = "xfrm" + namespace = DRAWING_NS + + rot = Integer(allow_none=True) + flipH = Bool(allow_none=True) + flipV = Bool(allow_none=True) + off = Typed(expected_type=Point2D, allow_none=True) + ext = Typed(expected_type=PositiveSize2D, allow_none=True) + chOff = Typed(expected_type=Point2D, allow_none=True) + chExt = Typed(expected_type=PositiveSize2D, allow_none=True) + + __elements__ = ('off', 'ext', 'chOff', 'chExt') + + def __init__(self, + rot=None, + flipH=None, + flipV=None, + off=None, + ext=None, + chOff=None, + chExt=None, + ): + self.rot = rot + self.flipH = flipH + self.flipV = flipV + self.off = off + self.ext = ext + self.chOff = chOff + self.chExt = chExt + + +class GroupTransform2D(Serialisable): + + tagname = "xfrm" + namespace = DRAWING_NS + + rot = Integer(allow_none=True) + flipH = Bool(allow_none=True) + flipV = Bool(allow_none=True) + off = Typed(expected_type=Point2D, allow_none=True) + ext = Typed(expected_type=PositiveSize2D, allow_none=True) + chOff = Typed(expected_type=Point2D, allow_none=True) + chExt = Typed(expected_type=PositiveSize2D, allow_none=True) + + __elements__ = ("off", "ext", "chOff", "chExt") + + def __init__(self, + rot=0, + flipH=None, + flipV=None, + off=None, + ext=None, + chOff=None, + chExt=None, + ): + self.rot = rot + self.flipH = flipH + self.flipV = flipV + self.off = off + self.ext = ext + self.chOff = chOff + self.chExt = chExt + + +class SphereCoords(Serialisable): + + tagname = "sphereCoords" # usually + + lat = Integer() + lon = Integer() + rev = Integer() + + def __init__(self, + lat=None, + lon=None, + rev=None, + ): + self.lat = lat + self.lon = lon + self.rev = rev + + +class Camera(Serialisable): + + tagname = "camera" + + prst = Set(values=[ + 'legacyObliqueTopLeft', 'legacyObliqueTop', 'legacyObliqueTopRight', 'legacyObliqueLeft', + 'legacyObliqueFront', 'legacyObliqueRight', 'legacyObliqueBottomLeft', + 'legacyObliqueBottom', 'legacyObliqueBottomRight', 'legacyPerspectiveTopLeft', + 'legacyPerspectiveTop', 'legacyPerspectiveTopRight', 'legacyPerspectiveLeft', + 'legacyPerspectiveFront', 'legacyPerspectiveRight', 'legacyPerspectiveBottomLeft', + 'legacyPerspectiveBottom', 'legacyPerspectiveBottomRight', 'orthographicFront', + 'isometricTopUp', 'isometricTopDown', 'isometricBottomUp', 'isometricBottomDown', + 'isometricLeftUp', 'isometricLeftDown', 'isometricRightUp', 'isometricRightDown', + 'isometricOffAxis1Left', 'isometricOffAxis1Right', 'isometricOffAxis1Top', + 'isometricOffAxis2Left', 'isometricOffAxis2Right', 'isometricOffAxis2Top', + 'isometricOffAxis3Left', 'isometricOffAxis3Right', 'isometricOffAxis3Bottom', + 'isometricOffAxis4Left', 'isometricOffAxis4Right', 'isometricOffAxis4Bottom', + 'obliqueTopLeft', 'obliqueTop', 'obliqueTopRight', 'obliqueLeft', 'obliqueRight', + 'obliqueBottomLeft', 'obliqueBottom', 'obliqueBottomRight', 'perspectiveFront', + 'perspectiveLeft', 'perspectiveRight', 'perspectiveAbove', 'perspectiveBelow', + 'perspectiveAboveLeftFacing', 'perspectiveAboveRightFacing', + 'perspectiveContrastingLeftFacing', 'perspectiveContrastingRightFacing', + 'perspectiveHeroicLeftFacing', 'perspectiveHeroicRightFacing', + 'perspectiveHeroicExtremeLeftFacing', 'perspectiveHeroicExtremeRightFacing', + 'perspectiveRelaxed', 'perspectiveRelaxedModerately']) + fov = Integer(allow_none=True) + zoom = Typed(expected_type=Percentage, allow_none=True) + rot = Typed(expected_type=SphereCoords, allow_none=True) + + + def __init__(self, + prst=None, + fov=None, + zoom=None, + rot=None, + ): + self.prst = prst + self.fov = fov + self.zoom = zoom + self.rot = rot + + +class LightRig(Serialisable): + + tagname = "lightRig" + + rig = Set(values=['legacyFlat1', 'legacyFlat2', 'legacyFlat3', 'legacyFlat4', 'legacyNormal1', + 'legacyNormal2', 'legacyNormal3', 'legacyNormal4', 'legacyHarsh1', + 'legacyHarsh2', 'legacyHarsh3', 'legacyHarsh4', 'threePt', 'balanced', + 'soft', 'harsh', 'flood', 'contrasting', 'morning', 'sunrise', 'sunset', + 'chilly', 'freezing', 'flat', 'twoPt', 'glow', 'brightRoom'] + ) + dir = Set(values=(['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br'])) + rot = Typed(expected_type=SphereCoords, allow_none=True) + + def __init__(self, + rig=None, + dir=None, + rot=None, + ): + self.rig = rig + self.dir = dir + self.rot = rot + + +class Vector3D(Serialisable): + + tagname = "vector" + + dx = Integer() # can be in or universl measure :-/ + dy = Integer() + dz = Integer() + + def __init__(self, + dx=None, + dy=None, + dz=None, + ): + self.dx = dx + self.dy = dy + self.dz = dz + + +class Point3D(Serialisable): + + tagname = "anchor" + + x = Integer() + y = Integer() + z = Integer() + + def __init__(self, + x=None, + y=None, + z=None, + ): + self.x = x + self.y = y + self.z = z + + +class Backdrop(Serialisable): + + anchor = Typed(expected_type=Point3D, ) + norm = Typed(expected_type=Vector3D, ) + up = Typed(expected_type=Vector3D, ) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + anchor=None, + norm=None, + up=None, + extLst=None, + ): + self.anchor = anchor + self.norm = norm + self.up = up + self.extLst = extLst + + +class Scene3D(Serialisable): + + camera = Typed(expected_type=Camera, ) + lightRig = Typed(expected_type=LightRig, ) + backdrop = Typed(expected_type=Backdrop, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + camera=None, + lightRig=None, + backdrop=None, + extLst=None, + ): + self.camera = camera + self.lightRig = lightRig + self.backdrop = backdrop + self.extLst = extLst + + +class Bevel(Serialisable): + + tagname = "bevel" + + w = Integer() + h = Integer() + prst = NoneSet(values= + ['relaxedInset', 'circle', 'slope', 'cross', 'angle', + 'softRound', 'convex', 'coolSlant', 'divot', 'riblet', + 'hardEdge', 'artDeco'] + ) + + def __init__(self, + w=None, + h=None, + prst=None, + ): + self.w = w + self.h = h + self.prst = prst + + +class Shape3D(Serialisable): + + namespace = DRAWING_NS + + z = Typed(expected_type=Coordinate, allow_none=True) + extrusionH = Integer(allow_none=True) + contourW = Integer(allow_none=True) + prstMaterial = NoneSet(values=[ + 'legacyMatte','legacyPlastic', 'legacyMetal', 'legacyWireframe', 'matte', 'plastic', + 'metal', 'warmMatte', 'translucentPowder', 'powder', 'dkEdge', + 'softEdge', 'clear', 'flat', 'softmetal'] + ) + bevelT = Typed(expected_type=Bevel, allow_none=True) + bevelB = Typed(expected_type=Bevel, allow_none=True) + extrusionClr = Typed(expected_type=Color, allow_none=True) + contourClr = Typed(expected_type=Color, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + z=None, + extrusionH=None, + contourW=None, + prstMaterial=None, + bevelT=None, + bevelB=None, + extrusionClr=None, + contourClr=None, + extLst=None, + ): + self.z = z + self.extrusionH = extrusionH + self.contourW = contourW + self.prstMaterial = prstMaterial + self.bevelT = bevelT + self.bevelB = bevelB + self.extrusionClr = extrusionClr + self.contourClr = contourClr + self.extLst = extLst + + +class Path2D(Serialisable): + + w = Float() + h = Float() + fill = NoneSet(values=(['norm', 'lighten', 'lightenLess', 'darken', 'darkenLess'])) + stroke = Bool(allow_none=True) + extrusionOk = Bool(allow_none=True) + + def __init__(self, + w=None, + h=None, + fill=None, + stroke=None, + extrusionOk=None, + ): + self.w = w + self.h = h + self.fill = fill + self.stroke = stroke + self.extrusionOk = extrusionOk + + +class Path2DList(Serialisable): + + path = Typed(expected_type=Path2D, allow_none=True) + + def __init__(self, + path=None, + ): + self.path = path + + +class GeomRect(Serialisable): + + l = Coordinate() + t = Coordinate() + r = Coordinate() + b = Coordinate() + + def __init__(self, + l=None, + t=None, + r=None, + b=None, + ): + self.l = l + self.t = t + self.r = r + self.b = b + + +class AdjPoint2D(Serialisable): + + x = Coordinate() + y = Coordinate() + + def __init__(self, + x=None, + y=None, + ): + self.x = x + self.y = y + + +class ConnectionSite(Serialisable): + + ang = MinMax(min=0, max=360) # guess work, can also be a name + pos = Typed(expected_type=AdjPoint2D, ) + + def __init__(self, + ang=None, + pos=None, + ): + self.ang = ang + self.pos = pos + + +class ConnectionSiteList(Serialisable): + + cxn = Typed(expected_type=ConnectionSite, allow_none=True) + + def __init__(self, + cxn=None, + ): + self.cxn = cxn + + +class AdjustHandleList(Serialisable): + + pass + +class GeomGuide(Serialisable): + + name = String() + fmla = String() + + def __init__(self, + name=None, + fmla=None, + ): + self.name = name + self.fmla = fmla + + +class GeomGuideList(Serialisable): + + gd = Typed(expected_type=GeomGuide, allow_none=True) + + def __init__(self, + gd=None, + ): + self.gd = gd + + +class CustomGeometry2D(Serialisable): + + avLst = Typed(expected_type=GeomGuideList, allow_none=True) + gdLst = Typed(expected_type=GeomGuideList, allow_none=True) + ahLst = Typed(expected_type=AdjustHandleList, allow_none=True) + cxnLst = Typed(expected_type=ConnectionSiteList, allow_none=True) + #rect = Typed(expected_type=GeomRect, allow_none=True) + pathLst = Typed(expected_type=Path2DList, ) + + def __init__(self, + avLst=None, + gdLst=None, + ahLst=None, + cxnLst=None, + rect=None, + pathLst=None, + ): + self.avLst = avLst + self.gdLst = gdLst + self.ahLst = ahLst + self.cxnLst = cxnLst + self.rect = None + self.pathLst = pathLst + + +class PresetGeometry2D(Serialisable): + + namespace = DRAWING_NS + + prst = Set(values=( + ['line', 'lineInv', 'triangle', 'rtTriangle', 'rect', + 'diamond', 'parallelogram', 'trapezoid', 'nonIsoscelesTrapezoid', + 'pentagon', 'hexagon', 'heptagon', 'octagon', 'decagon', 'dodecagon', + 'star4', 'star5', 'star6', 'star7', 'star8', 'star10', 'star12', + 'star16', 'star24', 'star32', 'roundRect', 'round1Rect', + 'round2SameRect', 'round2DiagRect', 'snipRoundRect', 'snip1Rect', + 'snip2SameRect', 'snip2DiagRect', 'plaque', 'ellipse', 'teardrop', + 'homePlate', 'chevron', 'pieWedge', 'pie', 'blockArc', 'donut', + 'noSmoking', 'rightArrow', 'leftArrow', 'upArrow', 'downArrow', + 'stripedRightArrow', 'notchedRightArrow', 'bentUpArrow', + 'leftRightArrow', 'upDownArrow', 'leftUpArrow', 'leftRightUpArrow', + 'quadArrow', 'leftArrowCallout', 'rightArrowCallout', 'upArrowCallout', + 'downArrowCallout', 'leftRightArrowCallout', 'upDownArrowCallout', + 'quadArrowCallout', 'bentArrow', 'uturnArrow', 'circularArrow', + 'leftCircularArrow', 'leftRightCircularArrow', 'curvedRightArrow', + 'curvedLeftArrow', 'curvedUpArrow', 'curvedDownArrow', 'swooshArrow', + 'cube', 'can', 'lightningBolt', 'heart', 'sun', 'moon', 'smileyFace', + 'irregularSeal1', 'irregularSeal2', 'foldedCorner', 'bevel', 'frame', + 'halfFrame', 'corner', 'diagStripe', 'chord', 'arc', 'leftBracket', + 'rightBracket', 'leftBrace', 'rightBrace', 'bracketPair', 'bracePair', + 'straightConnector1', 'bentConnector2', 'bentConnector3', + 'bentConnector4', 'bentConnector5', 'curvedConnector2', + 'curvedConnector3', 'curvedConnector4', 'curvedConnector5', 'callout1', + 'callout2', 'callout3', 'accentCallout1', 'accentCallout2', + 'accentCallout3', 'borderCallout1', 'borderCallout2', 'borderCallout3', + 'accentBorderCallout1', 'accentBorderCallout2', 'accentBorderCallout3', + 'wedgeRectCallout', 'wedgeRoundRectCallout', 'wedgeEllipseCallout', + 'cloudCallout', 'cloud', 'ribbon', 'ribbon2', 'ellipseRibbon', + 'ellipseRibbon2', 'leftRightRibbon', 'verticalScroll', + 'horizontalScroll', 'wave', 'doubleWave', 'plus', 'flowChartProcess', + 'flowChartDecision', 'flowChartInputOutput', + 'flowChartPredefinedProcess', 'flowChartInternalStorage', + 'flowChartDocument', 'flowChartMultidocument', 'flowChartTerminator', + 'flowChartPreparation', 'flowChartManualInput', + 'flowChartManualOperation', 'flowChartConnector', 'flowChartPunchedCard', + 'flowChartPunchedTape', 'flowChartSummingJunction', 'flowChartOr', + 'flowChartCollate', 'flowChartSort', 'flowChartExtract', + 'flowChartMerge', 'flowChartOfflineStorage', 'flowChartOnlineStorage', + 'flowChartMagneticTape', 'flowChartMagneticDisk', + 'flowChartMagneticDrum', 'flowChartDisplay', 'flowChartDelay', + 'flowChartAlternateProcess', 'flowChartOffpageConnector', + 'actionButtonBlank', 'actionButtonHome', 'actionButtonHelp', + 'actionButtonInformation', 'actionButtonForwardNext', + 'actionButtonBackPrevious', 'actionButtonEnd', 'actionButtonBeginning', + 'actionButtonReturn', 'actionButtonDocument', 'actionButtonSound', + 'actionButtonMovie', 'gear6', 'gear9', 'funnel', 'mathPlus', 'mathMinus', + 'mathMultiply', 'mathDivide', 'mathEqual', 'mathNotEqual', 'cornerTabs', + 'squareTabs', 'plaqueTabs', 'chartX', 'chartStar', 'chartPlus'])) + avLst = Typed(expected_type=GeomGuideList, allow_none=True) + + def __init__(self, + prst=None, + avLst=None, + ): + self.prst = prst + self.avLst = avLst + + +class FontReference(Serialisable): + + idx = NoneSet(values=(['major', 'minor'])) + + def __init__(self, + idx=None, + ): + self.idx = idx + + +class StyleMatrixReference(Serialisable): + + idx = Integer() + + def __init__(self, + idx=None, + ): + self.idx = idx + + +class ShapeStyle(Serialisable): + + lnRef = Typed(expected_type=StyleMatrixReference, ) + fillRef = Typed(expected_type=StyleMatrixReference, ) + effectRef = Typed(expected_type=StyleMatrixReference, ) + fontRef = Typed(expected_type=FontReference, ) + + def __init__(self, + lnRef=None, + fillRef=None, + effectRef=None, + fontRef=None, + ): + self.lnRef = lnRef + self.fillRef = fillRef + self.effectRef = effectRef + self.fontRef = fontRef diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/graphic.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/graphic.py new file mode 100644 index 00000000..2c340870 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/graphic.py @@ -0,0 +1,177 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.constants import CHART_NS, DRAWING_NS +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + String, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +from .effect import ( + EffectList, + EffectContainer, +) +from .fill import ( + Blip, + GradientFillProperties, + BlipFillProperties, +) +from .picture import PictureFrame +from .properties import ( + NonVisualDrawingProps, + NonVisualGroupShape, + GroupShapeProperties, +) +from .relation import ChartRelation +from .xdr import XDRTransform2D + + +class GraphicFrameLocking(Serialisable): + + noGrp = Bool(allow_none=True) + noDrilldown = Bool(allow_none=True) + noSelect = Bool(allow_none=True) + noChangeAspect = Bool(allow_none=True) + noMove = Bool(allow_none=True) + noResize = Bool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + noGrp=None, + noDrilldown=None, + noSelect=None, + noChangeAspect=None, + noMove=None, + noResize=None, + extLst=None, + ): + self.noGrp = noGrp + self.noDrilldown = noDrilldown + self.noSelect = noSelect + self.noChangeAspect = noChangeAspect + self.noMove = noMove + self.noResize = noResize + self.extLst = extLst + + +class NonVisualGraphicFrameProperties(Serialisable): + + tagname = "cNvGraphicFramePr" + + graphicFrameLocks = Typed(expected_type=GraphicFrameLocking, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + graphicFrameLocks=None, + extLst=None, + ): + self.graphicFrameLocks = graphicFrameLocks + self.extLst = extLst + + +class NonVisualGraphicFrame(Serialisable): + + tagname = "nvGraphicFramePr" + + cNvPr = Typed(expected_type=NonVisualDrawingProps) + cNvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrameProperties) + + __elements__ = ('cNvPr', 'cNvGraphicFramePr') + + def __init__(self, + cNvPr=None, + cNvGraphicFramePr=None, + ): + if cNvPr is None: + cNvPr = NonVisualDrawingProps(id=0, name="Chart 0") + self.cNvPr = cNvPr + if cNvGraphicFramePr is None: + cNvGraphicFramePr = NonVisualGraphicFrameProperties() + self.cNvGraphicFramePr = cNvGraphicFramePr + + +class GraphicData(Serialisable): + + tagname = "graphicData" + namespace = DRAWING_NS + + uri = String() + chart = Typed(expected_type=ChartRelation, allow_none=True) + + + def __init__(self, + uri=CHART_NS, + chart=None, + ): + self.uri = uri + self.chart = chart + + +class GraphicObject(Serialisable): + + tagname = "graphic" + namespace = DRAWING_NS + + graphicData = Typed(expected_type=GraphicData) + + def __init__(self, + graphicData=None, + ): + if graphicData is None: + graphicData = GraphicData() + self.graphicData = graphicData + + +class GraphicFrame(Serialisable): + + tagname = "graphicFrame" + + nvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrame) + xfrm = Typed(expected_type=XDRTransform2D) + graphic = Typed(expected_type=GraphicObject) + macro = String(allow_none=True) + fPublished = Bool(allow_none=True) + + __elements__ = ('nvGraphicFramePr', 'xfrm', 'graphic', 'macro', 'fPublished') + + def __init__(self, + nvGraphicFramePr=None, + xfrm=None, + graphic=None, + macro=None, + fPublished=None, + ): + if nvGraphicFramePr is None: + nvGraphicFramePr = NonVisualGraphicFrame() + self.nvGraphicFramePr = nvGraphicFramePr + if xfrm is None: + xfrm = XDRTransform2D() + self.xfrm = xfrm + if graphic is None: + graphic = GraphicObject() + self.graphic = graphic + self.macro = macro + self.fPublished = fPublished + + +class GroupShape(Serialisable): + + nvGrpSpPr = Typed(expected_type=NonVisualGroupShape) + nonVisualProperties = Alias("nvGrpSpPr") + grpSpPr = Typed(expected_type=GroupShapeProperties) + visualProperties = Alias("grpSpPr") + pic = Typed(expected_type=PictureFrame, allow_none=True) + + __elements__ = ["nvGrpSpPr", "grpSpPr", "pic"] + + def __init__(self, + nvGrpSpPr=None, + grpSpPr=None, + pic=None, + ): + self.nvGrpSpPr = nvGrpSpPr + self.grpSpPr = grpSpPr + self.pic = pic diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/image.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/image.py new file mode 100644 index 00000000..9d0446fe --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/image.py @@ -0,0 +1,65 @@ +# Copyright (c) 2010-2024 openpyxl + +from io import BytesIO + +try: + from PIL import Image as PILImage +except ImportError: + PILImage = False + + +def _import_image(img): + if not PILImage: + raise ImportError('You must install Pillow to fetch image objects') + + if not isinstance(img, PILImage.Image): + img = PILImage.open(img) + + return img + + +class Image: + """Image in a spreadsheet""" + + _id = 1 + _path = "/xl/media/image{0}.{1}" + anchor = "A1" + + def __init__(self, img): + + self.ref = img + mark_to_close = isinstance(img, str) + image = _import_image(img) + self.width, self.height = image.size + + try: + self.format = image.format.lower() + except AttributeError: + self.format = "png" + if mark_to_close: + # PIL instances created for metadata should be closed. + image.close() + + + def _data(self): + """ + Return image data, convert to supported types if necessary + """ + img = _import_image(self.ref) + # don't convert these file formats + if self.format in ['gif', 'jpeg', 'png']: + img.fp.seek(0) + fp = img.fp + else: + fp = BytesIO() + img.save(fp, format="png") + fp.seek(0) + + data = fp.read() + fp.close() + return data + + + @property + def path(self): + return self._path.format(self._id, self.format) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/line.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/line.py new file mode 100644 index 00000000..43388e63 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/line.py @@ -0,0 +1,144 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + MinMax, + NoneSet, + Alias, + Sequence +) + +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedNoneSet, + EmptyTag, +) +from openpyxl.xml.constants import DRAWING_NS + +from .colors import ColorChoiceDescriptor +from .fill import GradientFillProperties, PatternFillProperties +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +""" +Line elements from drawing main schema +""" + + +class LineEndProperties(Serialisable): + + tagname = "end" + namespace = DRAWING_NS + + type = NoneSet(values=(['none', 'triangle', 'stealth', 'diamond', 'oval', 'arrow'])) + w = NoneSet(values=(['sm', 'med', 'lg'])) + len = NoneSet(values=(['sm', 'med', 'lg'])) + + def __init__(self, + type=None, + w=None, + len=None, + ): + self.type = type + self.w = w + self.len = len + + +class DashStop(Serialisable): + + tagname = "ds" + namespace = DRAWING_NS + + d = Integer() + length = Alias('d') + sp = Integer() + space = Alias('sp') + + def __init__(self, + d=0, + sp=0, + ): + self.d = d + self.sp = sp + + +class DashStopList(Serialisable): + + ds = Sequence(expected_type=DashStop, allow_none=True) + + def __init__(self, + ds=None, + ): + self.ds = ds + + +class LineProperties(Serialisable): + + tagname = "ln" + namespace = DRAWING_NS + + w = MinMax(min=0, max=20116800, allow_none=True) # EMU + width = Alias('w') + cap = NoneSet(values=(['rnd', 'sq', 'flat'])) + cmpd = NoneSet(values=(['sng', 'dbl', 'thickThin', 'thinThick', 'tri'])) + algn = NoneSet(values=(['ctr', 'in'])) + + noFill = EmptyTag() + solidFill = ColorChoiceDescriptor() + gradFill = Typed(expected_type=GradientFillProperties, allow_none=True) + pattFill = Typed(expected_type=PatternFillProperties, allow_none=True) + + prstDash = NestedNoneSet(values=(['solid', 'dot', 'dash', 'lgDash', 'dashDot', + 'lgDashDot', 'lgDashDotDot', 'sysDash', 'sysDot', 'sysDashDot', + 'sysDashDotDot']), namespace=namespace) + dashStyle = Alias('prstDash') + + custDash = Typed(expected_type=DashStop, allow_none=True) + + round = EmptyTag() + bevel = EmptyTag() + miter = NestedInteger(allow_none=True, attribute="lim") + + headEnd = Typed(expected_type=LineEndProperties, allow_none=True) + tailEnd = Typed(expected_type=LineEndProperties, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ('noFill', 'solidFill', 'gradFill', 'pattFill', + 'prstDash', 'custDash', 'round', 'bevel', 'miter', 'headEnd', 'tailEnd') + + def __init__(self, + w=None, + cap=None, + cmpd=None, + algn=None, + noFill=None, + solidFill=None, + gradFill=None, + pattFill=None, + prstDash=None, + custDash=None, + round=None, + bevel=None, + miter=None, + headEnd=None, + tailEnd=None, + extLst=None, + ): + self.w = w + self.cap = cap + self.cmpd = cmpd + self.algn = algn + self.noFill = noFill + self.solidFill = solidFill + self.gradFill = gradFill + self.pattFill = pattFill + if prstDash is None: + prstDash = "solid" + self.prstDash = prstDash + self.custDash = custDash + self.round = round + self.bevel = bevel + self.miter = miter + self.headEnd = headEnd + self.tailEnd = tailEnd diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/picture.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/picture.py new file mode 100644 index 00000000..9a83facf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/picture.py @@ -0,0 +1,144 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.constants import DRAWING_NS + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + String, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +from openpyxl.chart.shapes import GraphicalProperties + +from .fill import BlipFillProperties +from .properties import NonVisualDrawingProps +from .geometry import ShapeStyle + + +class PictureLocking(Serialisable): + + tagname = "picLocks" + namespace = DRAWING_NS + + # Using attribute group AG_Locking + noCrop = Bool(allow_none=True) + noGrp = Bool(allow_none=True) + noSelect = Bool(allow_none=True) + noRot = Bool(allow_none=True) + noChangeAspect = Bool(allow_none=True) + noMove = Bool(allow_none=True) + noResize = Bool(allow_none=True) + noEditPoints = Bool(allow_none=True) + noAdjustHandles = Bool(allow_none=True) + noChangeArrowheads = Bool(allow_none=True) + noChangeShapeType = Bool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + noCrop=None, + noGrp=None, + noSelect=None, + noRot=None, + noChangeAspect=None, + noMove=None, + noResize=None, + noEditPoints=None, + noAdjustHandles=None, + noChangeArrowheads=None, + noChangeShapeType=None, + extLst=None, + ): + self.noCrop = noCrop + self.noGrp = noGrp + self.noSelect = noSelect + self.noRot = noRot + self.noChangeAspect = noChangeAspect + self.noMove = noMove + self.noResize = noResize + self.noEditPoints = noEditPoints + self.noAdjustHandles = noAdjustHandles + self.noChangeArrowheads = noChangeArrowheads + self.noChangeShapeType = noChangeShapeType + + +class NonVisualPictureProperties(Serialisable): + + tagname = "cNvPicPr" + + preferRelativeResize = Bool(allow_none=True) + picLocks = Typed(expected_type=PictureLocking, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ("picLocks",) + + def __init__(self, + preferRelativeResize=None, + picLocks=None, + extLst=None, + ): + self.preferRelativeResize = preferRelativeResize + self.picLocks = picLocks + + +class PictureNonVisual(Serialisable): + + tagname = "nvPicPr" + + cNvPr = Typed(expected_type=NonVisualDrawingProps, ) + cNvPicPr = Typed(expected_type=NonVisualPictureProperties, ) + + __elements__ = ("cNvPr", "cNvPicPr") + + def __init__(self, + cNvPr=None, + cNvPicPr=None, + ): + if cNvPr is None: + cNvPr = NonVisualDrawingProps(id=0, name="Image 1", descr="Name of file") + self.cNvPr = cNvPr + if cNvPicPr is None: + cNvPicPr = NonVisualPictureProperties() + self.cNvPicPr = cNvPicPr + + + + +class PictureFrame(Serialisable): + + tagname = "pic" + + macro = String(allow_none=True) + fPublished = Bool(allow_none=True) + nvPicPr = Typed(expected_type=PictureNonVisual, ) + blipFill = Typed(expected_type=BlipFillProperties, ) + spPr = Typed(expected_type=GraphicalProperties, ) + graphicalProperties = Alias('spPr') + style = Typed(expected_type=ShapeStyle, allow_none=True) + + __elements__ = ("nvPicPr", "blipFill", "spPr", "style") + + def __init__(self, + macro=None, + fPublished=None, + nvPicPr=None, + blipFill=None, + spPr=None, + style=None, + ): + self.macro = macro + self.fPublished = fPublished + if nvPicPr is None: + nvPicPr = PictureNonVisual() + self.nvPicPr = nvPicPr + if blipFill is None: + blipFill = BlipFillProperties() + self.blipFill = blipFill + if spPr is None: + spPr = GraphicalProperties() + self.spPr = spPr + self.style = style diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/properties.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/properties.py new file mode 100644 index 00000000..77b00728 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/properties.py @@ -0,0 +1,174 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.constants import DRAWING_NS +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Integer, + Set, + String, + Alias, + NoneSet, +) +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +from .geometry import GroupTransform2D, Scene3D +from .text import Hyperlink + + +class GroupShapeProperties(Serialisable): + + tagname = "grpSpPr" + + bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray', + 'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden'])) + xfrm = Typed(expected_type=GroupTransform2D, allow_none=True) + scene3d = Typed(expected_type=Scene3D, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + bwMode=None, + xfrm=None, + scene3d=None, + extLst=None, + ): + self.bwMode = bwMode + self.xfrm = xfrm + self.scene3d = scene3d + self.extLst = extLst + + +class GroupLocking(Serialisable): + + tagname = "grpSpLocks" + namespace = DRAWING_NS + + noGrp = Bool(allow_none=True) + noUngrp = Bool(allow_none=True) + noSelect = Bool(allow_none=True) + noRot = Bool(allow_none=True) + noChangeAspect = Bool(allow_none=True) + noMove = Bool(allow_none=True) + noResize = Bool(allow_none=True) + noChangeArrowheads = Bool(allow_none=True) + noEditPoints = Bool(allow_none=True) + noAdjustHandles = Bool(allow_none=True) + noChangeArrowheads = Bool(allow_none=True) + noChangeShapeType = Bool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + noGrp=None, + noUngrp=None, + noSelect=None, + noRot=None, + noChangeAspect=None, + noChangeArrowheads=None, + noMove=None, + noResize=None, + noEditPoints=None, + noAdjustHandles=None, + noChangeShapeType=None, + extLst=None, + ): + self.noGrp = noGrp + self.noUngrp = noUngrp + self.noSelect = noSelect + self.noRot = noRot + self.noChangeAspect = noChangeAspect + self.noChangeArrowheads = noChangeArrowheads + self.noMove = noMove + self.noResize = noResize + self.noEditPoints = noEditPoints + self.noAdjustHandles = noAdjustHandles + self.noChangeShapeType = noChangeShapeType + + +class NonVisualGroupDrawingShapeProps(Serialisable): + + tagname = "cNvGrpSpPr" + + grpSpLocks = Typed(expected_type=GroupLocking, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ("grpSpLocks",) + + def __init__(self, + grpSpLocks=None, + extLst=None, + ): + self.grpSpLocks = grpSpLocks + + +class NonVisualDrawingShapeProps(Serialisable): + + tagname = "cNvSpPr" + + spLocks = Typed(expected_type=GroupLocking, allow_none=True) + txBax = Bool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ("spLocks", "txBax") + + def __init__(self, + spLocks=None, + txBox=None, + extLst=None, + ): + self.spLocks = spLocks + self.txBox = txBox + + +class NonVisualDrawingProps(Serialisable): + + tagname = "cNvPr" + + id = Integer() + name = String() + descr = String(allow_none=True) + hidden = Bool(allow_none=True) + title = String(allow_none=True) + hlinkClick = Typed(expected_type=Hyperlink, allow_none=True) + hlinkHover = Typed(expected_type=Hyperlink, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ["hlinkClick", "hlinkHover"] + + def __init__(self, + id=None, + name=None, + descr=None, + hidden=None, + title=None, + hlinkClick=None, + hlinkHover=None, + extLst=None, + ): + self.id = id + self.name = name + self.descr = descr + self.hidden = hidden + self.title = title + self.hlinkClick = hlinkClick + self.hlinkHover = hlinkHover + self.extLst = extLst + +class NonVisualGroupShape(Serialisable): + + tagname = "nvGrpSpPr" + + cNvPr = Typed(expected_type=NonVisualDrawingProps) + cNvGrpSpPr = Typed(expected_type=NonVisualGroupDrawingShapeProps) + + __elements__ = ("cNvPr", "cNvGrpSpPr") + + def __init__(self, + cNvPr=None, + cNvGrpSpPr=None, + ): + self.cNvPr = cNvPr + self.cNvGrpSpPr = cNvGrpSpPr + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/relation.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/relation.py new file mode 100644 index 00000000..01632934 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/relation.py @@ -0,0 +1,17 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.constants import CHART_NS + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.excel import Relation + + +class ChartRelation(Serialisable): + + tagname = "chart" + namespace = CHART_NS + + id = Relation() + + def __init__(self, id): + self.id = id diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/spreadsheet_drawing.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/spreadsheet_drawing.py new file mode 100644 index 00000000..4f378ca2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/spreadsheet_drawing.py @@ -0,0 +1,382 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + NoneSet, + Integer, + Sequence, + Alias, +) +from openpyxl.descriptors.nested import ( + NestedText, + NestedNoneSet, +) +from openpyxl.descriptors.excel import Relation + +from openpyxl.packaging.relationship import ( + Relationship, + RelationshipList, +) +from openpyxl.utils import coordinate_to_tuple +from openpyxl.utils.units import ( + cm_to_EMU, + pixels_to_EMU, +) +from openpyxl.drawing.image import Image + +from openpyxl.xml.constants import SHEET_DRAWING_NS + +from openpyxl.chart._chart import ChartBase +from .xdr import ( + XDRPoint2D, + XDRPositiveSize2D, +) +from .fill import Blip +from .connector import Shape +from .graphic import ( + GroupShape, + GraphicFrame, + ) +from .geometry import PresetGeometry2D +from .picture import PictureFrame +from .relation import ChartRelation + + +class AnchorClientData(Serialisable): + + fLocksWithSheet = Bool(allow_none=True) + fPrintsWithSheet = Bool(allow_none=True) + + def __init__(self, + fLocksWithSheet=None, + fPrintsWithSheet=None, + ): + self.fLocksWithSheet = fLocksWithSheet + self.fPrintsWithSheet = fPrintsWithSheet + + +class AnchorMarker(Serialisable): + + tagname = "marker" + + col = NestedText(expected_type=int) + colOff = NestedText(expected_type=int) + row = NestedText(expected_type=int) + rowOff = NestedText(expected_type=int) + + def __init__(self, + col=0, + colOff=0, + row=0, + rowOff=0, + ): + self.col = col + self.colOff = colOff + self.row = row + self.rowOff = rowOff + + +class _AnchorBase(Serialisable): + + #one of + sp = Typed(expected_type=Shape, allow_none=True) + shape = Alias("sp") + grpSp = Typed(expected_type=GroupShape, allow_none=True) + groupShape = Alias("grpSp") + graphicFrame = Typed(expected_type=GraphicFrame, allow_none=True) + cxnSp = Typed(expected_type=Shape, allow_none=True) + connectionShape = Alias("cxnSp") + pic = Typed(expected_type=PictureFrame, allow_none=True) + contentPart = Relation() + + clientData = Typed(expected_type=AnchorClientData) + + __elements__ = ('sp', 'grpSp', 'graphicFrame', + 'cxnSp', 'pic', 'contentPart', 'clientData') + + def __init__(self, + clientData=None, + sp=None, + grpSp=None, + graphicFrame=None, + cxnSp=None, + pic=None, + contentPart=None + ): + if clientData is None: + clientData = AnchorClientData() + self.clientData = clientData + self.sp = sp + self.grpSp = grpSp + self.graphicFrame = graphicFrame + self.cxnSp = cxnSp + self.pic = pic + self.contentPart = contentPart + + +class AbsoluteAnchor(_AnchorBase): + + tagname = "absoluteAnchor" + + pos = Typed(expected_type=XDRPoint2D) + ext = Typed(expected_type=XDRPositiveSize2D) + + sp = _AnchorBase.sp + grpSp = _AnchorBase.grpSp + graphicFrame = _AnchorBase.graphicFrame + cxnSp = _AnchorBase.cxnSp + pic = _AnchorBase.pic + contentPart = _AnchorBase.contentPart + clientData = _AnchorBase.clientData + + __elements__ = ('pos', 'ext') + _AnchorBase.__elements__ + + def __init__(self, + pos=None, + ext=None, + **kw + ): + if pos is None: + pos = XDRPoint2D(0, 0) + self.pos = pos + if ext is None: + ext = XDRPositiveSize2D(0, 0) + self.ext = ext + super().__init__(**kw) + + +class OneCellAnchor(_AnchorBase): + + tagname = "oneCellAnchor" + + _from = Typed(expected_type=AnchorMarker) + ext = Typed(expected_type=XDRPositiveSize2D) + + sp = _AnchorBase.sp + grpSp = _AnchorBase.grpSp + graphicFrame = _AnchorBase.graphicFrame + cxnSp = _AnchorBase.cxnSp + pic = _AnchorBase.pic + contentPart = _AnchorBase.contentPart + clientData = _AnchorBase.clientData + + __elements__ = ('_from', 'ext') + _AnchorBase.__elements__ + + + def __init__(self, + _from=None, + ext=None, + **kw + ): + if _from is None: + _from = AnchorMarker() + self._from = _from + if ext is None: + ext = XDRPositiveSize2D(0, 0) + self.ext = ext + super().__init__(**kw) + + +class TwoCellAnchor(_AnchorBase): + + tagname = "twoCellAnchor" + + editAs = NoneSet(values=(['twoCell', 'oneCell', 'absolute'])) + _from = Typed(expected_type=AnchorMarker) + to = Typed(expected_type=AnchorMarker) + + sp = _AnchorBase.sp + grpSp = _AnchorBase.grpSp + graphicFrame = _AnchorBase.graphicFrame + cxnSp = _AnchorBase.cxnSp + pic = _AnchorBase.pic + contentPart = _AnchorBase.contentPart + clientData = _AnchorBase.clientData + + __elements__ = ('_from', 'to') + _AnchorBase.__elements__ + + def __init__(self, + editAs=None, + _from=None, + to=None, + **kw + ): + self.editAs = editAs + if _from is None: + _from = AnchorMarker() + self._from = _from + if to is None: + to = AnchorMarker() + self.to = to + super().__init__(**kw) + + +def _check_anchor(obj): + """ + Check whether an object has an existing Anchor object + If not create a OneCellAnchor using the provided coordinate + """ + anchor = obj.anchor + if not isinstance(anchor, _AnchorBase): + row, col = coordinate_to_tuple(anchor.upper()) + anchor = OneCellAnchor() + anchor._from.row = row -1 + anchor._from.col = col -1 + if isinstance(obj, ChartBase): + anchor.ext.width = cm_to_EMU(obj.width) + anchor.ext.height = cm_to_EMU(obj.height) + elif isinstance(obj, Image): + anchor.ext.width = pixels_to_EMU(obj.width) + anchor.ext.height = pixels_to_EMU(obj.height) + return anchor + + +class SpreadsheetDrawing(Serialisable): + + tagname = "wsDr" + mime_type = "application/vnd.openxmlformats-officedocument.drawing+xml" + _rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" + _path = PartName="/xl/drawings/drawing{0}.xml" + _id = None + + twoCellAnchor = Sequence(expected_type=TwoCellAnchor, allow_none=True) + oneCellAnchor = Sequence(expected_type=OneCellAnchor, allow_none=True) + absoluteAnchor = Sequence(expected_type=AbsoluteAnchor, allow_none=True) + + __elements__ = ("twoCellAnchor", "oneCellAnchor", "absoluteAnchor") + + def __init__(self, + twoCellAnchor=(), + oneCellAnchor=(), + absoluteAnchor=(), + ): + self.twoCellAnchor = twoCellAnchor + self.oneCellAnchor = oneCellAnchor + self.absoluteAnchor = absoluteAnchor + self.charts = [] + self.images = [] + self._rels = [] + + + def __hash__(self): + """ + Just need to check for identity + """ + return id(self) + + + def __bool__(self): + return bool(self.charts) or bool(self.images) + + + + def _write(self): + """ + create required structure and the serialise + """ + anchors = [] + for idx, obj in enumerate(self.charts + self.images, 1): + anchor = _check_anchor(obj) + if isinstance(obj, ChartBase): + rel = Relationship(type="chart", Target=obj.path) + anchor.graphicFrame = self._chart_frame(idx) + elif isinstance(obj, Image): + rel = Relationship(type="image", Target=obj.path) + child = anchor.pic or anchor.groupShape and anchor.groupShape.pic + if not child: + anchor.pic = self._picture_frame(idx) + else: + child.blipFill.blip.embed = "rId{0}".format(idx) + + anchors.append(anchor) + self._rels.append(rel) + + for a in anchors: + if isinstance(a, OneCellAnchor): + self.oneCellAnchor.append(a) + elif isinstance(a, TwoCellAnchor): + self.twoCellAnchor.append(a) + else: + self.absoluteAnchor.append(a) + + tree = self.to_tree() + tree.set('xmlns', SHEET_DRAWING_NS) + return tree + + + def _chart_frame(self, idx): + chart_rel = ChartRelation(f"rId{idx}") + frame = GraphicFrame() + nv = frame.nvGraphicFramePr.cNvPr + nv.id = idx + nv.name = "Chart {0}".format(idx) + frame.graphic.graphicData.chart = chart_rel + return frame + + + def _picture_frame(self, idx): + pic = PictureFrame() + pic.nvPicPr.cNvPr.descr = "Picture" + pic.nvPicPr.cNvPr.id = idx + pic.nvPicPr.cNvPr.name = "Image {0}".format(idx) + + pic.blipFill.blip = Blip() + pic.blipFill.blip.embed = "rId{0}".format(idx) + pic.blipFill.blip.cstate = "print" + + pic.spPr.prstGeom = PresetGeometry2D(prst="rect") + pic.spPr.ln = None + return pic + + + def _write_rels(self): + rels = RelationshipList() + for r in self._rels: + rels.append(r) + return rels.to_tree() + + + @property + def path(self): + return self._path.format(self._id) + + + @property + def _chart_rels(self): + """ + Get relationship information for each chart and bind anchor to it + """ + rels = [] + anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor + for anchor in anchors: + if anchor.graphicFrame is not None: + graphic = anchor.graphicFrame.graphic + rel = graphic.graphicData.chart + if rel is not None: + rel.anchor = anchor + rel.anchor.graphicFrame = None + rels.append(rel) + return rels + + + @property + def _blip_rels(self): + """ + Get relationship information for each blip and bind anchor to it + + Images that are not part of the XLSX package will be ignored. + """ + rels = [] + anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor + + for anchor in anchors: + child = anchor.pic or anchor.groupShape and anchor.groupShape.pic + if child and child.blipFill: + rel = child.blipFill.blip + if rel is not None and rel.embed: + rel.anchor = anchor + rels.append(rel) + + return rels diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/text.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/text.py new file mode 100644 index 00000000..5bdc771f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/text.py @@ -0,0 +1,717 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + Set, + NoneSet, + Sequence, + String, + Bool, + MinMax, + Integer +) +from openpyxl.descriptors.excel import ( + HexBinary, + Coordinate, + Relation, +) +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedText, + NestedValue, + EmptyTag +) +from openpyxl.xml.constants import DRAWING_NS + + +from .colors import ColorChoiceDescriptor +from .effect import ( + EffectList, + EffectContainer, +) +from .fill import( + GradientFillProperties, + BlipFillProperties, + PatternFillProperties, + Blip +) +from .geometry import ( + LineProperties, + Color, + Scene3D +) + +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList +from openpyxl.descriptors.nested import NestedBool + + +class EmbeddedWAVAudioFile(Serialisable): + + name = String(allow_none=True) + + def __init__(self, + name=None, + ): + self.name = name + + +class Hyperlink(Serialisable): + + tagname = "hlinkClick" + namespace = DRAWING_NS + + invalidUrl = String(allow_none=True) + action = String(allow_none=True) + tgtFrame = String(allow_none=True) + tooltip = String(allow_none=True) + history = Bool(allow_none=True) + highlightClick = Bool(allow_none=True) + endSnd = Bool(allow_none=True) + snd = Typed(expected_type=EmbeddedWAVAudioFile, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + id = Relation(allow_none=True) + + __elements__ = ('snd',) + + def __init__(self, + invalidUrl=None, + action=None, + tgtFrame=None, + tooltip=None, + history=None, + highlightClick=None, + endSnd=None, + snd=None, + extLst=None, + id=None, + ): + self.invalidUrl = invalidUrl + self.action = action + self.tgtFrame = tgtFrame + self.tooltip = tooltip + self.history = history + self.highlightClick = highlightClick + self.endSnd = endSnd + self.snd = snd + self.id = id + + +class Font(Serialisable): + + tagname = "latin" + namespace = DRAWING_NS + + typeface = String() + panose = HexBinary(allow_none=True) + pitchFamily = MinMax(min=0, max=52, allow_none=True) + charset = Integer(allow_none=True) + + def __init__(self, + typeface=None, + panose=None, + pitchFamily=None, + charset=None, + ): + self.typeface = typeface + self.panose = panose + self.pitchFamily = pitchFamily + self.charset = charset + + +class CharacterProperties(Serialisable): + + tagname = "defRPr" + namespace = DRAWING_NS + + kumimoji = Bool(allow_none=True) + lang = String(allow_none=True) + altLang = String(allow_none=True) + sz = MinMax(allow_none=True, min=100, max=400000) # 100ths of a point + b = Bool(allow_none=True) + i = Bool(allow_none=True) + u = NoneSet(values=(['words', 'sng', 'dbl', 'heavy', 'dotted', + 'dottedHeavy', 'dash', 'dashHeavy', 'dashLong', 'dashLongHeavy', + 'dotDash', 'dotDashHeavy', 'dotDotDash', 'dotDotDashHeavy', 'wavy', + 'wavyHeavy', 'wavyDbl'])) + strike = NoneSet(values=(['noStrike', 'sngStrike', 'dblStrike'])) + kern = Integer(allow_none=True) + cap = NoneSet(values=(['small', 'all'])) + spc = Integer(allow_none=True) + normalizeH = Bool(allow_none=True) + baseline = Integer(allow_none=True) + noProof = Bool(allow_none=True) + dirty = Bool(allow_none=True) + err = Bool(allow_none=True) + smtClean = Bool(allow_none=True) + smtId = Integer(allow_none=True) + bmk = String(allow_none=True) + ln = Typed(expected_type=LineProperties, allow_none=True) + highlight = Typed(expected_type=Color, allow_none=True) + latin = Typed(expected_type=Font, allow_none=True) + ea = Typed(expected_type=Font, allow_none=True) + cs = Typed(expected_type=Font, allow_none=True) + sym = Typed(expected_type=Font, allow_none=True) + hlinkClick = Typed(expected_type=Hyperlink, allow_none=True) + hlinkMouseOver = Typed(expected_type=Hyperlink, allow_none=True) + rtl = NestedBool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + # uses element group EG_FillProperties + noFill = EmptyTag(namespace=DRAWING_NS) + solidFill = ColorChoiceDescriptor() + gradFill = Typed(expected_type=GradientFillProperties, allow_none=True) + blipFill = Typed(expected_type=BlipFillProperties, allow_none=True) + pattFill = Typed(expected_type=PatternFillProperties, allow_none=True) + grpFill = EmptyTag(namespace=DRAWING_NS) + # uses element group EG_EffectProperties + effectLst = Typed(expected_type=EffectList, allow_none=True) + effectDag = Typed(expected_type=EffectContainer, allow_none=True) + # uses element group EG_TextUnderlineLine + uLnTx = EmptyTag() + uLn = Typed(expected_type=LineProperties, allow_none=True) + # uses element group EG_TextUnderlineFill + uFillTx = EmptyTag() + uFill = EmptyTag() + + __elements__ = ('ln', 'noFill', 'solidFill', 'gradFill', 'blipFill', + 'pattFill', 'grpFill', 'effectLst', 'effectDag', 'highlight','uLnTx', + 'uLn', 'uFillTx', 'uFill', 'latin', 'ea', 'cs', 'sym', 'hlinkClick', + 'hlinkMouseOver', 'rtl', ) + + def __init__(self, + kumimoji=None, + lang=None, + altLang=None, + sz=None, + b=None, + i=None, + u=None, + strike=None, + kern=None, + cap=None, + spc=None, + normalizeH=None, + baseline=None, + noProof=None, + dirty=None, + err=None, + smtClean=None, + smtId=None, + bmk=None, + ln=None, + highlight=None, + latin=None, + ea=None, + cs=None, + sym=None, + hlinkClick=None, + hlinkMouseOver=None, + rtl=None, + extLst=None, + noFill=None, + solidFill=None, + gradFill=None, + blipFill=None, + pattFill=None, + grpFill=None, + effectLst=None, + effectDag=None, + uLnTx=None, + uLn=None, + uFillTx=None, + uFill=None, + ): + self.kumimoji = kumimoji + self.lang = lang + self.altLang = altLang + self.sz = sz + self.b = b + self.i = i + self.u = u + self.strike = strike + self.kern = kern + self.cap = cap + self.spc = spc + self.normalizeH = normalizeH + self.baseline = baseline + self.noProof = noProof + self.dirty = dirty + self.err = err + self.smtClean = smtClean + self.smtId = smtId + self.bmk = bmk + self.ln = ln + self.highlight = highlight + self.latin = latin + self.ea = ea + self.cs = cs + self.sym = sym + self.hlinkClick = hlinkClick + self.hlinkMouseOver = hlinkMouseOver + self.rtl = rtl + self.noFill = noFill + self.solidFill = solidFill + self.gradFill = gradFill + self.blipFill = blipFill + self.pattFill = pattFill + self.grpFill = grpFill + self.effectLst = effectLst + self.effectDag = effectDag + self.uLnTx = uLnTx + self.uLn = uLn + self.uFillTx = uFillTx + self.uFill = uFill + + +class TabStop(Serialisable): + + pos = Typed(expected_type=Coordinate, allow_none=True) + algn = Typed(expected_type=Set(values=(['l', 'ctr', 'r', 'dec']))) + + def __init__(self, + pos=None, + algn=None, + ): + self.pos = pos + self.algn = algn + + +class TabStopList(Serialisable): + + tab = Typed(expected_type=TabStop, allow_none=True) + + def __init__(self, + tab=None, + ): + self.tab = tab + + +class Spacing(Serialisable): + + spcPct = NestedInteger(allow_none=True) + spcPts = NestedInteger(allow_none=True) + + __elements__ = ('spcPct', 'spcPts') + + def __init__(self, + spcPct=None, + spcPts=None, + ): + self.spcPct = spcPct + self.spcPts = spcPts + + +class AutonumberBullet(Serialisable): + + type = Set(values=(['alphaLcParenBoth', 'alphaUcParenBoth', + 'alphaLcParenR', 'alphaUcParenR', 'alphaLcPeriod', 'alphaUcPeriod', + 'arabicParenBoth', 'arabicParenR', 'arabicPeriod', 'arabicPlain', + 'romanLcParenBoth', 'romanUcParenBoth', 'romanLcParenR', 'romanUcParenR', + 'romanLcPeriod', 'romanUcPeriod', 'circleNumDbPlain', + 'circleNumWdBlackPlain', 'circleNumWdWhitePlain', 'arabicDbPeriod', + 'arabicDbPlain', 'ea1ChsPeriod', 'ea1ChsPlain', 'ea1ChtPeriod', + 'ea1ChtPlain', 'ea1JpnChsDbPeriod', 'ea1JpnKorPlain', 'ea1JpnKorPeriod', + 'arabic1Minus', 'arabic2Minus', 'hebrew2Minus', 'thaiAlphaPeriod', + 'thaiAlphaParenR', 'thaiAlphaParenBoth', 'thaiNumPeriod', + 'thaiNumParenR', 'thaiNumParenBoth', 'hindiAlphaPeriod', + 'hindiNumPeriod', 'hindiNumParenR', 'hindiAlpha1Period'])) + startAt = Integer() + + def __init__(self, + type=None, + startAt=None, + ): + self.type = type + self.startAt = startAt + + +class ParagraphProperties(Serialisable): + + tagname = "pPr" + namespace = DRAWING_NS + + marL = Integer(allow_none=True) + marR = Integer(allow_none=True) + lvl = Integer(allow_none=True) + indent = Integer(allow_none=True) + algn = NoneSet(values=(['l', 'ctr', 'r', 'just', 'justLow', 'dist', 'thaiDist'])) + defTabSz = Integer(allow_none=True) + rtl = Bool(allow_none=True) + eaLnBrk = Bool(allow_none=True) + fontAlgn = NoneSet(values=(['auto', 't', 'ctr', 'base', 'b'])) + latinLnBrk = Bool(allow_none=True) + hangingPunct = Bool(allow_none=True) + + # uses element group EG_TextBulletColor + # uses element group EG_TextBulletSize + # uses element group EG_TextBulletTypeface + # uses element group EG_TextBullet + lnSpc = Typed(expected_type=Spacing, allow_none=True) + spcBef = Typed(expected_type=Spacing, allow_none=True) + spcAft = Typed(expected_type=Spacing, allow_none=True) + tabLst = Typed(expected_type=TabStopList, allow_none=True) + defRPr = Typed(expected_type=CharacterProperties, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + buClrTx = EmptyTag() + buClr = Typed(expected_type=Color, allow_none=True) + buSzTx = EmptyTag() + buSzPct = NestedInteger(allow_none=True) + buSzPts = NestedInteger(allow_none=True) + buFontTx = EmptyTag() + buFont = Typed(expected_type=Font, allow_none=True) + buNone = EmptyTag() + buAutoNum = EmptyTag() + buChar = NestedValue(expected_type=str, attribute="char", allow_none=True) + buBlip = NestedValue(expected_type=Blip, attribute="blip", allow_none=True) + + __elements__ = ('lnSpc', 'spcBef', 'spcAft', 'tabLst', 'defRPr', + 'buClrTx', 'buClr', 'buSzTx', 'buSzPct', 'buSzPts', 'buFontTx', 'buFont', + 'buNone', 'buAutoNum', 'buChar', 'buBlip') + + def __init__(self, + marL=None, + marR=None, + lvl=None, + indent=None, + algn=None, + defTabSz=None, + rtl=None, + eaLnBrk=None, + fontAlgn=None, + latinLnBrk=None, + hangingPunct=None, + lnSpc=None, + spcBef=None, + spcAft=None, + tabLst=None, + defRPr=None, + extLst=None, + buClrTx=None, + buClr=None, + buSzTx=None, + buSzPct=None, + buSzPts=None, + buFontTx=None, + buFont=None, + buNone=None, + buAutoNum=None, + buChar=None, + buBlip=None, + ): + self.marL = marL + self.marR = marR + self.lvl = lvl + self.indent = indent + self.algn = algn + self.defTabSz = defTabSz + self.rtl = rtl + self.eaLnBrk = eaLnBrk + self.fontAlgn = fontAlgn + self.latinLnBrk = latinLnBrk + self.hangingPunct = hangingPunct + self.lnSpc = lnSpc + self.spcBef = spcBef + self.spcAft = spcAft + self.tabLst = tabLst + self.defRPr = defRPr + self.buClrTx = buClrTx + self.buClr = buClr + self.buSzTx = buSzTx + self.buSzPct = buSzPct + self.buSzPts = buSzPts + self.buFontTx = buFontTx + self.buFont = buFont + self.buNone = buNone + self.buAutoNum = buAutoNum + self.buChar = buChar + self.buBlip = buBlip + self.defRPr = defRPr + + +class ListStyle(Serialisable): + + tagname = "lstStyle" + namespace = DRAWING_NS + + defPPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl1pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl2pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl3pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl4pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl5pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl6pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl7pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl8pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl9pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ("defPPr", "lvl1pPr", "lvl2pPr", "lvl3pPr", "lvl4pPr", + "lvl5pPr", "lvl6pPr", "lvl7pPr", "lvl8pPr", "lvl9pPr") + + def __init__(self, + defPPr=None, + lvl1pPr=None, + lvl2pPr=None, + lvl3pPr=None, + lvl4pPr=None, + lvl5pPr=None, + lvl6pPr=None, + lvl7pPr=None, + lvl8pPr=None, + lvl9pPr=None, + extLst=None, + ): + self.defPPr = defPPr + self.lvl1pPr = lvl1pPr + self.lvl2pPr = lvl2pPr + self.lvl3pPr = lvl3pPr + self.lvl4pPr = lvl4pPr + self.lvl5pPr = lvl5pPr + self.lvl6pPr = lvl6pPr + self.lvl7pPr = lvl7pPr + self.lvl8pPr = lvl8pPr + self.lvl9pPr = lvl9pPr + + +class RegularTextRun(Serialisable): + + tagname = "r" + namespace = DRAWING_NS + + rPr = Typed(expected_type=CharacterProperties, allow_none=True) + properties = Alias("rPr") + t = NestedText(expected_type=str) + value = Alias("t") + + __elements__ = ('rPr', 't') + + def __init__(self, + rPr=None, + t="", + ): + self.rPr = rPr + self.t = t + + +class LineBreak(Serialisable): + + tagname = "br" + namespace = DRAWING_NS + + rPr = Typed(expected_type=CharacterProperties, allow_none=True) + + __elements__ = ('rPr',) + + def __init__(self, + rPr=None, + ): + self.rPr = rPr + + +class TextField(Serialisable): + + id = String() + type = String(allow_none=True) + rPr = Typed(expected_type=CharacterProperties, allow_none=True) + pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + t = String(allow_none=True) + + __elements__ = ('rPr', 'pPr') + + def __init__(self, + id=None, + type=None, + rPr=None, + pPr=None, + t=None, + ): + self.id = id + self.type = type + self.rPr = rPr + self.pPr = pPr + self.t = t + + +class Paragraph(Serialisable): + + tagname = "p" + namespace = DRAWING_NS + + # uses element group EG_TextRun + pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + properties = Alias("pPr") + endParaRPr = Typed(expected_type=CharacterProperties, allow_none=True) + r = Sequence(expected_type=RegularTextRun) + text = Alias('r') + br = Typed(expected_type=LineBreak, allow_none=True) + fld = Typed(expected_type=TextField, allow_none=True) + + __elements__ = ('pPr', 'r', 'br', 'fld', 'endParaRPr') + + def __init__(self, + pPr=None, + endParaRPr=None, + r=None, + br=None, + fld=None, + ): + self.pPr = pPr + self.endParaRPr = endParaRPr + if r is None: + r = [RegularTextRun()] + self.r = r + self.br = br + self.fld = fld + + +class GeomGuide(Serialisable): + + name = String(()) + fmla = String(()) + + def __init__(self, + name=None, + fmla=None, + ): + self.name = name + self.fmla = fmla + + +class GeomGuideList(Serialisable): + + gd = Sequence(expected_type=GeomGuide, allow_none=True) + + def __init__(self, + gd=None, + ): + self.gd = gd + + +class PresetTextShape(Serialisable): + + prst = Typed(expected_type=Set(values=( + ['textNoShape', 'textPlain','textStop', 'textTriangle', 'textTriangleInverted', 'textChevron', + 'textChevronInverted', 'textRingInside', 'textRingOutside', 'textArchUp', + 'textArchDown', 'textCircle', 'textButton', 'textArchUpPour', + 'textArchDownPour', 'textCirclePour', 'textButtonPour', 'textCurveUp', + 'textCurveDown', 'textCanUp', 'textCanDown', 'textWave1', 'textWave2', + 'textDoubleWave1', 'textWave4', 'textInflate', 'textDeflate', + 'textInflateBottom', 'textDeflateBottom', 'textInflateTop', + 'textDeflateTop', 'textDeflateInflate', 'textDeflateInflateDeflate', + 'textFadeRight', 'textFadeLeft', 'textFadeUp', 'textFadeDown', + 'textSlantUp', 'textSlantDown', 'textCascadeUp', 'textCascadeDown' + ] + ))) + avLst = Typed(expected_type=GeomGuideList, allow_none=True) + + def __init__(self, + prst=None, + avLst=None, + ): + self.prst = prst + self.avLst = avLst + + +class TextNormalAutofit(Serialisable): + + fontScale = Integer() + lnSpcReduction = Integer() + + def __init__(self, + fontScale=None, + lnSpcReduction=None, + ): + self.fontScale = fontScale + self.lnSpcReduction = lnSpcReduction + + +class RichTextProperties(Serialisable): + + tagname = "bodyPr" + namespace = DRAWING_NS + + rot = Integer(allow_none=True) + spcFirstLastPara = Bool(allow_none=True) + vertOverflow = NoneSet(values=(['overflow', 'ellipsis', 'clip'])) + horzOverflow = NoneSet(values=(['overflow', 'clip'])) + vert = NoneSet(values=(['horz', 'vert', 'vert270', 'wordArtVert', + 'eaVert', 'mongolianVert', 'wordArtVertRtl'])) + wrap = NoneSet(values=(['none', 'square'])) + lIns = Integer(allow_none=True) + tIns = Integer(allow_none=True) + rIns = Integer(allow_none=True) + bIns = Integer(allow_none=True) + numCol = Integer(allow_none=True) + spcCol = Integer(allow_none=True) + rtlCol = Bool(allow_none=True) + fromWordArt = Bool(allow_none=True) + anchor = NoneSet(values=(['t', 'ctr', 'b', 'just', 'dist'])) + anchorCtr = Bool(allow_none=True) + forceAA = Bool(allow_none=True) + upright = Bool(allow_none=True) + compatLnSpc = Bool(allow_none=True) + prstTxWarp = Typed(expected_type=PresetTextShape, allow_none=True) + scene3d = Typed(expected_type=Scene3D, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + noAutofit = EmptyTag() + normAutofit = EmptyTag() + spAutoFit = EmptyTag() + flatTx = NestedInteger(attribute="z", allow_none=True) + + __elements__ = ('prstTxWarp', 'scene3d', 'noAutofit', 'normAutofit', 'spAutoFit') + + def __init__(self, + rot=None, + spcFirstLastPara=None, + vertOverflow=None, + horzOverflow=None, + vert=None, + wrap=None, + lIns=None, + tIns=None, + rIns=None, + bIns=None, + numCol=None, + spcCol=None, + rtlCol=None, + fromWordArt=None, + anchor=None, + anchorCtr=None, + forceAA=None, + upright=None, + compatLnSpc=None, + prstTxWarp=None, + scene3d=None, + extLst=None, + noAutofit=None, + normAutofit=None, + spAutoFit=None, + flatTx=None, + ): + self.rot = rot + self.spcFirstLastPara = spcFirstLastPara + self.vertOverflow = vertOverflow + self.horzOverflow = horzOverflow + self.vert = vert + self.wrap = wrap + self.lIns = lIns + self.tIns = tIns + self.rIns = rIns + self.bIns = bIns + self.numCol = numCol + self.spcCol = spcCol + self.rtlCol = rtlCol + self.fromWordArt = fromWordArt + self.anchor = anchor + self.anchorCtr = anchorCtr + self.forceAA = forceAA + self.upright = upright + self.compatLnSpc = compatLnSpc + self.prstTxWarp = prstTxWarp + self.scene3d = scene3d + self.noAutofit = noAutofit + self.normAutofit = normAutofit + self.spAutoFit = spAutoFit + self.flatTx = flatTx diff --git a/.venv/lib/python3.12/site-packages/openpyxl/drawing/xdr.py b/.venv/lib/python3.12/site-packages/openpyxl/drawing/xdr.py new file mode 100644 index 00000000..335480ce --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/drawing/xdr.py @@ -0,0 +1,33 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Spreadsheet Drawing has some copies of Drawing ML elements +""" + +from .geometry import Point2D, PositiveSize2D, Transform2D + + +class XDRPoint2D(Point2D): + + namespace = None + x = Point2D.x + y = Point2D.y + + +class XDRPositiveSize2D(PositiveSize2D): + + namespace = None + cx = PositiveSize2D.cx + cy = PositiveSize2D.cy + + +class XDRTransform2D(Transform2D): + + namespace = None + rot = Transform2D.rot + flipH = Transform2D.flipH + flipV = Transform2D.flipV + off = Transform2D.off + ext = Transform2D.ext + chOff = Transform2D.chOff + chExt = Transform2D.chExt diff --git a/.venv/lib/python3.12/site-packages/openpyxl/formatting/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/formatting/__init__.py new file mode 100644 index 00000000..bedc2bc4 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/formatting/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2010-2024 openpyxl + +from .rule import Rule diff --git a/.venv/lib/python3.12/site-packages/openpyxl/formatting/formatting.py b/.venv/lib/python3.12/site-packages/openpyxl/formatting/formatting.py new file mode 100644 index 00000000..bf622bf9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/formatting/formatting.py @@ -0,0 +1,114 @@ +# Copyright (c) 2010-2024 openpyxl + +from collections import OrderedDict + +from openpyxl.descriptors import ( + Bool, + Sequence, + Alias, + Convertible, +) +from openpyxl.descriptors.serialisable import Serialisable + +from .rule import Rule + +from openpyxl.worksheet.cell_range import MultiCellRange + +class ConditionalFormatting(Serialisable): + + tagname = "conditionalFormatting" + + sqref = Convertible(expected_type=MultiCellRange) + cells = Alias("sqref") + pivot = Bool(allow_none=True) + cfRule = Sequence(expected_type=Rule) + rules = Alias("cfRule") + + + def __init__(self, sqref=(), pivot=None, cfRule=(), extLst=None): + self.sqref = sqref + self.pivot = pivot + self.cfRule = cfRule + + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return self.sqref == other.sqref + + + def __hash__(self): + return hash(self.sqref) + + + def __repr__(self): + return "<{cls} {cells}>".format(cls=self.__class__.__name__, cells=self.sqref) + + + def __contains__(self, coord): + """ + Check whether a certain cell is affected by the formatting + """ + return coord in self.sqref + + +class ConditionalFormattingList: + """Conditional formatting rules.""" + + + def __init__(self): + self._cf_rules = OrderedDict() + self.max_priority = 0 + + + def add(self, range_string, cfRule): + """Add a rule such as ColorScaleRule, FormulaRule or CellIsRule + + The priority will be added automatically. + """ + cf = range_string + if isinstance(range_string, str): + cf = ConditionalFormatting(range_string) + if not isinstance(cfRule, Rule): + raise ValueError("Only instances of openpyxl.formatting.rule.Rule may be added") + rule = cfRule + self.max_priority += 1 + if not rule.priority: + rule.priority = self.max_priority + + self._cf_rules.setdefault(cf, []).append(rule) + + + def __bool__(self): + return bool(self._cf_rules) + + + def __len__(self): + return len(self._cf_rules) + + + def __iter__(self): + for cf, rules in self._cf_rules.items(): + cf.rules = rules + yield cf + + + def __getitem__(self, key): + """ + Get the rules for a cell range + """ + if isinstance(key, str): + key = ConditionalFormatting(sqref=key) + return self._cf_rules[key] + + + def __delitem__(self, key): + key = ConditionalFormatting(sqref=key) + del self._cf_rules[key] + + + def __setitem__(self, key, rule): + """ + Add a rule for a cell range + """ + self.add(key, rule) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/formatting/rule.py b/.venv/lib/python3.12/site-packages/openpyxl/formatting/rule.py new file mode 100644 index 00000000..c4ba7f8f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/formatting/rule.py @@ -0,0 +1,291 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Sequence, + Bool, + NoneSet, + Set, + Integer, + Float, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.styles.colors import Color, ColorDescriptor +from openpyxl.styles.differential import DifferentialStyle + +from openpyxl.utils.cell import COORD_RE + + +class ValueDescriptor(Float): + """ + Expected type depends upon type attribute of parent :-( + + Most values should be numeric BUT they can also be cell references + """ + + def __set__(self, instance, value): + ref = None + if value is not None and isinstance(value, str): + ref = COORD_RE.match(value) + if instance.type == "formula" or ref: + self.expected_type = str + else: + self.expected_type = float + super().__set__(instance, value) + + +class FormatObject(Serialisable): + + tagname = "cfvo" + + type = Set(values=(['num', 'percent', 'max', 'min', 'formula', 'percentile'])) + val = ValueDescriptor(allow_none=True) + gte = Bool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + type, + val=None, + gte=None, + extLst=None, + ): + self.type = type + self.val = val + self.gte = gte + + +class RuleType(Serialisable): + + cfvo = Sequence(expected_type=FormatObject) + + +class IconSet(RuleType): + + tagname = "iconSet" + + iconSet = NoneSet(values=(['3Arrows', '3ArrowsGray', '3Flags', + '3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2', + '4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights', + '5Arrows', '5ArrowsGray', '5Rating', '5Quarters'])) + showValue = Bool(allow_none=True) + percent = Bool(allow_none=True) + reverse = Bool(allow_none=True) + + __elements__ = ("cfvo",) + + def __init__(self, + iconSet=None, + showValue=None, + percent=None, + reverse=None, + cfvo=None, + ): + self.iconSet = iconSet + self.showValue = showValue + self.percent = percent + self.reverse = reverse + self.cfvo = cfvo + + +class DataBar(RuleType): + + tagname = "dataBar" + + minLength = Integer(allow_none=True) + maxLength = Integer(allow_none=True) + showValue = Bool(allow_none=True) + color = ColorDescriptor() + + __elements__ = ('cfvo', 'color') + + def __init__(self, + minLength=None, + maxLength=None, + showValue=None, + cfvo=None, + color=None, + ): + self.minLength = minLength + self.maxLength = maxLength + self.showValue = showValue + self.cfvo = cfvo + self.color = color + + +class ColorScale(RuleType): + + tagname = "colorScale" + + color = Sequence(expected_type=Color) + + __elements__ = ('cfvo', 'color') + + def __init__(self, + cfvo=None, + color=None, + ): + self.cfvo = cfvo + self.color = color + + +class Rule(Serialisable): + + tagname = "cfRule" + + type = Set(values=(['expression', 'cellIs', 'colorScale', 'dataBar', + 'iconSet', 'top10', 'uniqueValues', 'duplicateValues', 'containsText', + 'notContainsText', 'beginsWith', 'endsWith', 'containsBlanks', + 'notContainsBlanks', 'containsErrors', 'notContainsErrors', 'timePeriod', + 'aboveAverage'])) + dxfId = Integer(allow_none=True) + priority = Integer() + stopIfTrue = Bool(allow_none=True) + aboveAverage = Bool(allow_none=True) + percent = Bool(allow_none=True) + bottom = Bool(allow_none=True) + operator = NoneSet(values=(['lessThan', 'lessThanOrEqual', 'equal', + 'notEqual', 'greaterThanOrEqual', 'greaterThan', 'between', 'notBetween', + 'containsText', 'notContains', 'beginsWith', 'endsWith'])) + text = String(allow_none=True) + timePeriod = NoneSet(values=(['today', 'yesterday', 'tomorrow', 'last7Days', + 'thisMonth', 'lastMonth', 'nextMonth', 'thisWeek', 'lastWeek', + 'nextWeek'])) + rank = Integer(allow_none=True) + stdDev = Integer(allow_none=True) + equalAverage = Bool(allow_none=True) + formula = Sequence(expected_type=str) + colorScale = Typed(expected_type=ColorScale, allow_none=True) + dataBar = Typed(expected_type=DataBar, allow_none=True) + iconSet = Typed(expected_type=IconSet, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + dxf = Typed(expected_type=DifferentialStyle, allow_none=True) + + __elements__ = ('colorScale', 'dataBar', 'iconSet', 'formula') + __attrs__ = ('type', 'rank', 'priority', 'equalAverage', 'operator', + 'aboveAverage', 'dxfId', 'stdDev', 'stopIfTrue', 'timePeriod', 'text', + 'percent', 'bottom') + + + def __init__(self, + type, + dxfId=None, + priority=0, + stopIfTrue=None, + aboveAverage=None, + percent=None, + bottom=None, + operator=None, + text=None, + timePeriod=None, + rank=None, + stdDev=None, + equalAverage=None, + formula=(), + colorScale=None, + dataBar=None, + iconSet=None, + extLst=None, + dxf=None, + ): + self.type = type + self.dxfId = dxfId + self.priority = priority + self.stopIfTrue = stopIfTrue + self.aboveAverage = aboveAverage + self.percent = percent + self.bottom = bottom + self.operator = operator + self.text = text + self.timePeriod = timePeriod + self.rank = rank + self.stdDev = stdDev + self.equalAverage = equalAverage + self.formula = formula + self.colorScale = colorScale + self.dataBar = dataBar + self.iconSet = iconSet + self.dxf = dxf + + +def ColorScaleRule(start_type=None, + start_value=None, + start_color=None, + mid_type=None, + mid_value=None, + mid_color=None, + end_type=None, + end_value=None, + end_color=None): + + """Backwards compatibility""" + formats = [] + if start_type is not None: + formats.append(FormatObject(type=start_type, val=start_value)) + if mid_type is not None: + formats.append(FormatObject(type=mid_type, val=mid_value)) + if end_type is not None: + formats.append(FormatObject(type=end_type, val=end_value)) + colors = [] + for v in (start_color, mid_color, end_color): + if v is not None: + if not isinstance(v, Color): + v = Color(v) + colors.append(v) + cs = ColorScale(cfvo=formats, color=colors) + rule = Rule(type="colorScale", colorScale=cs) + return rule + + +def FormulaRule(formula=None, stopIfTrue=None, font=None, border=None, + fill=None): + """ + Conditional formatting with custom differential style + """ + rule = Rule(type="expression", formula=formula, stopIfTrue=stopIfTrue) + rule.dxf = DifferentialStyle(font=font, border=border, fill=fill) + return rule + + +def CellIsRule(operator=None, formula=None, stopIfTrue=None, font=None, border=None, fill=None): + """ + Conditional formatting rule based on cell contents. + """ + # Excel doesn't use >, >=, etc, but allow for ease of python development + expand = {">": "greaterThan", ">=": "greaterThanOrEqual", "<": "lessThan", "<=": "lessThanOrEqual", + "=": "equal", "==": "equal", "!=": "notEqual"} + + operator = expand.get(operator, operator) + + rule = Rule(type='cellIs', operator=operator, formula=formula, stopIfTrue=stopIfTrue) + rule.dxf = DifferentialStyle(font=font, border=border, fill=fill) + + return rule + + +def IconSetRule(icon_style=None, type=None, values=None, showValue=None, percent=None, reverse=None): + """ + Convenience function for creating icon set rules + """ + cfvo = [] + for val in values: + cfvo.append(FormatObject(type, val)) + icon_set = IconSet(iconSet=icon_style, cfvo=cfvo, showValue=showValue, + percent=percent, reverse=reverse) + rule = Rule(type='iconSet', iconSet=icon_set) + + return rule + + +def DataBarRule(start_type=None, start_value=None, end_type=None, + end_value=None, color=None, showValue=None, minLength=None, maxLength=None): + start = FormatObject(start_type, start_value) + end = FormatObject(end_type, end_value) + data_bar = DataBar(cfvo=[start, end], color=color, showValue=showValue, + minLength=minLength, maxLength=maxLength) + rule = Rule(type='dataBar', dataBar=data_bar) + + return rule diff --git a/.venv/lib/python3.12/site-packages/openpyxl/formula/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/formula/__init__.py new file mode 100644 index 00000000..a98a0c4a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/formula/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2010-2024 openpyxl + +from .tokenizer import Tokenizer diff --git a/.venv/lib/python3.12/site-packages/openpyxl/formula/tokenizer.py b/.venv/lib/python3.12/site-packages/openpyxl/formula/tokenizer.py new file mode 100644 index 00000000..9bf26240 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/formula/tokenizer.py @@ -0,0 +1,446 @@ +""" +This module contains a tokenizer for Excel formulae. + +The tokenizer is based on the Javascript tokenizer found at +http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html written by Eric +Bachtal +""" + +import re + + +class TokenizerError(Exception): + """Base class for all Tokenizer errors.""" + + +class Tokenizer: + + """ + A tokenizer for Excel worksheet formulae. + + Converts a str string representing an Excel formula (in A1 notation) + into a sequence of `Token` objects. + + `formula`: The str string to tokenize + + Tokenizer defines a method `._parse()` to parse the formula into tokens, + which can then be accessed through the `.items` attribute. + + """ + + SN_RE = re.compile("^[1-9](\\.[0-9]+)?[Ee]$") # Scientific notation + WSPACE_RE = re.compile(r"[ \n]+") + STRING_REGEXES = { + # Inside a string, all characters are treated as literals, except for + # the quote character used to start the string. That character, when + # doubled is treated as a single character in the string. If an + # unmatched quote appears, the string is terminated. + '"': re.compile('"(?:[^"]*"")*[^"]*"(?!")'), + "'": re.compile("'(?:[^']*'')*[^']*'(?!')"), + } + ERROR_CODES = ("#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", + "#NUM!", "#N/A", "#GETTING_DATA") + TOKEN_ENDERS = ',;}) +-*/^&=><%' # Each of these characters, marks the + # end of an operand token + + def __init__(self, formula): + self.formula = formula + self.items = [] + self.token_stack = [] # Used to keep track of arrays, functions, and + # parentheses + self.offset = 0 # How many chars have we read + self.token = [] # Used to build up token values char by char + self._parse() + + def _parse(self): + """Populate self.items with the tokens from the formula.""" + if self.offset: + return # Already parsed! + if not self.formula: + return + elif self.formula[0] == '=': + self.offset += 1 + else: + self.items.append(Token(self.formula, Token.LITERAL)) + return + consumers = ( + ('"\'', self._parse_string), + ('[', self._parse_brackets), + ('#', self._parse_error), + (' ', self._parse_whitespace), + ('\n', self._parse_whitespace), + ('+-*/^&=><%', self._parse_operator), + ('{(', self._parse_opener), + (')}', self._parse_closer), + (';,', self._parse_separator), + ) + dispatcher = {} # maps chars to the specific parsing function + for chars, consumer in consumers: + dispatcher.update(dict.fromkeys(chars, consumer)) + while self.offset < len(self.formula): + if self.check_scientific_notation(): # May consume one character + continue + curr_char = self.formula[self.offset] + if curr_char in self.TOKEN_ENDERS: + self.save_token() + if curr_char in dispatcher: + self.offset += dispatcher[curr_char]() + else: + # TODO: this can probably be sped up using a regex to get to + # the next interesting character + self.token.append(curr_char) + self.offset += 1 + self.save_token() + + def _parse_string(self): + """ + Parse a "-delimited string or '-delimited link. + + The offset must be pointing to either a single quote ("'") or double + quote ('"') character. The strings are parsed according to Excel + rules where to escape the delimiter you just double it up. E.g., + "abc""def" in Excel is parsed as 'abc"def' in Python. + + Returns the number of characters matched. (Does not update + self.offset) + + """ + self.assert_empty_token(can_follow=':') + delim = self.formula[self.offset] + assert delim in ('"', "'") + regex = self.STRING_REGEXES[delim] + match = regex.match(self.formula[self.offset:]) + if match is None: + subtype = "string" if delim == '"' else 'link' + raise TokenizerError(f"Reached end of formula while parsing {subtype} in {self.formula}") + match = match.group(0) + if delim == '"': + self.items.append(Token.make_operand(match)) + else: + self.token.append(match) + return len(match) + + def _parse_brackets(self): + """ + Consume all the text between square brackets []. + + Returns the number of characters matched. (Does not update + self.offset) + + """ + assert self.formula[self.offset] == '[' + lefts = [(t.start(), 1) for t in + re.finditer(r"\[", self.formula[self.offset:])] + rights = [(t.start(), -1) for t in + re.finditer(r"\]", self.formula[self.offset:])] + + open_count = 0 + for idx, open_close in sorted(lefts + rights): + open_count += open_close + if open_count == 0: + outer_right = idx + 1 + self.token.append( + self.formula[self.offset:self.offset + outer_right]) + return outer_right + + raise TokenizerError(f"Encountered unmatched '[' in {self.formula}") + + def _parse_error(self): + """ + Consume the text following a '#' as an error. + + Looks for a match in self.ERROR_CODES and returns the number of + characters matched. (Does not update self.offset) + + """ + self.assert_empty_token(can_follow='!') + assert self.formula[self.offset] == '#' + subformula = self.formula[self.offset:] + for err in self.ERROR_CODES: + if subformula.startswith(err): + self.items.append(Token.make_operand(''.join(self.token) + err)) + del self.token[:] + return len(err) + raise TokenizerError(f"Invalid error code at position {self.offset} in '{self.formula}'") + + def _parse_whitespace(self): + """ + Consume a string of consecutive spaces. + + Returns the number of spaces found. (Does not update self.offset). + + """ + assert self.formula[self.offset] in (' ', '\n') + self.items.append(Token(self.formula[self.offset], Token.WSPACE)) + return self.WSPACE_RE.match(self.formula[self.offset:]).end() + + def _parse_operator(self): + """ + Consume the characters constituting an operator. + + Returns the number of characters consumed. (Does not update + self.offset) + + """ + if self.formula[self.offset:self.offset + 2] in ('>=', '<=', '<>'): + self.items.append(Token( + self.formula[self.offset:self.offset + 2], + Token.OP_IN + )) + return 2 + curr_char = self.formula[self.offset] # guaranteed to be 1 char + assert curr_char in '%*/^&=><+-' + if curr_char == '%': + token = Token('%', Token.OP_POST) + elif curr_char in "*/^&=><": + token = Token(curr_char, Token.OP_IN) + # From here on, curr_char is guaranteed to be in '+-' + elif not self.items: + token = Token(curr_char, Token.OP_PRE) + else: + prev = next((i for i in reversed(self.items) + if i.type != Token.WSPACE), None) + is_infix = prev and ( + prev.subtype == Token.CLOSE + or prev.type == Token.OP_POST + or prev.type == Token.OPERAND + ) + if is_infix: + token = Token(curr_char, Token.OP_IN) + else: + token = Token(curr_char, Token.OP_PRE) + self.items.append(token) + return 1 + + def _parse_opener(self): + """ + Consumes a ( or { character. + + Returns the number of characters consumed. (Does not update + self.offset) + + """ + assert self.formula[self.offset] in ('(', '{') + if self.formula[self.offset] == '{': + self.assert_empty_token() + token = Token.make_subexp("{") + elif self.token: + token_value = "".join(self.token) + '(' + del self.token[:] + token = Token.make_subexp(token_value) + else: + token = Token.make_subexp("(") + self.items.append(token) + self.token_stack.append(token) + return 1 + + def _parse_closer(self): + """ + Consumes a } or ) character. + + Returns the number of characters consumed. (Does not update + self.offset) + + """ + assert self.formula[self.offset] in (')', '}') + token = self.token_stack.pop().get_closer() + if token.value != self.formula[self.offset]: + raise TokenizerError( + "Mismatched ( and { pair in '%s'" % self.formula) + self.items.append(token) + return 1 + + def _parse_separator(self): + """ + Consumes a ; or , character. + + Returns the number of characters consumed. (Does not update + self.offset) + + """ + curr_char = self.formula[self.offset] + assert curr_char in (';', ',') + if curr_char == ';': + token = Token.make_separator(";") + else: + try: + top_type = self.token_stack[-1].type + except IndexError: + token = Token(",", Token.OP_IN) # Range Union operator + else: + if top_type == Token.PAREN: + token = Token(",", Token.OP_IN) # Range Union operator + else: + token = Token.make_separator(",") + self.items.append(token) + return 1 + + def check_scientific_notation(self): + """ + Consumes a + or - character if part of a number in sci. notation. + + Returns True if the character was consumed and self.offset was + updated, False otherwise. + + """ + curr_char = self.formula[self.offset] + if (curr_char in '+-' + and len(self.token) >= 1 + and self.SN_RE.match("".join(self.token))): + self.token.append(curr_char) + self.offset += 1 + return True + return False + + def assert_empty_token(self, can_follow=()): + """ + Ensure that there's no token currently being parsed. + + Or if there is a token being parsed, it must end with a character in + can_follow. + + If there are unconsumed token contents, it means we hit an unexpected + token transition. In this case, we raise a TokenizerError + + """ + if self.token and self.token[-1] not in can_follow: + raise TokenizerError(f"Unexpected character at position {self.offset} in '{self.formula}'") + + def save_token(self): + """If there's a token being parsed, add it to the item list.""" + if self.token: + self.items.append(Token.make_operand("".join(self.token))) + del self.token[:] + + def render(self): + """Convert the parsed tokens back to a string.""" + if not self.items: + return "" + elif self.items[0].type == Token.LITERAL: + return self.items[0].value + return "=" + "".join(token.value for token in self.items) + + +class Token: + + """ + A token in an Excel formula. + + Tokens have three attributes: + + * `value`: The string value parsed that led to this token + * `type`: A string identifying the type of token + * `subtype`: A string identifying subtype of the token (optional, and + defaults to "") + + """ + + __slots__ = ['value', 'type', 'subtype'] + + LITERAL = "LITERAL" + OPERAND = "OPERAND" + FUNC = "FUNC" + ARRAY = "ARRAY" + PAREN = "PAREN" + SEP = "SEP" + OP_PRE = "OPERATOR-PREFIX" + OP_IN = "OPERATOR-INFIX" + OP_POST = "OPERATOR-POSTFIX" + WSPACE = "WHITE-SPACE" + + def __init__(self, value, type_, subtype=""): + self.value = value + self.type = type_ + self.subtype = subtype + + # Literal operands: + # + # Literal operands are always of type 'OPERAND' and can be of subtype + # 'TEXT' (for text strings), 'NUMBER' (for all numeric types), 'LOGICAL' + # (for TRUE and FALSE), 'ERROR' (for literal error values), or 'RANGE' + # (for all range references). + + TEXT = 'TEXT' + NUMBER = 'NUMBER' + LOGICAL = 'LOGICAL' + ERROR = 'ERROR' + RANGE = 'RANGE' + + def __repr__(self): + return u"{0} {1} {2}:".format(self.type, self.subtype, self.value) + + @classmethod + def make_operand(cls, value): + """Create an operand token.""" + if value.startswith('"'): + subtype = cls.TEXT + elif value.startswith('#'): + subtype = cls.ERROR + elif value in ('TRUE', 'FALSE'): + subtype = cls.LOGICAL + else: + try: + float(value) + subtype = cls.NUMBER + except ValueError: + subtype = cls.RANGE + return cls(value, cls.OPERAND, subtype) + + + # Subexpresssions + # + # There are 3 types of `Subexpressions`: functions, array literals, and + # parentheticals. Subexpressions have 'OPEN' and 'CLOSE' tokens. 'OPEN' + # is used when parsing the initial expression token (i.e., '(' or '{') + # and 'CLOSE' is used when parsing the closing expression token ('}' or + # ')'). + + OPEN = "OPEN" + CLOSE = "CLOSE" + + @classmethod + def make_subexp(cls, value, func=False): + """ + Create a subexpression token. + + `value`: The value of the token + `func`: If True, force the token to be of type FUNC + + """ + assert value[-1] in ('{', '}', '(', ')') + if func: + assert re.match('.+\\(|\\)', value) + type_ = Token.FUNC + elif value in '{}': + type_ = Token.ARRAY + elif value in '()': + type_ = Token.PAREN + else: + type_ = Token.FUNC + subtype = cls.CLOSE if value in ')}' else cls.OPEN + return cls(value, type_, subtype) + + def get_closer(self): + """Return a closing token that matches this token's type.""" + assert self.type in (self.FUNC, self.ARRAY, self.PAREN) + assert self.subtype == self.OPEN + value = "}" if self.type == self.ARRAY else ")" + return self.make_subexp(value, func=self.type == self.FUNC) + + # Separator tokens + # + # Argument separators always have type 'SEP' and can have one of two + # subtypes: 'ARG', 'ROW'. 'ARG' is used for the ',' token, when used to + # delimit either function arguments or array elements. 'ROW' is used for + # the ';' token, which is always used to delimit rows in an array + # literal. + + ARG = "ARG" + ROW = "ROW" + + @classmethod + def make_separator(cls, value): + """Create a separator token""" + assert value in (',', ';') + subtype = cls.ARG if value == ',' else cls.ROW + return cls(value, cls.SEP, subtype) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/formula/translate.py b/.venv/lib/python3.12/site-packages/openpyxl/formula/translate.py new file mode 100644 index 00000000..a7e90ec8 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/formula/translate.py @@ -0,0 +1,166 @@ +""" +This module contains code to translate formulae across cells in a worksheet. + +The idea is that if A1 has formula "=B1+C1", then translating it to cell A2 +results in formula "=B2+C2". The algorithm relies on the formula tokenizer +to identify the parts of the formula that need to change. + +""" + +import re +from .tokenizer import Tokenizer, Token +from openpyxl.utils import ( + coordinate_to_tuple, + column_index_from_string, + get_column_letter +) + +class TranslatorError(Exception): + """ + Raised when a formula can't be translated across cells. + + This error arises when a formula's references would be translated outside + the worksheet's bounds on the top or left. Excel represents these + situations with a #REF! literal error. E.g., if the formula at B2 is + '=A1', attempting to translate the formula to B1 raises TranslatorError, + since there's no cell above A1. Similarly, translating the same formula + from B2 to A2 raises TranslatorError, since there's no cell to the left of + A1. + + """ + + +class Translator: + + """ + Modifies a formula so that it can be translated from one cell to another. + + `formula`: The str string to translate. Must include the leading '=' + character. + `origin`: The cell address (in A1 notation) where this formula was + defined (excluding the worksheet name). + + """ + + def __init__(self, formula, origin): + # Excel errors out when a workbook has formulae in R1C1 notation, + # regardless of the calcPr:refMode setting, so I'm assuming the + # formulae stored in the workbook must be in A1 notation. + self.row, self.col = coordinate_to_tuple(origin) + self.tokenizer = Tokenizer(formula) + + def get_tokens(self): + "Returns a list with the tokens comprising the formula." + return self.tokenizer.items + + ROW_RANGE_RE = re.compile(r"(\$?[1-9][0-9]{0,6}):(\$?[1-9][0-9]{0,6})$") + COL_RANGE_RE = re.compile(r"(\$?[A-Za-z]{1,3}):(\$?[A-Za-z]{1,3})$") + CELL_REF_RE = re.compile(r"(\$?[A-Za-z]{1,3})(\$?[1-9][0-9]{0,6})$") + + @staticmethod + def translate_row(row_str, rdelta): + """ + Translate a range row-snippet by the given number of rows. + """ + if row_str.startswith('$'): + return row_str + else: + new_row = int(row_str) + rdelta + if new_row <= 0: + raise TranslatorError("Formula out of range") + return str(new_row) + + @staticmethod + def translate_col(col_str, cdelta): + """ + Translate a range col-snippet by the given number of columns + """ + if col_str.startswith('$'): + return col_str + else: + try: + return get_column_letter( + column_index_from_string(col_str) + cdelta) + except ValueError: + raise TranslatorError("Formula out of range") + + @staticmethod + def strip_ws_name(range_str): + "Splits out the worksheet reference, if any, from a range reference." + # This code assumes that named ranges cannot contain any exclamation + # marks. Excel refuses to create these (even using VBA), and + # complains of a corrupt workbook when there are names with + # exclamation marks. The ECMA spec only states that named ranges will + # be of `ST_Xstring` type, which in theory allows '!' (char code + # 0x21) per http://www.w3.org/TR/xml/#charsets + if '!' in range_str: + sheet, range_str = range_str.rsplit('!', 1) + return sheet + "!", range_str + return "", range_str + + @classmethod + def translate_range(cls, range_str, rdelta, cdelta): + """ + Translate an A1-style range reference to the destination cell. + + `rdelta`: the row offset to add to the range + `cdelta`: the column offset to add to the range + `range_str`: an A1-style reference to a range. Potentially includes + the worksheet reference. Could also be a named range. + + """ + ws_part, range_str = cls.strip_ws_name(range_str) + match = cls.ROW_RANGE_RE.match(range_str) # e.g. `3:4` + if match is not None: + return (ws_part + cls.translate_row(match.group(1), rdelta) + ":" + + cls.translate_row(match.group(2), rdelta)) + match = cls.COL_RANGE_RE.match(range_str) # e.g. `A:BC` + if match is not None: + return (ws_part + cls.translate_col(match.group(1), cdelta) + ':' + + cls.translate_col(match.group(2), cdelta)) + if ':' in range_str: # e.g. `A1:B5` + # The check is necessarily general because range references can + # have one or both endpoints specified by named ranges. I.e., + # `named_range:C2`, `C2:named_range`, and `name1:name2` are all + # valid references. Further, Excel allows chaining multiple + # colons together (with unclear meaning) + return ws_part + ":".join( + cls.translate_range(piece, rdelta, cdelta) + for piece in range_str.split(':')) + match = cls.CELL_REF_RE.match(range_str) + if match is None: # Must be a named range + return range_str + return (ws_part + cls.translate_col(match.group(1), cdelta) + + cls.translate_row(match.group(2), rdelta)) + + def translate_formula(self, dest=None, row_delta=0, col_delta=0): + """ + Convert the formula into A1 notation, or as row and column coordinates + + The formula is converted into A1 assuming it is assigned to the cell + whose address is `dest` (no worksheet name). + + """ + tokens = self.get_tokens() + if not tokens: + return "" + elif tokens[0].type == Token.LITERAL: + return tokens[0].value + out = ['='] + # per the spec: + # A compliant producer or consumer considers a defined name in the + # range A1-XFD1048576 to be an error. All other names outside this + # range can be defined as names and overrides a cell reference if an + # ambiguity exists. (I.18.2.5) + if dest: + row, col = coordinate_to_tuple(dest) + row_delta = row - self.row + col_delta = col - self.col + for token in tokens: + if (token.type == Token.OPERAND + and token.subtype == Token.RANGE): + out.append(self.translate_range(token.value, row_delta, + col_delta)) + else: + out.append(token.value) + return "".join(out) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py new file mode 100644 index 00000000..c3085ee5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py @@ -0,0 +1,3 @@ +""" +Stuff related to Office OpenXML packaging: relationships, archive, content types. +""" diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/core.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/core.py new file mode 100644 index 00000000..45153732 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/core.py @@ -0,0 +1,115 @@ +# Copyright (c) 2010-2024 openpyxl + +import datetime + +from openpyxl.descriptors import ( + DateTime, + Alias, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.nested import NestedText +from openpyxl.xml.functions import ( + Element, + QName, +) +from openpyxl.xml.constants import ( + COREPROPS_NS, + DCORE_NS, + XSI_NS, + DCTERMS_NS, +) + + +class NestedDateTime(DateTime, NestedText): + + expected_type = datetime.datetime + + def to_tree(self, tagname=None, value=None, namespace=None): + namespace = getattr(self, "namespace", namespace) + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + el = Element(tagname) + if value is not None: + value = value.replace(tzinfo=None) + el.text = value.isoformat(timespec="seconds") + 'Z' + return el + + +class QualifiedDateTime(NestedDateTime): + + """In certain situations Excel will complain if the additional type + attribute isn't set""" + + def to_tree(self, tagname=None, value=None, namespace=None): + el = super().to_tree(tagname, value, namespace) + el.set("{%s}type" % XSI_NS, QName(DCTERMS_NS, "W3CDTF")) + return el + + +class DocumentProperties(Serialisable): + """High-level properties of the document. + Defined in ECMA-376 Par2 Annex D + """ + + tagname = "coreProperties" + namespace = COREPROPS_NS + + category = NestedText(expected_type=str, allow_none=True) + contentStatus = NestedText(expected_type=str, allow_none=True) + keywords = NestedText(expected_type=str, allow_none=True) + lastModifiedBy = NestedText(expected_type=str, allow_none=True) + lastPrinted = NestedDateTime(allow_none=True) + revision = NestedText(expected_type=str, allow_none=True) + version = NestedText(expected_type=str, allow_none=True) + last_modified_by = Alias("lastModifiedBy") + + # Dublin Core Properties + subject = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + title = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + creator = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + description = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + identifier = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + language = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + # Dublin Core Terms + created = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS) # assumed to be UTC + modified = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS) # assumed to be UTC + + __elements__ = ("creator", "title", "description", "subject","identifier", + "language", "created", "modified", "lastModifiedBy", "category", + "contentStatus", "version", "revision", "keywords", "lastPrinted", + ) + + + def __init__(self, + category=None, + contentStatus=None, + keywords=None, + lastModifiedBy=None, + lastPrinted=None, + revision=None, + version=None, + created=None, + creator="openpyxl", + description=None, + identifier=None, + language=None, + modified=None, + subject=None, + title=None, + ): + now = datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None) + self.contentStatus = contentStatus + self.lastPrinted = lastPrinted + self.revision = revision + self.version = version + self.creator = creator + self.lastModifiedBy = lastModifiedBy + self.modified = modified or now + self.created = created or now + self.title = title + self.subject = subject + self.description = description + self.identifier = identifier + self.language = language + self.keywords = keywords + self.category = category diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py new file mode 100644 index 00000000..7e253d78 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py @@ -0,0 +1,289 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Implementation of custom properties see § 22.3 in the specification""" + + +from warnings import warn + +from openpyxl.descriptors import Strict +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.sequence import Sequence +from openpyxl.descriptors import ( + Alias, + String, + Integer, + Float, + DateTime, + Bool, +) +from openpyxl.descriptors.nested import ( + NestedText, +) + +from openpyxl.xml.constants import ( + CUSTPROPS_NS, + VTYPES_NS, + CPROPS_FMTID, +) + +from .core import NestedDateTime + + +class NestedBoolText(Bool, NestedText): + """ + Descriptor for handling nested elements with the value stored in the text part + """ + + pass + + +class _CustomDocumentProperty(Serialisable): + + """ + Low-level representation of a Custom Document Property. + Not used directly + Must always contain a child element, even if this is empty + """ + + tagname = "property" + _typ = None + + name = String(allow_none=True) + lpwstr = NestedText(expected_type=str, allow_none=True, namespace=VTYPES_NS) + i4 = NestedText(expected_type=int, allow_none=True, namespace=VTYPES_NS) + r8 = NestedText(expected_type=float, allow_none=True, namespace=VTYPES_NS) + filetime = NestedDateTime(allow_none=True, namespace=VTYPES_NS) + bool = NestedBoolText(expected_type=bool, allow_none=True, namespace=VTYPES_NS) + linkTarget = String(expected_type=str, allow_none=True) + fmtid = String() + pid = Integer() + + def __init__(self, + name=None, + pid=0, + fmtid=CPROPS_FMTID, + linkTarget=None, + **kw): + self.fmtid = fmtid + self.pid = pid + self.name = name + self._typ = None + self.linkTarget = linkTarget + + for k, v in kw.items(): + setattr(self, k, v) + setattr(self, "_typ", k) # ugh! + for e in self.__elements__: + if e not in kw: + setattr(self, e, None) + + + @property + def type(self): + if self._typ is not None: + return self._typ + for a in self.__elements__: + if getattr(self, a) is not None: + return a + if self.linkTarget is not None: + return "linkTarget" + + + def to_tree(self, tagname=None, idx=None, namespace=None): + child = getattr(self, self._typ, None) + if child is None: + setattr(self, self._typ, "") + + return super().to_tree(tagname=None, idx=None, namespace=None) + + +class _CustomDocumentPropertyList(Serialisable): + + """ + Parses and seriliases property lists but is not used directly + """ + + tagname = "Properties" + + property = Sequence(expected_type=_CustomDocumentProperty, namespace=CUSTPROPS_NS) + customProps = Alias("property") + + + def __init__(self, property=()): + self.property = property + + + def __len__(self): + return len(self.property) + + + def to_tree(self, tagname=None, idx=None, namespace=None): + for idx, p in enumerate(self.property, 2): + p.pid = idx + tree = super().to_tree(tagname, idx, namespace) + tree.set("xmlns", CUSTPROPS_NS) + + return tree + + +class _TypedProperty(Strict): + + name = String() + + def __init__(self, + name, + value): + self.name = name + self.value = value + + + def __eq__(self, other): + return self.name == other.name and self.value == other.value + + + def __repr__(self): + return f"{self.__class__.__name__}, name={self.name}, value={self.value}" + + +class IntProperty(_TypedProperty): + + value = Integer() + + +class FloatProperty(_TypedProperty): + + value = Float() + + +class StringProperty(_TypedProperty): + + value = String(allow_none=True) + + +class DateTimeProperty(_TypedProperty): + + value = DateTime() + + +class BoolProperty(_TypedProperty): + + value = Bool() + + +class LinkProperty(_TypedProperty): + + value = String() + + +# from Python +CLASS_MAPPING = { + StringProperty: "lpwstr", + IntProperty: "i4", + FloatProperty: "r8", + DateTimeProperty: "filetime", + BoolProperty: "bool", + LinkProperty: "linkTarget" +} + +XML_MAPPING = {v:k for k,v in CLASS_MAPPING.items()} + + +class CustomPropertyList(Strict): + + + props = Sequence(expected_type=_TypedProperty) + + def __init__(self): + self.props = [] + + + @classmethod + def from_tree(cls, tree): + """ + Create list from OOXML element + """ + prop_list = _CustomDocumentPropertyList.from_tree(tree) + props = [] + + for prop in prop_list.property: + attr = prop.type + + typ = XML_MAPPING.get(attr, None) + if not typ: + warn(f"Unknown type for {prop.name}") + continue + value = getattr(prop, attr) + link = prop.linkTarget + if link is not None: + typ = LinkProperty + value = prop.linkTarget + + new_prop = typ(name=prop.name, value=value) + props.append(new_prop) + + new_prop_list = cls() + new_prop_list.props = props + return new_prop_list + + + def append(self, prop): + if prop.name in self.names: + raise ValueError(f"Property with name {prop.name} already exists") + + self.props.append(prop) + + + def to_tree(self): + props = [] + + for p in self.props: + attr = CLASS_MAPPING.get(p.__class__, None) + if not attr: + raise TypeError("Unknown adapter for {p}") + np = _CustomDocumentProperty(name=p.name, **{attr:p.value}) + if isinstance(p, LinkProperty): + np._typ = "lpwstr" + #np.lpwstr = "" + props.append(np) + + prop_list = _CustomDocumentPropertyList(property=props) + return prop_list.to_tree() + + + def __len__(self): + return len(self.props) + + + @property + def names(self): + """List of property names""" + return [p.name for p in self.props] + + + def __getitem__(self, name): + """ + Get property by name + """ + for p in self.props: + if p.name == name: + return p + raise KeyError(f"Property with name {name} not found") + + + def __delitem__(self, name): + """ + Delete a propery by name + """ + for idx, p in enumerate(self.props): + if p.name == name: + self.props.pop(idx) + return + raise KeyError(f"Property with name {name} not found") + + + def __repr__(self): + return f"{self.__class__.__name__} containing {self.props}" + + + def __iter__(self): + return iter(self.props) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py new file mode 100644 index 00000000..fbd794af --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py @@ -0,0 +1,137 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, +) +from openpyxl.descriptors.nested import ( + NestedText, +) + +from openpyxl.xml.constants import XPROPS_NS +from openpyxl import __version__ + + +class DigSigBlob(Serialisable): + + __elements__ = __attrs__ = () + + +class VectorLpstr(Serialisable): + + __elements__ = __attrs__ = () + + +class VectorVariant(Serialisable): + + __elements__ = __attrs__ = () + + +class ExtendedProperties(Serialisable): + + """ + See 22.2 + + Most of this is irrelevant but Excel is very picky about the version number + + It uses XX.YYYY (Version.Build) and expects everyone else to + + We provide Major.Minor and the full version in the application name + """ + + tagname = "Properties" + + Template = NestedText(expected_type=str, allow_none=True) + Manager = NestedText(expected_type=str, allow_none=True) + Company = NestedText(expected_type=str, allow_none=True) + Pages = NestedText(expected_type=int, allow_none=True) + Words = NestedText(expected_type=int,allow_none=True) + Characters = NestedText(expected_type=int, allow_none=True) + PresentationFormat = NestedText(expected_type=str, allow_none=True) + Lines = NestedText(expected_type=int, allow_none=True) + Paragraphs = NestedText(expected_type=int, allow_none=True) + Slides = NestedText(expected_type=int, allow_none=True) + Notes = NestedText(expected_type=int, allow_none=True) + TotalTime = NestedText(expected_type=int, allow_none=True) + HiddenSlides = NestedText(expected_type=int, allow_none=True) + MMClips = NestedText(expected_type=int, allow_none=True) + ScaleCrop = NestedText(expected_type=bool, allow_none=True) + HeadingPairs = Typed(expected_type=VectorVariant, allow_none=True) + TitlesOfParts = Typed(expected_type=VectorLpstr, allow_none=True) + LinksUpToDate = NestedText(expected_type=bool, allow_none=True) + CharactersWithSpaces = NestedText(expected_type=int, allow_none=True) + SharedDoc = NestedText(expected_type=bool, allow_none=True) + HyperlinkBase = NestedText(expected_type=str, allow_none=True) + HLinks = Typed(expected_type=VectorVariant, allow_none=True) + HyperlinksChanged = NestedText(expected_type=bool, allow_none=True) + DigSig = Typed(expected_type=DigSigBlob, allow_none=True) + Application = NestedText(expected_type=str, allow_none=True) + AppVersion = NestedText(expected_type=str, allow_none=True) + DocSecurity = NestedText(expected_type=int, allow_none=True) + + __elements__ = ('Application', 'AppVersion', 'DocSecurity', 'ScaleCrop', + 'LinksUpToDate', 'SharedDoc', 'HyperlinksChanged') + + def __init__(self, + Template=None, + Manager=None, + Company=None, + Pages=None, + Words=None, + Characters=None, + PresentationFormat=None, + Lines=None, + Paragraphs=None, + Slides=None, + Notes=None, + TotalTime=None, + HiddenSlides=None, + MMClips=None, + ScaleCrop=None, + HeadingPairs=None, + TitlesOfParts=None, + LinksUpToDate=None, + CharactersWithSpaces=None, + SharedDoc=None, + HyperlinkBase=None, + HLinks=None, + HyperlinksChanged=None, + DigSig=None, + Application=None, + AppVersion=None, + DocSecurity=None, + ): + self.Template = Template + self.Manager = Manager + self.Company = Company + self.Pages = Pages + self.Words = Words + self.Characters = Characters + self.PresentationFormat = PresentationFormat + self.Lines = Lines + self.Paragraphs = Paragraphs + self.Slides = Slides + self.Notes = Notes + self.TotalTime = TotalTime + self.HiddenSlides = HiddenSlides + self.MMClips = MMClips + self.ScaleCrop = ScaleCrop + self.HeadingPairs = None + self.TitlesOfParts = None + self.LinksUpToDate = LinksUpToDate + self.CharactersWithSpaces = CharactersWithSpaces + self.SharedDoc = SharedDoc + self.HyperlinkBase = HyperlinkBase + self.HLinks = None + self.HyperlinksChanged = HyperlinksChanged + self.DigSig = None + self.Application = f"Microsoft Excel Compatible / Openpyxl {__version__}" + self.AppVersion = ".".join(__version__.split(".")[:-1]) + self.DocSecurity = DocSecurity + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", XPROPS_NS) + return tree diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py new file mode 100644 index 00000000..cacc0462 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py @@ -0,0 +1,56 @@ +# Copyright (c) 2010-2024 openpyxl + +from abc import abstractproperty +from openpyxl.compat.abc import ABC + + +class ISerialisableFile(ABC): + + """ + Interface for Serialisable classes that represent files in the archive + """ + + + @abstractproperty + def id(self): + """ + Object id making it unique + """ + pass + + + @abstractproperty + def _path(self): + """ + File path in the archive + """ + pass + + + @abstractproperty + def _namespace(self): + """ + Qualified namespace when serialised + """ + pass + + + @abstractproperty + def _type(self): + """ + The content type for the manifest + """ + + + @abstractproperty + def _rel_type(self): + """ + The content type for relationships + """ + + + @abstractproperty + def _rel_id(self): + """ + Links object with parent + """ diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py new file mode 100644 index 00000000..41da07f4 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py @@ -0,0 +1,194 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +File manifest +""" +from mimetypes import MimeTypes +import os.path + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import String, Sequence +from openpyxl.xml.functions import fromstring +from openpyxl.xml.constants import ( + ARC_CONTENT_TYPES, + ARC_THEME, + ARC_STYLE, + THEME_TYPE, + STYLES_TYPE, + CONTYPES_NS, + ACTIVEX, + CTRL, + VBA, +) +from openpyxl.xml.functions import tostring + +# initialise mime-types +mimetypes = MimeTypes() +mimetypes.add_type('application/xml', ".xml") +mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', ".rels") +mimetypes.add_type("application/vnd.ms-office.vbaProject", ".bin") +mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", ".vml") +mimetypes.add_type("image/x-emf", ".emf") + + +class FileExtension(Serialisable): + + tagname = "Default" + + Extension = String() + ContentType = String() + + def __init__(self, Extension, ContentType): + self.Extension = Extension + self.ContentType = ContentType + + +class Override(Serialisable): + + tagname = "Override" + + PartName = String() + ContentType = String() + + def __init__(self, PartName, ContentType): + self.PartName = PartName + self.ContentType = ContentType + + +DEFAULT_TYPES = [ + FileExtension("rels", "application/vnd.openxmlformats-package.relationships+xml"), + FileExtension("xml", "application/xml"), +] + +DEFAULT_OVERRIDE = [ + Override("/" + ARC_STYLE, STYLES_TYPE), # Styles + Override("/" + ARC_THEME, THEME_TYPE), # Theme + Override("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml"), + Override("/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml") +] + + +class Manifest(Serialisable): + + tagname = "Types" + + Default = Sequence(expected_type=FileExtension, unique=True) + Override = Sequence(expected_type=Override, unique=True) + path = "[Content_Types].xml" + + __elements__ = ("Default", "Override") + + def __init__(self, + Default=(), + Override=(), + ): + if not Default: + Default = DEFAULT_TYPES + self.Default = Default + if not Override: + Override = DEFAULT_OVERRIDE + self.Override = Override + + + @property + def filenames(self): + return [part.PartName for part in self.Override] + + + @property + def extensions(self): + """ + Map content types to file extensions + Skip parts without extensions + """ + exts = {os.path.splitext(part.PartName)[-1] for part in self.Override} + return [(ext[1:], mimetypes.types_map[True][ext]) for ext in sorted(exts) if ext] + + + def to_tree(self): + """ + Custom serialisation method to allow setting a default namespace + """ + defaults = [t.Extension for t in self.Default] + for ext, mime in self.extensions: + if ext not in defaults: + mime = FileExtension(ext, mime) + self.Default.append(mime) + tree = super().to_tree() + tree.set("xmlns", CONTYPES_NS) + return tree + + + def __contains__(self, content_type): + """ + Check whether a particular content type is contained + """ + for t in self.Override: + if t.ContentType == content_type: + return True + + + def find(self, content_type): + """ + Find specific content-type + """ + try: + return next(self.findall(content_type)) + except StopIteration: + return + + + def findall(self, content_type): + """ + Find all elements of a specific content-type + """ + for t in self.Override: + if t.ContentType == content_type: + yield t + + + def append(self, obj): + """ + Add content object to the package manifest + # needs a contract... + """ + ct = Override(PartName=obj.path, ContentType=obj.mime_type) + self.Override.append(ct) + + + def _write(self, archive, workbook): + """ + Write manifest to the archive + """ + self.append(workbook) + self._write_vba(workbook) + self._register_mimetypes(filenames=archive.namelist()) + archive.writestr(self.path, tostring(self.to_tree())) + + + def _register_mimetypes(self, filenames): + """ + Make sure that the mime type for all file extensions is registered + """ + for fn in filenames: + ext = os.path.splitext(fn)[-1] + if not ext: + continue + mime = mimetypes.types_map[True][ext] + fe = FileExtension(ext[1:], mime) + self.Default.append(fe) + + + def _write_vba(self, workbook): + """ + Add content types from cached workbook when keeping VBA + """ + if workbook.vba_archive: + node = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES)) + mf = Manifest.from_tree(node) + filenames = self.filenames + for override in mf.Override: + if override.PartName not in (ACTIVEX, CTRL, VBA): + continue + if override.PartName not in filenames: + self.Override.append(override) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py new file mode 100644 index 00000000..4318282d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py @@ -0,0 +1,158 @@ +# Copyright (c) 2010-2024 openpyxl + +import posixpath +from warnings import warn + +from openpyxl.descriptors import ( + String, + Alias, + Sequence, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.container import ElementList + +from openpyxl.xml.constants import REL_NS, PKG_REL_NS +from openpyxl.xml.functions import ( + Element, + fromstring, +) + + +class Relationship(Serialisable): + """Represents many kinds of relationships.""" + + tagname = "Relationship" + + Type = String() + Target = String() + target = Alias("Target") + TargetMode = String(allow_none=True) + Id = String(allow_none=True) + id = Alias("Id") + + + def __init__(self, + Id=None, + Type=None, + type=None, + Target=None, + TargetMode=None + ): + """ + `type` can be used as a shorthand with the default relationships namespace + otherwise the `Type` must be a fully qualified URL + """ + if type is not None: + Type = "{0}/{1}".format(REL_NS, type) + self.Type = Type + self.Target = Target + self.TargetMode = TargetMode + self.Id = Id + + +class RelationshipList(ElementList): + + tagname = "Relationships" + expected_type = Relationship + + + def append(self, value): + super().append(value) + if not value.Id: + value.Id = f"rId{len(self)}" + + + def find(self, content_type): + """ + Find relationships by content-type + NB. these content-types namespaced objects and different to the MIME-types + in the package manifest :-( + """ + for r in self: + if r.Type == content_type: + yield r + + + def get(self, key): + for r in self: + if r.Id == key: + return r + raise KeyError("Unknown relationship: {0}".format(key)) + + + def to_dict(self): + """Return a dictionary of relations keyed by id""" + return {r.id:r for r in self} + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", PKG_REL_NS) + return tree + + +def get_rels_path(path): + """ + Convert relative path to absolutes that can be loaded from a zip + archive. + The path to be passed in is that of containing object (workbook, + worksheet, etc.) + """ + folder, obj = posixpath.split(path) + filename = posixpath.join(folder, '_rels', '{0}.rels'.format(obj)) + return filename + + +def get_dependents(archive, filename): + """ + Normalise dependency file paths to absolute ones + + Relative paths are relative to parent object + """ + src = archive.read(filename) + node = fromstring(src) + try: + rels = RelationshipList.from_tree(node) + except TypeError: + msg = "{0} contains invalid dependency definitions".format(filename) + warn(msg) + rels = RelationshipList() + folder = posixpath.dirname(filename) + parent = posixpath.split(folder)[0] + for r in rels: + if r.TargetMode == "External": + continue + elif r.target.startswith("/"): + r.target = r.target[1:] + else: + pth = posixpath.join(parent, r.target) + r.target = posixpath.normpath(pth) + return rels + + +def get_rel(archive, deps, id=None, cls=None): + """ + Get related object based on id or rel_type + """ + if not any([id, cls]): + raise ValueError("Either the id or the content type are required") + if id is not None: + rel = deps.get(id) + else: + try: + rel = next(deps.find(cls.rel_type)) + except StopIteration: # no known dependency + return + + path = rel.target + src = archive.read(path) + tree = fromstring(src) + obj = cls.from_tree(tree) + + rels_path = get_rels_path(path) + try: + obj.deps = get_dependents(archive, rels_path) + except KeyError: + obj.deps = [] + + return obj diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py new file mode 100644 index 00000000..a6413cdc --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py @@ -0,0 +1,185 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + String, + Integer, + Bool, + NoneSet, +) +from openpyxl.descriptors.excel import ExtensionList, Relation +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.descriptors.nested import NestedString + +from openpyxl.xml.constants import SHEET_MAIN_NS + +from openpyxl.workbook.defined_name import DefinedNameList +from openpyxl.workbook.external_reference import ExternalReference +from openpyxl.workbook.function_group import FunctionGroupList +from openpyxl.workbook.properties import WorkbookProperties, CalcProperties, FileVersion +from openpyxl.workbook.protection import WorkbookProtection, FileSharing +from openpyxl.workbook.smart_tags import SmartTagList, SmartTagProperties +from openpyxl.workbook.views import CustomWorkbookView, BookView +from openpyxl.workbook.web import WebPublishing, WebPublishObjectList + + +class FileRecoveryProperties(Serialisable): + + tagname = "fileRecoveryPr" + + autoRecover = Bool(allow_none=True) + crashSave = Bool(allow_none=True) + dataExtractLoad = Bool(allow_none=True) + repairLoad = Bool(allow_none=True) + + def __init__(self, + autoRecover=None, + crashSave=None, + dataExtractLoad=None, + repairLoad=None, + ): + self.autoRecover = autoRecover + self.crashSave = crashSave + self.dataExtractLoad = dataExtractLoad + self.repairLoad = repairLoad + + +class ChildSheet(Serialisable): + """ + Represents a reference to a worksheet or chartsheet in workbook.xml + + It contains the title, order and state but only an indirect reference to + the objects themselves. + """ + + tagname = "sheet" + + name = String() + sheetId = Integer() + state = NoneSet(values=(['visible', 'hidden', 'veryHidden'])) + id = Relation() + + def __init__(self, + name=None, + sheetId=None, + state="visible", + id=None, + ): + self.name = name + self.sheetId = sheetId + self.state = state + self.id = id + + +class PivotCache(Serialisable): + + tagname = "pivotCache" + + cacheId = Integer() + id = Relation() + + def __init__(self, + cacheId=None, + id=None + ): + self.cacheId = cacheId + self.id = id + + +class WorkbookPackage(Serialisable): + + """ + Represent the workbook file in the archive + """ + + tagname = "workbook" + + conformance = NoneSet(values=['strict', 'transitional']) + fileVersion = Typed(expected_type=FileVersion, allow_none=True) + fileSharing = Typed(expected_type=FileSharing, allow_none=True) + workbookPr = Typed(expected_type=WorkbookProperties, allow_none=True) + properties = Alias("workbookPr") + workbookProtection = Typed(expected_type=WorkbookProtection, allow_none=True) + bookViews = NestedSequence(expected_type=BookView) + sheets = NestedSequence(expected_type=ChildSheet) + functionGroups = Typed(expected_type=FunctionGroupList, allow_none=True) + externalReferences = NestedSequence(expected_type=ExternalReference) + definedNames = Typed(expected_type=DefinedNameList, allow_none=True) + calcPr = Typed(expected_type=CalcProperties, allow_none=True) + oleSize = NestedString(allow_none=True, attribute="ref") + customWorkbookViews = NestedSequence(expected_type=CustomWorkbookView) + pivotCaches = NestedSequence(expected_type=PivotCache, allow_none=True) + smartTagPr = Typed(expected_type=SmartTagProperties, allow_none=True) + smartTagTypes = Typed(expected_type=SmartTagList, allow_none=True) + webPublishing = Typed(expected_type=WebPublishing, allow_none=True) + fileRecoveryPr = Typed(expected_type=FileRecoveryProperties, allow_none=True) + webPublishObjects = Typed(expected_type=WebPublishObjectList, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + Ignorable = NestedString(namespace="http://schemas.openxmlformats.org/markup-compatibility/2006", allow_none=True) + + __elements__ = ('fileVersion', 'fileSharing', 'workbookPr', + 'workbookProtection', 'bookViews', 'sheets', 'functionGroups', + 'externalReferences', 'definedNames', 'calcPr', 'oleSize', + 'customWorkbookViews', 'pivotCaches', 'smartTagPr', 'smartTagTypes', + 'webPublishing', 'fileRecoveryPr', 'webPublishObjects') + + def __init__(self, + conformance=None, + fileVersion=None, + fileSharing=None, + workbookPr=None, + workbookProtection=None, + bookViews=(), + sheets=(), + functionGroups=None, + externalReferences=(), + definedNames=None, + calcPr=None, + oleSize=None, + customWorkbookViews=(), + pivotCaches=(), + smartTagPr=None, + smartTagTypes=None, + webPublishing=None, + fileRecoveryPr=None, + webPublishObjects=None, + extLst=None, + Ignorable=None, + ): + self.conformance = conformance + self.fileVersion = fileVersion + self.fileSharing = fileSharing + if workbookPr is None: + workbookPr = WorkbookProperties() + self.workbookPr = workbookPr + self.workbookProtection = workbookProtection + self.bookViews = bookViews + self.sheets = sheets + self.functionGroups = functionGroups + self.externalReferences = externalReferences + self.definedNames = definedNames + self.calcPr = calcPr + self.oleSize = oleSize + self.customWorkbookViews = customWorkbookViews + self.pivotCaches = pivotCaches + self.smartTagPr = smartTagPr + self.smartTagTypes = smartTagTypes + self.webPublishing = webPublishing + self.fileRecoveryPr = fileRecoveryPr + self.webPublishObjects = webPublishObjects + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def active(self): + for view in self.bookViews: + if view.activeTab is not None: + return view.activeTab + return 0 diff --git a/.venv/lib/python3.12/site-packages/openpyxl/pivot/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/pivot/__init__.py new file mode 100644 index 00000000..ab6cdead --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/pivot/__init__.py @@ -0,0 +1 @@ +# Copyright (c) 2010-2024 openpyxl diff --git a/.venv/lib/python3.12/site-packages/openpyxl/pivot/cache.py b/.venv/lib/python3.12/site-packages/openpyxl/pivot/cache.py new file mode 100644 index 00000000..7ae2b4dd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/pivot/cache.py @@ -0,0 +1,965 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Float, + Set, + NoneSet, + String, + Integer, + DateTime, + Sequence, +) + +from openpyxl.descriptors.excel import ( + HexBinary, + ExtensionList, + Relation, +) +from openpyxl.descriptors.nested import NestedInteger +from openpyxl.descriptors.sequence import ( + NestedSequence, + MultiSequence, + MultiSequencePart, +) +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.xml.functions import tostring +from openpyxl.packaging.relationship import ( + RelationshipList, + Relationship, + get_rels_path +) + +from .table import ( + PivotArea, + Reference, +) +from .fields import ( + Boolean, + Error, + Missing, + Number, + Text, + TupleList, + DateTimeField, +) + +class MeasureDimensionMap(Serialisable): + + tagname = "map" + + measureGroup = Integer(allow_none=True) + dimension = Integer(allow_none=True) + + def __init__(self, + measureGroup=None, + dimension=None, + ): + self.measureGroup = measureGroup + self.dimension = dimension + + +class MeasureGroup(Serialisable): + + tagname = "measureGroup" + + name = String() + caption = String() + + def __init__(self, + name=None, + caption=None, + ): + self.name = name + self.caption = caption + + +class PivotDimension(Serialisable): + + tagname = "dimension" + + measure = Bool() + name = String() + uniqueName = String() + caption = String() + + def __init__(self, + measure=None, + name=None, + uniqueName=None, + caption=None, + ): + self.measure = measure + self.name = name + self.uniqueName = uniqueName + self.caption = caption + + +class CalculatedMember(Serialisable): + + tagname = "calculatedMember" + + name = String() + mdx = String() + memberName = String(allow_none=True) + hierarchy = String(allow_none=True) + parent = String(allow_none=True) + solveOrder = Integer(allow_none=True) + set = Bool() + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + name=None, + mdx=None, + memberName=None, + hierarchy=None, + parent=None, + solveOrder=None, + set=None, + extLst=None, + ): + self.name = name + self.mdx = mdx + self.memberName = memberName + self.hierarchy = hierarchy + self.parent = parent + self.solveOrder = solveOrder + self.set = set + #self.extLst = extLst + + +class CalculatedItem(Serialisable): + + tagname = "calculatedItem" + + field = Integer(allow_none=True) + formula = String() + pivotArea = Typed(expected_type=PivotArea, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('pivotArea', 'extLst') + + def __init__(self, + field=None, + formula=None, + pivotArea=None, + extLst=None, + ): + self.field = field + self.formula = formula + self.pivotArea = pivotArea + self.extLst = extLst + + +class ServerFormat(Serialisable): + + tagname = "serverFormat" + + culture = String(allow_none=True) + format = String(allow_none=True) + + def __init__(self, + culture=None, + format=None, + ): + self.culture = culture + self.format = format + + +class Query(Serialisable): + + tagname = "query" + + mdx = String() + tpls = Typed(expected_type=TupleList, allow_none=True) + + __elements__ = ('tpls',) + + def __init__(self, + mdx=None, + tpls=None, + ): + self.mdx = mdx + self.tpls = tpls + + +class OLAPSet(Serialisable): + + tagname = "set" + + count = Integer() + maxRank = Integer() + setDefinition = String() + sortType = NoneSet(values=(['ascending', 'descending', 'ascendingAlpha', + 'descendingAlpha', 'ascendingNatural', 'descendingNatural'])) + queryFailed = Bool() + tpls = Typed(expected_type=TupleList, allow_none=True) + sortByTuple = Typed(expected_type=TupleList, allow_none=True) + + __elements__ = ('tpls', 'sortByTuple') + + def __init__(self, + count=None, + maxRank=None, + setDefinition=None, + sortType=None, + queryFailed=None, + tpls=None, + sortByTuple=None, + ): + self.count = count + self.maxRank = maxRank + self.setDefinition = setDefinition + self.sortType = sortType + self.queryFailed = queryFailed + self.tpls = tpls + self.sortByTuple = sortByTuple + + +class PCDSDTCEntries(Serialisable): + # Implements CT_PCDSDTCEntries + + tagname = "entries" + + count = Integer(allow_none=True) + # elements are choice + m = Typed(expected_type=Missing, allow_none=True) + n = Typed(expected_type=Number, allow_none=True) + e = Typed(expected_type=Error, allow_none=True) + s = Typed(expected_type=Text, allow_none=True) + + __elements__ = ('m', 'n', 'e', 's') + + def __init__(self, + count=None, + m=None, + n=None, + e=None, + s=None, + ): + self.count = count + self.m = m + self.n = n + self.e = e + self.s = s + + +class TupleCache(Serialisable): + + tagname = "tupleCache" + + entries = Typed(expected_type=PCDSDTCEntries, allow_none=True) + sets = NestedSequence(expected_type=OLAPSet, count=True) + queryCache = NestedSequence(expected_type=Query, count=True) + serverFormats = NestedSequence(expected_type=ServerFormat, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('entries', 'sets', 'queryCache', 'serverFormats', 'extLst') + + def __init__(self, + entries=None, + sets=(), + queryCache=(), + serverFormats=(), + extLst=None, + ): + self.entries = entries + self.sets = sets + self.queryCache = queryCache + self.serverFormats = serverFormats + self.extLst = extLst + + +class OLAPKPI(Serialisable): + + tagname = "kpi" + + uniqueName = String() + caption = String(allow_none=True) + displayFolder = String(allow_none=True) + measureGroup = String(allow_none=True) + parent = String(allow_none=True) + value = String() + goal = String(allow_none=True) + status = String(allow_none=True) + trend = String(allow_none=True) + weight = String(allow_none=True) + time = String(allow_none=True) + + def __init__(self, + uniqueName=None, + caption=None, + displayFolder=None, + measureGroup=None, + parent=None, + value=None, + goal=None, + status=None, + trend=None, + weight=None, + time=None, + ): + self.uniqueName = uniqueName + self.caption = caption + self.displayFolder = displayFolder + self.measureGroup = measureGroup + self.parent = parent + self.value = value + self.goal = goal + self.status = status + self.trend = trend + self.weight = weight + self.time = time + + +class GroupMember(Serialisable): + + tagname = "groupMember" + + uniqueName = String() + group = Bool() + + def __init__(self, + uniqueName=None, + group=None, + ): + self.uniqueName = uniqueName + self.group = group + + +class LevelGroup(Serialisable): + + tagname = "group" + + name = String() + uniqueName = String() + caption = String() + uniqueParent = String() + id = Integer() + groupMembers = NestedSequence(expected_type=GroupMember, count=True) + + __elements__ = ('groupMembers',) + + def __init__(self, + name=None, + uniqueName=None, + caption=None, + uniqueParent=None, + id=None, + groupMembers=(), + ): + self.name = name + self.uniqueName = uniqueName + self.caption = caption + self.uniqueParent = uniqueParent + self.id = id + self.groupMembers = groupMembers + + +class GroupLevel(Serialisable): + + tagname = "groupLevel" + + uniqueName = String() + caption = String() + user = Bool() + customRollUp = Bool() + groups = NestedSequence(expected_type=LevelGroup, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('groups', 'extLst') + + def __init__(self, + uniqueName=None, + caption=None, + user=None, + customRollUp=None, + groups=(), + extLst=None, + ): + self.uniqueName = uniqueName + self.caption = caption + self.user = user + self.customRollUp = customRollUp + self.groups = groups + self.extLst = extLst + + +class FieldUsage(Serialisable): + + tagname = "fieldUsage" + + x = Integer() + + def __init__(self, + x=None, + ): + self.x = x + + +class CacheHierarchy(Serialisable): + + tagname = "cacheHierarchy" + + uniqueName = String() + caption = String(allow_none=True) + measure = Bool() + set = Bool() + parentSet = Integer(allow_none=True) + iconSet = Integer() + attribute = Bool() + time = Bool() + keyAttribute = Bool() + defaultMemberUniqueName = String(allow_none=True) + allUniqueName = String(allow_none=True) + allCaption = String(allow_none=True) + dimensionUniqueName = String(allow_none=True) + displayFolder = String(allow_none=True) + measureGroup = String(allow_none=True) + measures = Bool() + count = Integer() + oneField = Bool() + memberValueDatatype = Integer(allow_none=True) + unbalanced = Bool(allow_none=True) + unbalancedGroup = Bool(allow_none=True) + hidden = Bool() + fieldsUsage = NestedSequence(expected_type=FieldUsage, count=True) + groupLevels = NestedSequence(expected_type=GroupLevel, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('fieldsUsage', 'groupLevels') + + def __init__(self, + uniqueName="", + caption=None, + measure=None, + set=None, + parentSet=None, + iconSet=0, + attribute=None, + time=None, + keyAttribute=None, + defaultMemberUniqueName=None, + allUniqueName=None, + allCaption=None, + dimensionUniqueName=None, + displayFolder=None, + measureGroup=None, + measures=None, + count=None, + oneField=None, + memberValueDatatype=None, + unbalanced=None, + unbalancedGroup=None, + hidden=None, + fieldsUsage=(), + groupLevels=(), + extLst=None, + ): + self.uniqueName = uniqueName + self.caption = caption + self.measure = measure + self.set = set + self.parentSet = parentSet + self.iconSet = iconSet + self.attribute = attribute + self.time = time + self.keyAttribute = keyAttribute + self.defaultMemberUniqueName = defaultMemberUniqueName + self.allUniqueName = allUniqueName + self.allCaption = allCaption + self.dimensionUniqueName = dimensionUniqueName + self.displayFolder = displayFolder + self.measureGroup = measureGroup + self.measures = measures + self.count = count + self.oneField = oneField + self.memberValueDatatype = memberValueDatatype + self.unbalanced = unbalanced + self.unbalancedGroup = unbalancedGroup + self.hidden = hidden + self.fieldsUsage = fieldsUsage + self.groupLevels = groupLevels + self.extLst = extLst + + +class GroupItems(Serialisable): + + tagname = "groupItems" + + m = Sequence(expected_type=Missing) + n = Sequence(expected_type=Number) + b = Sequence(expected_type=Boolean) + e = Sequence(expected_type=Error) + s = Sequence(expected_type=Text) + d = Sequence(expected_type=DateTimeField,) + + __elements__ = ('m', 'n', 'b', 'e', 's', 'd') + __attrs__ = ("count", ) + + def __init__(self, + count=None, + m=(), + n=(), + b=(), + e=(), + s=(), + d=(), + ): + self.m = m + self.n = n + self.b = b + self.e = e + self.s = s + self.d = d + + + @property + def count(self): + return len(self.m + self.n + self.b + self.e + self.s + self.d) + + +class RangePr(Serialisable): + + tagname = "rangePr" + + autoStart = Bool(allow_none=True) + autoEnd = Bool(allow_none=True) + groupBy = NoneSet(values=(['range', 'seconds', 'minutes', 'hours', 'days', + 'months', 'quarters', 'years'])) + startNum = Float(allow_none=True) + endNum = Float(allow_none=True) + startDate = DateTime(allow_none=True) + endDate = DateTime(allow_none=True) + groupInterval = Float(allow_none=True) + + def __init__(self, + autoStart=True, + autoEnd=True, + groupBy="range", + startNum=None, + endNum=None, + startDate=None, + endDate=None, + groupInterval=1, + ): + self.autoStart = autoStart + self.autoEnd = autoEnd + self.groupBy = groupBy + self.startNum = startNum + self.endNum = endNum + self.startDate = startDate + self.endDate = endDate + self.groupInterval = groupInterval + + +class FieldGroup(Serialisable): + + tagname = "fieldGroup" + + par = Integer(allow_none=True) + base = Integer(allow_none=True) + rangePr = Typed(expected_type=RangePr, allow_none=True) + discretePr = NestedSequence(expected_type=NestedInteger, count=True) + groupItems = Typed(expected_type=GroupItems, allow_none=True) + + __elements__ = ('rangePr', 'discretePr', 'groupItems') + + def __init__(self, + par=None, + base=None, + rangePr=None, + discretePr=(), + groupItems=None, + ): + self.par = par + self.base = base + self.rangePr = rangePr + self.discretePr = discretePr + self.groupItems = groupItems + + +class SharedItems(Serialisable): + + tagname = "sharedItems" + + _fields = MultiSequence() + m = MultiSequencePart(expected_type=Missing, store="_fields") + n = MultiSequencePart(expected_type=Number, store="_fields") + b = MultiSequencePart(expected_type=Boolean, store="_fields") + e = MultiSequencePart(expected_type=Error, store="_fields") + s = MultiSequencePart(expected_type=Text, store="_fields") + d = MultiSequencePart(expected_type=DateTimeField, store="_fields") + # attributes are optional and must be derived from associated cache records + containsSemiMixedTypes = Bool(allow_none=True) + containsNonDate = Bool(allow_none=True) + containsDate = Bool(allow_none=True) + containsString = Bool(allow_none=True) + containsBlank = Bool(allow_none=True) + containsMixedTypes = Bool(allow_none=True) + containsNumber = Bool(allow_none=True) + containsInteger = Bool(allow_none=True) + minValue = Float(allow_none=True) + maxValue = Float(allow_none=True) + minDate = DateTime(allow_none=True) + maxDate = DateTime(allow_none=True) + longText = Bool(allow_none=True) + + __attrs__ = ('count', 'containsBlank', 'containsDate', 'containsInteger', + 'containsMixedTypes', 'containsNonDate', 'containsNumber', + 'containsSemiMixedTypes', 'containsString', 'minValue', 'maxValue', + 'minDate', 'maxDate', 'longText') + + def __init__(self, + _fields=(), + containsSemiMixedTypes=None, + containsNonDate=None, + containsDate=None, + containsString=None, + containsBlank=None, + containsMixedTypes=None, + containsNumber=None, + containsInteger=None, + minValue=None, + maxValue=None, + minDate=None, + maxDate=None, + count=None, + longText=None, + ): + self._fields = _fields + self.containsBlank = containsBlank + self.containsDate = containsDate + self.containsNonDate = containsNonDate + self.containsString = containsString + self.containsMixedTypes = containsMixedTypes + self.containsSemiMixedTypes = containsSemiMixedTypes + self.containsNumber = containsNumber + self.containsInteger = containsInteger + self.minValue = minValue + self.maxValue = maxValue + self.minDate = minDate + self.maxDate = maxDate + self.longText = longText + + + @property + def count(self): + return len(self._fields) + + +class CacheField(Serialisable): + + tagname = "cacheField" + + sharedItems = Typed(expected_type=SharedItems, allow_none=True) + fieldGroup = Typed(expected_type=FieldGroup, allow_none=True) + mpMap = NestedInteger(allow_none=True, attribute="v") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + name = String() + caption = String(allow_none=True) + propertyName = String(allow_none=True) + serverField = Bool(allow_none=True) + uniqueList = Bool(allow_none=True) + numFmtId = Integer(allow_none=True) + formula = String(allow_none=True) + sqlType = Integer(allow_none=True) + hierarchy = Integer(allow_none=True) + level = Integer(allow_none=True) + databaseField = Bool(allow_none=True) + mappingCount = Integer(allow_none=True) + memberPropertyField = Bool(allow_none=True) + + __elements__ = ('sharedItems', 'fieldGroup', 'mpMap') + + def __init__(self, + sharedItems=None, + fieldGroup=None, + mpMap=None, + extLst=None, + name=None, + caption=None, + propertyName=None, + serverField=None, + uniqueList=True, + numFmtId=None, + formula=None, + sqlType=0, + hierarchy=0, + level=0, + databaseField=True, + mappingCount=None, + memberPropertyField=None, + ): + self.sharedItems = sharedItems + self.fieldGroup = fieldGroup + self.mpMap = mpMap + self.extLst = extLst + self.name = name + self.caption = caption + self.propertyName = propertyName + self.serverField = serverField + self.uniqueList = uniqueList + self.numFmtId = numFmtId + self.formula = formula + self.sqlType = sqlType + self.hierarchy = hierarchy + self.level = level + self.databaseField = databaseField + self.mappingCount = mappingCount + self.memberPropertyField = memberPropertyField + + +class RangeSet(Serialisable): + + tagname = "rangeSet" + + i1 = Integer(allow_none=True) + i2 = Integer(allow_none=True) + i3 = Integer(allow_none=True) + i4 = Integer(allow_none=True) + ref = String() + name = String(allow_none=True) + sheet = String(allow_none=True) + + def __init__(self, + i1=None, + i2=None, + i3=None, + i4=None, + ref=None, + name=None, + sheet=None, + ): + self.i1 = i1 + self.i2 = i2 + self.i3 = i3 + self.i4 = i4 + self.ref = ref + self.name = name + self.sheet = sheet + + +class PageItem(Serialisable): + + tagname = "pageItem" + + name = String() + + def __init__(self, + name=None, + ): + self.name = name + + +class Consolidation(Serialisable): + + tagname = "consolidation" + + autoPage = Bool(allow_none=True) + pages = NestedSequence(expected_type=PageItem, count=True) + rangeSets = NestedSequence(expected_type=RangeSet, count=True) + + __elements__ = ('pages', 'rangeSets') + + def __init__(self, + autoPage=None, + pages=(), + rangeSets=(), + ): + self.autoPage = autoPage + self.pages = pages + self.rangeSets = rangeSets + + +class WorksheetSource(Serialisable): + + tagname = "worksheetSource" + + ref = String(allow_none=True) + name = String(allow_none=True) + sheet = String(allow_none=True) + + def __init__(self, + ref=None, + name=None, + sheet=None, + ): + self.ref = ref + self.name = name + self.sheet = sheet + + +class CacheSource(Serialisable): + + tagname = "cacheSource" + + type = Set(values=(['worksheet', 'external', 'consolidation', 'scenario'])) + connectionId = Integer(allow_none=True) + # some elements are choice + worksheetSource = Typed(expected_type=WorksheetSource, allow_none=True) + consolidation = Typed(expected_type=Consolidation, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('worksheetSource', 'consolidation',) + + def __init__(self, + type=None, + connectionId=None, + worksheetSource=None, + consolidation=None, + extLst=None, + ): + self.type = type + self.connectionId = connectionId + self.worksheetSource = worksheetSource + self.consolidation = consolidation + + +class CacheDefinition(Serialisable): + + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" + rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" + _id = 1 + _path = "/xl/pivotCache/pivotCacheDefinition{0}.xml" + records = None + + tagname = "pivotCacheDefinition" + + invalid = Bool(allow_none=True) + saveData = Bool(allow_none=True) + refreshOnLoad = Bool(allow_none=True) + optimizeMemory = Bool(allow_none=True) + enableRefresh = Bool(allow_none=True) + refreshedBy = String(allow_none=True) + refreshedDate = Float(allow_none=True) + refreshedDateIso = DateTime(allow_none=True) + backgroundQuery = Bool(allow_none=True) + missingItemsLimit = Integer(allow_none=True) + createdVersion = Integer(allow_none=True) + refreshedVersion = Integer(allow_none=True) + minRefreshableVersion = Integer(allow_none=True) + recordCount = Integer(allow_none=True) + upgradeOnRefresh = Bool(allow_none=True) + supportSubquery = Bool(allow_none=True) + supportAdvancedDrill = Bool(allow_none=True) + cacheSource = Typed(expected_type=CacheSource) + cacheFields = NestedSequence(expected_type=CacheField, count=True) + cacheHierarchies = NestedSequence(expected_type=CacheHierarchy, allow_none=True) + kpis = NestedSequence(expected_type=OLAPKPI, count=True) + tupleCache = Typed(expected_type=TupleCache, allow_none=True) + calculatedItems = NestedSequence(expected_type=CalculatedItem, count=True) + calculatedMembers = NestedSequence(expected_type=CalculatedMember, count=True) + dimensions = NestedSequence(expected_type=PivotDimension, allow_none=True) + measureGroups = NestedSequence(expected_type=MeasureGroup, count=True) + maps = NestedSequence(expected_type=MeasureDimensionMap, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + id = Relation() + + __elements__ = ('cacheSource', 'cacheFields', 'cacheHierarchies', 'kpis', + 'tupleCache', 'calculatedItems', 'calculatedMembers', 'dimensions', + 'measureGroups', 'maps',) + + def __init__(self, + invalid=None, + saveData=None, + refreshOnLoad=None, + optimizeMemory=None, + enableRefresh=None, + refreshedBy=None, + refreshedDate=None, + refreshedDateIso=None, + backgroundQuery=None, + missingItemsLimit=None, + createdVersion=None, + refreshedVersion=None, + minRefreshableVersion=None, + recordCount=None, + upgradeOnRefresh=None, + tupleCache=None, + supportSubquery=None, + supportAdvancedDrill=None, + cacheSource=None, + cacheFields=(), + cacheHierarchies=(), + kpis=(), + calculatedItems=(), + calculatedMembers=(), + dimensions=(), + measureGroups=(), + maps=(), + extLst=None, + id = None, + ): + self.invalid = invalid + self.saveData = saveData + self.refreshOnLoad = refreshOnLoad + self.optimizeMemory = optimizeMemory + self.enableRefresh = enableRefresh + self.refreshedBy = refreshedBy + self.refreshedDate = refreshedDate + self.refreshedDateIso = refreshedDateIso + self.backgroundQuery = backgroundQuery + self.missingItemsLimit = missingItemsLimit + self.createdVersion = createdVersion + self.refreshedVersion = refreshedVersion + self.minRefreshableVersion = minRefreshableVersion + self.recordCount = recordCount + self.upgradeOnRefresh = upgradeOnRefresh + self.supportSubquery = supportSubquery + self.supportAdvancedDrill = supportAdvancedDrill + self.cacheSource = cacheSource + self.cacheFields = cacheFields + self.cacheHierarchies = cacheHierarchies + self.kpis = kpis + self.tupleCache = tupleCache + self.calculatedItems = calculatedItems + self.calculatedMembers = calculatedMembers + self.dimensions = dimensions + self.measureGroups = measureGroups + self.maps = maps + self.id = id + + + def to_tree(self): + node = super().to_tree() + node.set("xmlns", SHEET_MAIN_NS) + return node + + + @property + def path(self): + return self._path.format(self._id) + + + def _write(self, archive, manifest): + """ + Add to zipfile and update manifest + """ + self._write_rels(archive, manifest) + xml = tostring(self.to_tree()) + archive.writestr(self.path[1:], xml) + manifest.append(self) + + + def _write_rels(self, archive, manifest): + """ + Write the relevant child objects and add links + """ + if self.records is None: + return + + rels = RelationshipList() + r = Relationship(Type=self.records.rel_type, Target=self.records.path) + rels.append(r) + self.id = r.id + self.records._id = self._id + self.records._write(archive, manifest) + + path = get_rels_path(self.path) + xml = tostring(rels.to_tree()) + archive.writestr(path[1:], xml) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/pivot/fields.py b/.venv/lib/python3.12/site-packages/openpyxl/pivot/fields.py new file mode 100644 index 00000000..cd6bcb28 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/pivot/fields.py @@ -0,0 +1,326 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + DateTime, + Bool, + Float, + String, + Integer, + Sequence, +) +from openpyxl.descriptors.excel import HexBinary + +class Index(Serialisable): + + tagname = "x" + + v = Integer(allow_none=True) + + def __init__(self, + v=0, + ): + self.v = v + + +class Tuple(Serialisable): + + tagname = "tpl" + + fld = Integer(allow_none=True) + hier = Integer(allow_none=True) + item = Integer() + + def __init__(self, + fld=None, + hier=None, + item=None, + ): + self.fld = fld + self.hier = hier + self.item = item + + +class TupleList(Serialisable): + + tagname = "tpls" + + c = Integer(allow_none=True) + tpl = Typed(expected_type=Tuple, ) + + __elements__ = ('tpl',) + + def __init__(self, + c=None, + tpl=None, + ): + self.c = c + self.tpl = tpl + + +class Missing(Serialisable): + + tagname = "m" + + tpls = Sequence(expected_type=TupleList) + x = Sequence(expected_type=Index) + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + _in = Integer(allow_none=True) + bc = HexBinary(allow_none=True) + fc = HexBinary(allow_none=True) + i = Bool(allow_none=True) + un = Bool(allow_none=True) + st = Bool(allow_none=True) + b = Bool(allow_none=True) + + __elements__ = ('tpls', 'x') + + def __init__(self, + tpls=(), + x=(), + u=None, + f=None, + c=None, + cp=None, + _in=None, + bc=None, + fc=None, + i=None, + un=None, + st=None, + b=None, + ): + self.tpls = tpls + self.x = x + self.u = u + self.f = f + self.c = c + self.cp = cp + self._in = _in + self.bc = bc + self.fc = fc + self.i = i + self.un = un + self.st = st + self.b = b + + +class Number(Serialisable): + + tagname = "n" + + tpls = Sequence(expected_type=TupleList) + x = Sequence(expected_type=Index) + v = Float() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + _in = Integer(allow_none=True) + bc = HexBinary(allow_none=True) + fc = HexBinary(allow_none=True) + i = Bool(allow_none=True) + un = Bool(allow_none=True) + st = Bool(allow_none=True) + b = Bool(allow_none=True) + + __elements__ = ('tpls', 'x') + + def __init__(self, + tpls=(), + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + _in=None, + bc=None, + fc=None, + i=None, + un=None, + st=None, + b=None, + ): + self.tpls = tpls + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp + self._in = _in + self.bc = bc + self.fc = fc + self.i = i + self.un = un + self.st = st + self.b = b + + +class Error(Serialisable): + + tagname = "e" + + tpls = Typed(expected_type=TupleList, allow_none=True) + x = Sequence(expected_type=Index) + v = String() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + _in = Integer(allow_none=True) + bc = HexBinary(allow_none=True) + fc = HexBinary(allow_none=True) + i = Bool(allow_none=True) + un = Bool(allow_none=True) + st = Bool(allow_none=True) + b = Bool(allow_none=True) + + __elements__ = ('tpls', 'x') + + def __init__(self, + tpls=None, + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + _in=None, + bc=None, + fc=None, + i=None, + un=None, + st=None, + b=None, + ): + self.tpls = tpls + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp + self._in = _in + self.bc = bc + self.fc = fc + self.i = i + self.un = un + self.st = st + self.b = b + + +class Boolean(Serialisable): + + tagname = "b" + + x = Sequence(expected_type=Index) + v = Bool() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + + __elements__ = ('x',) + + def __init__(self, + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + ): + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp + + +class Text(Serialisable): + + tagname = "s" + + tpls = Sequence(expected_type=TupleList) + x = Sequence(expected_type=Index) + v = String() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + _in = Integer(allow_none=True) + bc = HexBinary(allow_none=True) + fc = HexBinary(allow_none=True) + i = Bool(allow_none=True) + un = Bool(allow_none=True) + st = Bool(allow_none=True) + b = Bool(allow_none=True) + + __elements__ = ('tpls', 'x') + + def __init__(self, + tpls=(), + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + _in=None, + bc=None, + fc=None, + i=None, + un=None, + st=None, + b=None, + ): + self.tpls = tpls + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp + self._in = _in + self.bc = bc + self.fc = fc + self.i = i + self.un = un + self.st = st + self.b = b + + +class DateTimeField(Serialisable): + + tagname = "d" + + x = Sequence(expected_type=Index) + v = DateTime() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + + __elements__ = ('x',) + + def __init__(self, + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + ): + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp diff --git a/.venv/lib/python3.12/site-packages/openpyxl/pivot/record.py b/.venv/lib/python3.12/site-packages/openpyxl/pivot/record.py new file mode 100644 index 00000000..42603770 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/pivot/record.py @@ -0,0 +1,111 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + Sequence, +) +from openpyxl.descriptors.sequence import ( + MultiSequence, + MultiSequencePart, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedBool, +) + +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.xml.functions import tostring + +from .fields import ( + Boolean, + Error, + Missing, + Number, + Text, + TupleList, + DateTimeField, + Index, +) + + +class Record(Serialisable): + + tagname = "r" + + _fields = MultiSequence() + m = MultiSequencePart(expected_type=Missing, store="_fields") + n = MultiSequencePart(expected_type=Number, store="_fields") + b = MultiSequencePart(expected_type=Boolean, store="_fields") + e = MultiSequencePart(expected_type=Error, store="_fields") + s = MultiSequencePart(expected_type=Text, store="_fields") + d = MultiSequencePart(expected_type=DateTimeField, store="_fields") + x = MultiSequencePart(expected_type=Index, store="_fields") + + + def __init__(self, + _fields=(), + m=None, + n=None, + b=None, + e=None, + s=None, + d=None, + x=None, + ): + self._fields = _fields + + +class RecordList(Serialisable): + + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml" + rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords" + _id = 1 + _path = "/xl/pivotCache/pivotCacheRecords{0}.xml" + + tagname ="pivotCacheRecords" + + r = Sequence(expected_type=Record, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('r', ) + __attrs__ = ('count', ) + + def __init__(self, + count=None, + r=(), + extLst=None, + ): + self.r = r + self.extLst = extLst + + + @property + def count(self): + return len(self.r) + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def path(self): + return self._path.format(self._id) + + + def _write(self, archive, manifest): + """ + Write to zipfile and update manifest + """ + xml = tostring(self.to_tree()) + archive.writestr(self.path[1:], xml) + manifest.append(self) + + + def _write_rels(self, archive, manifest): + pass diff --git a/.venv/lib/python3.12/site-packages/openpyxl/pivot/table.py b/.venv/lib/python3.12/site-packages/openpyxl/pivot/table.py new file mode 100644 index 00000000..cc3548b1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/pivot/table.py @@ -0,0 +1,1261 @@ +# Copyright (c) 2010-2024 openpyxl + + +from collections import defaultdict +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + NoneSet, + Set, + Bool, + String, + Bool, + Sequence, +) + +from openpyxl.descriptors.excel import ExtensionList, Relation +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.xml.functions import tostring +from openpyxl.packaging.relationship import ( + RelationshipList, + Relationship, + get_rels_path +) +from .fields import Index + +from openpyxl.worksheet.filters import ( + AutoFilter, +) + + +class HierarchyUsage(Serialisable): + + tagname = "hierarchyUsage" + + hierarchyUsage = Integer() + + def __init__(self, + hierarchyUsage=None, + ): + self.hierarchyUsage = hierarchyUsage + + +class ColHierarchiesUsage(Serialisable): + + tagname = "colHierarchiesUsage" + + colHierarchyUsage = Sequence(expected_type=HierarchyUsage, ) + + __elements__ = ('colHierarchyUsage',) + __attrs__ = ('count', ) + + def __init__(self, + count=None, + colHierarchyUsage=(), + ): + self.colHierarchyUsage = colHierarchyUsage + + + @property + def count(self): + return len(self.colHierarchyUsage) + + +class RowHierarchiesUsage(Serialisable): + + tagname = "rowHierarchiesUsage" + + rowHierarchyUsage = Sequence(expected_type=HierarchyUsage, ) + + __elements__ = ('rowHierarchyUsage',) + __attrs__ = ('count', ) + + def __init__(self, + count=None, + rowHierarchyUsage=(), + ): + self.rowHierarchyUsage = rowHierarchyUsage + + @property + def count(self): + return len(self.rowHierarchyUsage) + + +class PivotFilter(Serialisable): + + tagname = "filter" + + fld = Integer() + mpFld = Integer(allow_none=True) + type = Set(values=(['unknown', 'count', 'percent', 'sum', 'captionEqual', + 'captionNotEqual', 'captionBeginsWith', 'captionNotBeginsWith', + 'captionEndsWith', 'captionNotEndsWith', 'captionContains', + 'captionNotContains', 'captionGreaterThan', 'captionGreaterThanOrEqual', + 'captionLessThan', 'captionLessThanOrEqual', 'captionBetween', + 'captionNotBetween', 'valueEqual', 'valueNotEqual', 'valueGreaterThan', + 'valueGreaterThanOrEqual', 'valueLessThan', 'valueLessThanOrEqual', + 'valueBetween', 'valueNotBetween', 'dateEqual', 'dateNotEqual', + 'dateOlderThan', 'dateOlderThanOrEqual', 'dateNewerThan', + 'dateNewerThanOrEqual', 'dateBetween', 'dateNotBetween', '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'])) + evalOrder = Integer(allow_none=True) + id = Integer() + iMeasureHier = Integer(allow_none=True) + iMeasureFld = Integer(allow_none=True) + name = String(allow_none=True) + description = String(allow_none=True) + stringValue1 = String(allow_none=True) + stringValue2 = String(allow_none=True) + autoFilter = Typed(expected_type=AutoFilter, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('autoFilter',) + + def __init__(self, + fld=None, + mpFld=None, + type=None, + evalOrder=None, + id=None, + iMeasureHier=None, + iMeasureFld=None, + name=None, + description=None, + stringValue1=None, + stringValue2=None, + autoFilter=None, + extLst=None, + ): + self.fld = fld + self.mpFld = mpFld + self.type = type + self.evalOrder = evalOrder + self.id = id + self.iMeasureHier = iMeasureHier + self.iMeasureFld = iMeasureFld + self.name = name + self.description = description + self.stringValue1 = stringValue1 + self.stringValue2 = stringValue2 + self.autoFilter = autoFilter + + +class PivotFilters(Serialisable): + + count = Integer() + filter = Typed(expected_type=PivotFilter, allow_none=True) + + __elements__ = ('filter',) + + def __init__(self, + count=None, + filter=None, + ): + self.filter = filter + + +class PivotTableStyle(Serialisable): + + tagname = "pivotTableStyleInfo" + + name = String(allow_none=True) + showRowHeaders = Bool() + showColHeaders = Bool() + showRowStripes = Bool() + showColStripes = Bool() + showLastColumn = Bool() + + def __init__(self, + name=None, + showRowHeaders=None, + showColHeaders=None, + showRowStripes=None, + showColStripes=None, + showLastColumn=None, + ): + self.name = name + self.showRowHeaders = showRowHeaders + self.showColHeaders = showColHeaders + self.showRowStripes = showRowStripes + self.showColStripes = showColStripes + self.showLastColumn = showLastColumn + + +class MemberList(Serialisable): + + tagname = "members" + + level = Integer(allow_none=True) + member = NestedSequence(expected_type=String, attribute="name") + + __elements__ = ('member',) + + def __init__(self, + count=None, + level=None, + member=(), + ): + self.level = level + self.member = member + + @property + def count(self): + return len(self.member) + + +class MemberProperty(Serialisable): + + tagname = "mps" + + name = String(allow_none=True) + showCell = Bool(allow_none=True) + showTip = Bool(allow_none=True) + showAsCaption = Bool(allow_none=True) + nameLen = Integer(allow_none=True) + pPos = Integer(allow_none=True) + pLen = Integer(allow_none=True) + level = Integer(allow_none=True) + field = Integer() + + def __init__(self, + name=None, + showCell=None, + showTip=None, + showAsCaption=None, + nameLen=None, + pPos=None, + pLen=None, + level=None, + field=None, + ): + self.name = name + self.showCell = showCell + self.showTip = showTip + self.showAsCaption = showAsCaption + self.nameLen = nameLen + self.pPos = pPos + self.pLen = pLen + self.level = level + self.field = field + + +class PivotHierarchy(Serialisable): + + tagname = "pivotHierarchy" + + outline = Bool() + multipleItemSelectionAllowed = Bool() + subtotalTop = Bool() + showInFieldList = Bool() + dragToRow = Bool() + dragToCol = Bool() + dragToPage = Bool() + dragToData = Bool() + dragOff = Bool() + includeNewItemsInFilter = Bool() + caption = String(allow_none=True) + mps = NestedSequence(expected_type=MemberProperty, count=True) + members = Typed(expected_type=MemberList, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('mps', 'members',) + + def __init__(self, + outline=None, + multipleItemSelectionAllowed=None, + subtotalTop=None, + showInFieldList=None, + dragToRow=None, + dragToCol=None, + dragToPage=None, + dragToData=None, + dragOff=None, + includeNewItemsInFilter=None, + caption=None, + mps=(), + members=None, + extLst=None, + ): + self.outline = outline + self.multipleItemSelectionAllowed = multipleItemSelectionAllowed + self.subtotalTop = subtotalTop + self.showInFieldList = showInFieldList + self.dragToRow = dragToRow + self.dragToCol = dragToCol + self.dragToPage = dragToPage + self.dragToData = dragToData + self.dragOff = dragOff + self.includeNewItemsInFilter = includeNewItemsInFilter + self.caption = caption + self.mps = mps + self.members = members + self.extLst = extLst + + +class Reference(Serialisable): + + tagname = "reference" + + field = Integer(allow_none=True) + selected = Bool(allow_none=True) + byPosition = Bool(allow_none=True) + relative = Bool(allow_none=True) + defaultSubtotal = Bool(allow_none=True) + sumSubtotal = Bool(allow_none=True) + countASubtotal = Bool(allow_none=True) + avgSubtotal = Bool(allow_none=True) + maxSubtotal = Bool(allow_none=True) + minSubtotal = Bool(allow_none=True) + productSubtotal = Bool(allow_none=True) + countSubtotal = Bool(allow_none=True) + stdDevSubtotal = Bool(allow_none=True) + stdDevPSubtotal = Bool(allow_none=True) + varSubtotal = Bool(allow_none=True) + varPSubtotal = Bool(allow_none=True) + x = Sequence(expected_type=Index) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('x',) + + def __init__(self, + field=None, + count=None, + selected=None, + byPosition=None, + relative=None, + defaultSubtotal=None, + sumSubtotal=None, + countASubtotal=None, + avgSubtotal=None, + maxSubtotal=None, + minSubtotal=None, + productSubtotal=None, + countSubtotal=None, + stdDevSubtotal=None, + stdDevPSubtotal=None, + varSubtotal=None, + varPSubtotal=None, + x=(), + extLst=None, + ): + self.field = field + self.selected = selected + self.byPosition = byPosition + self.relative = relative + self.defaultSubtotal = defaultSubtotal + self.sumSubtotal = sumSubtotal + self.countASubtotal = countASubtotal + self.avgSubtotal = avgSubtotal + self.maxSubtotal = maxSubtotal + self.minSubtotal = minSubtotal + self.productSubtotal = productSubtotal + self.countSubtotal = countSubtotal + self.stdDevSubtotal = stdDevSubtotal + self.stdDevPSubtotal = stdDevPSubtotal + self.varSubtotal = varSubtotal + self.varPSubtotal = varPSubtotal + self.x = x + + + @property + def count(self): + return len(self.field) + + +class PivotArea(Serialisable): + + tagname = "pivotArea" + + references = NestedSequence(expected_type=Reference, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + field = Integer(allow_none=True) + type = NoneSet(values=(['normal', 'data', 'all', 'origin', 'button', + 'topEnd', 'topRight'])) + dataOnly = Bool(allow_none=True) + labelOnly = Bool(allow_none=True) + grandRow = Bool(allow_none=True) + grandCol = Bool(allow_none=True) + cacheIndex = Bool(allow_none=True) + outline = Bool(allow_none=True) + offset = String(allow_none=True) + collapsedLevelsAreSubtotals = Bool(allow_none=True) + axis = NoneSet(values=(['axisRow', 'axisCol', 'axisPage', 'axisValues'])) + fieldPosition = Integer(allow_none=True) + + __elements__ = ('references',) + + def __init__(self, + references=(), + extLst=None, + field=None, + type="normal", + dataOnly=True, + labelOnly=None, + grandRow=None, + grandCol=None, + cacheIndex=None, + outline=True, + offset=None, + collapsedLevelsAreSubtotals=None, + axis=None, + fieldPosition=None, + ): + self.references = references + self.extLst = extLst + self.field = field + self.type = type + self.dataOnly = dataOnly + self.labelOnly = labelOnly + self.grandRow = grandRow + self.grandCol = grandCol + self.cacheIndex = cacheIndex + self.outline = outline + self.offset = offset + self.collapsedLevelsAreSubtotals = collapsedLevelsAreSubtotals + self.axis = axis + self.fieldPosition = fieldPosition + + +class ChartFormat(Serialisable): + + tagname = "chartFormat" + + chart = Integer() + format = Integer() + series = Bool() + pivotArea = Typed(expected_type=PivotArea, ) + + __elements__ = ('pivotArea',) + + def __init__(self, + chart=None, + format=None, + series=None, + pivotArea=None, + ): + self.chart = chart + self.format = format + self.series = series + self.pivotArea = pivotArea + + +class ConditionalFormat(Serialisable): + + tagname = "conditionalFormat" + + scope = Set(values=(['selection', 'data', 'field'])) + type = NoneSet(values=(['all', 'row', 'column'])) + priority = Integer() + pivotAreas = NestedSequence(expected_type=PivotArea) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('pivotAreas',) + + def __init__(self, + scope="selection", + type=None, + priority=None, + pivotAreas=(), + extLst=None, + ): + self.scope = scope + self.type = type + self.priority = priority + self.pivotAreas = pivotAreas + self.extLst = extLst + + +class ConditionalFormatList(Serialisable): + + tagname = "conditionalFormats" + + conditionalFormat = Sequence(expected_type=ConditionalFormat) + + __attrs__ = ("count",) + + def __init__(self, conditionalFormat=(), count=None): + self.conditionalFormat = conditionalFormat + + + def by_priority(self): + """ + Return a dictionary of format objects keyed by (field id and format property). + This can be used to map the formats to field but also to dedupe to match + worksheet definitions which are grouped by cell range + """ + + fmts = {} + for fmt in self.conditionalFormat: + for area in fmt.pivotAreas: + for ref in area.references: + for field in ref.x: + key = (field.v, fmt.priority) + fmts[key] = fmt + + return fmts + + + def _dedupe(self): + """ + Group formats by field index and priority. + Sorted to match sorting and grouping for corresponding worksheet formats + + The implemtenters notes contain significant deviance from the OOXML + specification, in particular how conditional formats in tables relate to + those defined in corresponding worksheets and how to determine which + format applies to which fields. + + There are some magical interdependencies: + + * Every pivot table fmt must have a worksheet cxf with the same priority. + + * In the reference part the field 4294967294 refers to a data field, the + spec says -2 + + * Data fields are referenced by the 0-index reference.x.v value + + Things are made more complicated by the fact that field items behave + diffently if the parent is a reference or shared item: "In Office if the + parent is the reference element, then restrictions of this value are + defined by reference@field. If the parent is the tables element, then + this value specifies the index into the table tag position in @url." + Yeah, right! + """ + fmts = self.by_priority() + # sort by priority in order, keeping the highest numerical priority, least when + # actually applied + # this is not documented but it's what Excel is happy with + fmts = {field:fmt for (field, priority), fmt in sorted(fmts.items(), reverse=True)} + #fmts = {field:fmt for (field, priority), fmt in fmts.items()} + if fmts: + self.conditionalFormat = list(fmts.values()) + + + @property + def count(self): + return len(self.conditionalFormat) + + + def to_tree(self, tagname=None): + self._dedupe() + return super().to_tree(tagname) + + +class Format(Serialisable): + + tagname = "format" + + action = NoneSet(values=(['blank', 'formatting', 'drill', 'formula'])) + dxfId = Integer(allow_none=True) + pivotArea = Typed(expected_type=PivotArea, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('pivotArea',) + + def __init__(self, + action="formatting", + dxfId=None, + pivotArea=None, + extLst=None, + ): + self.action = action + self.dxfId = dxfId + self.pivotArea = pivotArea + self.extLst = extLst + + +class DataField(Serialisable): + + tagname = "dataField" + + name = String(allow_none=True) + fld = Integer() + subtotal = Set(values=(['average', 'count', 'countNums', 'max', 'min', + 'product', 'stdDev', 'stdDevp', 'sum', 'var', 'varp'])) + showDataAs = Set(values=(['normal', 'difference', 'percent', + 'percentDiff', 'runTotal', 'percentOfRow', 'percentOfCol', + 'percentOfTotal', 'index'])) + baseField = Integer() + baseItem = Integer() + numFmtId = Integer(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + + def __init__(self, + name=None, + fld=None, + subtotal="sum", + showDataAs="normal", + baseField=-1, + baseItem=1048832, + numFmtId=None, + extLst=None, + ): + self.name = name + self.fld = fld + self.subtotal = subtotal + self.showDataAs = showDataAs + self.baseField = baseField + self.baseItem = baseItem + self.numFmtId = numFmtId + self.extLst = extLst + + +class PageField(Serialisable): + + tagname = "pageField" + + fld = Integer() + item = Integer(allow_none=True) + hier = Integer(allow_none=True) + name = String(allow_none=True) + cap = String(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + fld=None, + item=None, + hier=None, + name=None, + cap=None, + extLst=None, + ): + self.fld = fld + self.item = item + self.hier = hier + self.name = name + self.cap = cap + self.extLst = extLst + + +class RowColItem(Serialisable): + + tagname = "i" + + t = Set(values=(['data', 'default', 'sum', 'countA', 'avg', 'max', 'min', + 'product', 'count', 'stdDev', 'stdDevP', 'var', 'varP', 'grand', + 'blank'])) + r = Integer() + i = Integer() + x = Sequence(expected_type=Index, attribute="v") + + __elements__ = ('x',) + + def __init__(self, + t="data", + r=0, + i=0, + x=(), + ): + self.t = t + self.r = r + self.i = i + self.x = x + + +class RowColField(Serialisable): + + tagname = "field" + + x = Integer() + + def __init__(self, + x=None, + ): + self.x = x + + +class AutoSortScope(Serialisable): + + pivotArea = Typed(expected_type=PivotArea, ) + + __elements__ = ('pivotArea',) + + def __init__(self, + pivotArea=None, + ): + self.pivotArea = pivotArea + + +class FieldItem(Serialisable): + + tagname = "item" + + n = String(allow_none=True) + t = Set(values=(['data', 'default', 'sum', 'countA', 'avg', 'max', 'min', + 'product', 'count', 'stdDev', 'stdDevP', 'var', 'varP', 'grand', + 'blank'])) + h = Bool(allow_none=True) + s = Bool(allow_none=True) + sd = Bool(allow_none=True) + f = Bool(allow_none=True) + m = Bool(allow_none=True) + c = Bool(allow_none=True) + x = Integer(allow_none=True) + d = Bool(allow_none=True) + e = Bool(allow_none=True) + + def __init__(self, + n=None, + t="data", + h=None, + s=None, + sd=True, + f=None, + m=None, + c=None, + x=None, + d=None, + e=None, + ): + self.n = n + self.t = t + self.h = h + self.s = s + self.sd = sd + self.f = f + self.m = m + self.c = c + self.x = x + self.d = d + self.e = e + + +class PivotField(Serialisable): + + tagname = "pivotField" + + items = NestedSequence(expected_type=FieldItem, count=True) + autoSortScope = Typed(expected_type=AutoSortScope, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + name = String(allow_none=True) + axis = NoneSet(values=(['axisRow', 'axisCol', 'axisPage', 'axisValues'])) + dataField = Bool(allow_none=True) + subtotalCaption = String(allow_none=True) + showDropDowns = Bool(allow_none=True) + hiddenLevel = Bool(allow_none=True) + uniqueMemberProperty = String(allow_none=True) + compact = Bool(allow_none=True) + allDrilled = Bool(allow_none=True) + numFmtId = Integer(allow_none=True) + outline = Bool(allow_none=True) + subtotalTop = Bool(allow_none=True) + dragToRow = Bool(allow_none=True) + dragToCol = Bool(allow_none=True) + multipleItemSelectionAllowed = Bool(allow_none=True) + dragToPage = Bool(allow_none=True) + dragToData = Bool(allow_none=True) + dragOff = Bool(allow_none=True) + showAll = Bool(allow_none=True) + insertBlankRow = Bool(allow_none=True) + serverField = Bool(allow_none=True) + insertPageBreak = Bool(allow_none=True) + autoShow = Bool(allow_none=True) + topAutoShow = Bool(allow_none=True) + hideNewItems = Bool(allow_none=True) + measureFilter = Bool(allow_none=True) + includeNewItemsInFilter = Bool(allow_none=True) + itemPageCount = Integer(allow_none=True) + sortType = Set(values=(['manual', 'ascending', 'descending'])) + dataSourceSort = Bool(allow_none=True) + nonAutoSortDefault = Bool(allow_none=True) + rankBy = Integer(allow_none=True) + defaultSubtotal = Bool(allow_none=True) + sumSubtotal = Bool(allow_none=True) + countASubtotal = Bool(allow_none=True) + avgSubtotal = Bool(allow_none=True) + maxSubtotal = Bool(allow_none=True) + minSubtotal = Bool(allow_none=True) + productSubtotal = Bool(allow_none=True) + countSubtotal = Bool(allow_none=True) + stdDevSubtotal = Bool(allow_none=True) + stdDevPSubtotal = Bool(allow_none=True) + varSubtotal = Bool(allow_none=True) + varPSubtotal = Bool(allow_none=True) + showPropCell = Bool(allow_none=True) + showPropTip = Bool(allow_none=True) + showPropAsCaption = Bool(allow_none=True) + defaultAttributeDrillState = Bool(allow_none=True) + + __elements__ = ('items', 'autoSortScope',) + + def __init__(self, + items=(), + autoSortScope=None, + name=None, + axis=None, + dataField=None, + subtotalCaption=None, + showDropDowns=True, + hiddenLevel=None, + uniqueMemberProperty=None, + compact=True, + allDrilled=None, + numFmtId=None, + outline=True, + subtotalTop=True, + dragToRow=True, + dragToCol=True, + multipleItemSelectionAllowed=None, + dragToPage=True, + dragToData=True, + dragOff=True, + showAll=True, + insertBlankRow=None, + serverField=None, + insertPageBreak=None, + autoShow=None, + topAutoShow=True, + hideNewItems=None, + measureFilter=None, + includeNewItemsInFilter=None, + itemPageCount=10, + sortType="manual", + dataSourceSort=None, + nonAutoSortDefault=None, + rankBy=None, + defaultSubtotal=True, + sumSubtotal=None, + countASubtotal=None, + avgSubtotal=None, + maxSubtotal=None, + minSubtotal=None, + productSubtotal=None, + countSubtotal=None, + stdDevSubtotal=None, + stdDevPSubtotal=None, + varSubtotal=None, + varPSubtotal=None, + showPropCell=None, + showPropTip=None, + showPropAsCaption=None, + defaultAttributeDrillState=None, + extLst=None, + ): + self.items = items + self.autoSortScope = autoSortScope + self.name = name + self.axis = axis + self.dataField = dataField + self.subtotalCaption = subtotalCaption + self.showDropDowns = showDropDowns + self.hiddenLevel = hiddenLevel + self.uniqueMemberProperty = uniqueMemberProperty + self.compact = compact + self.allDrilled = allDrilled + self.numFmtId = numFmtId + self.outline = outline + self.subtotalTop = subtotalTop + self.dragToRow = dragToRow + self.dragToCol = dragToCol + self.multipleItemSelectionAllowed = multipleItemSelectionAllowed + self.dragToPage = dragToPage + self.dragToData = dragToData + self.dragOff = dragOff + self.showAll = showAll + self.insertBlankRow = insertBlankRow + self.serverField = serverField + self.insertPageBreak = insertPageBreak + self.autoShow = autoShow + self.topAutoShow = topAutoShow + self.hideNewItems = hideNewItems + self.measureFilter = measureFilter + self.includeNewItemsInFilter = includeNewItemsInFilter + self.itemPageCount = itemPageCount + self.sortType = sortType + self.dataSourceSort = dataSourceSort + self.nonAutoSortDefault = nonAutoSortDefault + self.rankBy = rankBy + self.defaultSubtotal = defaultSubtotal + self.sumSubtotal = sumSubtotal + self.countASubtotal = countASubtotal + self.avgSubtotal = avgSubtotal + self.maxSubtotal = maxSubtotal + self.minSubtotal = minSubtotal + self.productSubtotal = productSubtotal + self.countSubtotal = countSubtotal + self.stdDevSubtotal = stdDevSubtotal + self.stdDevPSubtotal = stdDevPSubtotal + self.varSubtotal = varSubtotal + self.varPSubtotal = varPSubtotal + self.showPropCell = showPropCell + self.showPropTip = showPropTip + self.showPropAsCaption = showPropAsCaption + self.defaultAttributeDrillState = defaultAttributeDrillState + + +class Location(Serialisable): + + tagname = "location" + + ref = String() + firstHeaderRow = Integer() + firstDataRow = Integer() + firstDataCol = Integer() + rowPageCount = Integer(allow_none=True) + colPageCount = Integer(allow_none=True) + + def __init__(self, + ref=None, + firstHeaderRow=None, + firstDataRow=None, + firstDataCol=None, + rowPageCount=None, + colPageCount=None, + ): + self.ref = ref + self.firstHeaderRow = firstHeaderRow + self.firstDataRow = firstDataRow + self.firstDataCol = firstDataCol + self.rowPageCount = rowPageCount + self.colPageCount = colPageCount + + +class TableDefinition(Serialisable): + + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" + rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable" + _id = 1 + _path = "/xl/pivotTables/pivotTable{0}.xml" + + tagname = "pivotTableDefinition" + cache = None + + name = String() + cacheId = Integer() + dataOnRows = Bool() + dataPosition = Integer(allow_none=True) + dataCaption = String() + grandTotalCaption = String(allow_none=True) + errorCaption = String(allow_none=True) + showError = Bool() + missingCaption = String(allow_none=True) + showMissing = Bool() + pageStyle = String(allow_none=True) + pivotTableStyle = String(allow_none=True) + vacatedStyle = String(allow_none=True) + tag = String(allow_none=True) + updatedVersion = Integer() + minRefreshableVersion = Integer() + asteriskTotals = Bool() + showItems = Bool() + editData = Bool() + disableFieldList = Bool() + showCalcMbrs = Bool() + visualTotals = Bool() + showMultipleLabel = Bool() + showDataDropDown = Bool() + showDrill = Bool() + printDrill = Bool() + showMemberPropertyTips = Bool() + showDataTips = Bool() + enableWizard = Bool() + enableDrill = Bool() + enableFieldProperties = Bool() + preserveFormatting = Bool() + useAutoFormatting = Bool() + pageWrap = Integer() + pageOverThenDown = Bool() + subtotalHiddenItems = Bool() + rowGrandTotals = Bool() + colGrandTotals = Bool() + fieldPrintTitles = Bool() + itemPrintTitles = Bool() + mergeItem = Bool() + showDropZones = Bool() + createdVersion = Integer() + indent = Integer() + showEmptyRow = Bool() + showEmptyCol = Bool() + showHeaders = Bool() + compact = Bool() + outline = Bool() + outlineData = Bool() + compactData = Bool() + published = Bool() + gridDropZones = Bool() + immersive = Bool() + multipleFieldFilters = Bool() + chartFormat = Integer() + rowHeaderCaption = String(allow_none=True) + colHeaderCaption = String(allow_none=True) + fieldListSortAscending = Bool() + mdxSubqueries = Bool() + customListSort = Bool(allow_none=True) + autoFormatId = Integer(allow_none=True) + applyNumberFormats = Bool() + applyBorderFormats = Bool() + applyFontFormats = Bool() + applyPatternFormats = Bool() + applyAlignmentFormats = Bool() + applyWidthHeightFormats = Bool() + location = Typed(expected_type=Location, ) + pivotFields = NestedSequence(expected_type=PivotField, count=True) + rowFields = NestedSequence(expected_type=RowColField, count=True) + rowItems = NestedSequence(expected_type=RowColItem, count=True) + colFields = NestedSequence(expected_type=RowColField, count=True) + colItems = NestedSequence(expected_type=RowColItem, count=True) + pageFields = NestedSequence(expected_type=PageField, count=True) + dataFields = NestedSequence(expected_type=DataField, count=True) + formats = NestedSequence(expected_type=Format, count=True) + conditionalFormats = Typed(expected_type=ConditionalFormatList, allow_none=True) + chartFormats = NestedSequence(expected_type=ChartFormat, count=True) + pivotHierarchies = NestedSequence(expected_type=PivotHierarchy, count=True) + pivotTableStyleInfo = Typed(expected_type=PivotTableStyle, allow_none=True) + filters = NestedSequence(expected_type=PivotFilter, count=True) + rowHierarchiesUsage = Typed(expected_type=RowHierarchiesUsage, allow_none=True) + colHierarchiesUsage = Typed(expected_type=ColHierarchiesUsage, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + id = Relation() + + __elements__ = ('location', 'pivotFields', 'rowFields', 'rowItems', + 'colFields', 'colItems', 'pageFields', 'dataFields', 'formats', + 'conditionalFormats', 'chartFormats', 'pivotHierarchies', + 'pivotTableStyleInfo', 'filters', 'rowHierarchiesUsage', + 'colHierarchiesUsage',) + + def __init__(self, + name=None, + cacheId=None, + dataOnRows=False, + dataPosition=None, + dataCaption=None, + grandTotalCaption=None, + errorCaption=None, + showError=False, + missingCaption=None, + showMissing=True, + pageStyle=None, + pivotTableStyle=None, + vacatedStyle=None, + tag=None, + updatedVersion=0, + minRefreshableVersion=0, + asteriskTotals=False, + showItems=True, + editData=False, + disableFieldList=False, + showCalcMbrs=True, + visualTotals=True, + showMultipleLabel=True, + showDataDropDown=True, + showDrill=True, + printDrill=False, + showMemberPropertyTips=True, + showDataTips=True, + enableWizard=True, + enableDrill=True, + enableFieldProperties=True, + preserveFormatting=True, + useAutoFormatting=False, + pageWrap=0, + pageOverThenDown=False, + subtotalHiddenItems=False, + rowGrandTotals=True, + colGrandTotals=True, + fieldPrintTitles=False, + itemPrintTitles=False, + mergeItem=False, + showDropZones=True, + createdVersion=0, + indent=1, + showEmptyRow=False, + showEmptyCol=False, + showHeaders=True, + compact=True, + outline=False, + outlineData=False, + compactData=True, + published=False, + gridDropZones=False, + immersive=True, + multipleFieldFilters=None, + chartFormat=0, + rowHeaderCaption=None, + colHeaderCaption=None, + fieldListSortAscending=None, + mdxSubqueries=None, + customListSort=None, + autoFormatId=None, + applyNumberFormats=False, + applyBorderFormats=False, + applyFontFormats=False, + applyPatternFormats=False, + applyAlignmentFormats=False, + applyWidthHeightFormats=False, + location=None, + pivotFields=(), + rowFields=(), + rowItems=(), + colFields=(), + colItems=(), + pageFields=(), + dataFields=(), + formats=(), + conditionalFormats=None, + chartFormats=(), + pivotHierarchies=(), + pivotTableStyleInfo=None, + filters=(), + rowHierarchiesUsage=None, + colHierarchiesUsage=None, + extLst=None, + id=None, + ): + self.name = name + self.cacheId = cacheId + self.dataOnRows = dataOnRows + self.dataPosition = dataPosition + self.dataCaption = dataCaption + self.grandTotalCaption = grandTotalCaption + self.errorCaption = errorCaption + self.showError = showError + self.missingCaption = missingCaption + self.showMissing = showMissing + self.pageStyle = pageStyle + self.pivotTableStyle = pivotTableStyle + self.vacatedStyle = vacatedStyle + self.tag = tag + self.updatedVersion = updatedVersion + self.minRefreshableVersion = minRefreshableVersion + self.asteriskTotals = asteriskTotals + self.showItems = showItems + self.editData = editData + self.disableFieldList = disableFieldList + self.showCalcMbrs = showCalcMbrs + self.visualTotals = visualTotals + self.showMultipleLabel = showMultipleLabel + self.showDataDropDown = showDataDropDown + self.showDrill = showDrill + self.printDrill = printDrill + self.showMemberPropertyTips = showMemberPropertyTips + self.showDataTips = showDataTips + self.enableWizard = enableWizard + self.enableDrill = enableDrill + self.enableFieldProperties = enableFieldProperties + self.preserveFormatting = preserveFormatting + self.useAutoFormatting = useAutoFormatting + self.pageWrap = pageWrap + self.pageOverThenDown = pageOverThenDown + self.subtotalHiddenItems = subtotalHiddenItems + self.rowGrandTotals = rowGrandTotals + self.colGrandTotals = colGrandTotals + self.fieldPrintTitles = fieldPrintTitles + self.itemPrintTitles = itemPrintTitles + self.mergeItem = mergeItem + self.showDropZones = showDropZones + self.createdVersion = createdVersion + self.indent = indent + self.showEmptyRow = showEmptyRow + self.showEmptyCol = showEmptyCol + self.showHeaders = showHeaders + self.compact = compact + self.outline = outline + self.outlineData = outlineData + self.compactData = compactData + self.published = published + self.gridDropZones = gridDropZones + self.immersive = immersive + self.multipleFieldFilters = multipleFieldFilters + self.chartFormat = chartFormat + self.rowHeaderCaption = rowHeaderCaption + self.colHeaderCaption = colHeaderCaption + self.fieldListSortAscending = fieldListSortAscending + self.mdxSubqueries = mdxSubqueries + self.customListSort = customListSort + self.autoFormatId = autoFormatId + self.applyNumberFormats = applyNumberFormats + self.applyBorderFormats = applyBorderFormats + self.applyFontFormats = applyFontFormats + self.applyPatternFormats = applyPatternFormats + self.applyAlignmentFormats = applyAlignmentFormats + self.applyWidthHeightFormats = applyWidthHeightFormats + self.location = location + self.pivotFields = pivotFields + self.rowFields = rowFields + self.rowItems = rowItems + self.colFields = colFields + self.colItems = colItems + self.pageFields = pageFields + self.dataFields = dataFields + self.formats = formats + self.conditionalFormats = conditionalFormats + self.conditionalFormats = None + self.chartFormats = chartFormats + self.pivotHierarchies = pivotHierarchies + self.pivotTableStyleInfo = pivotTableStyleInfo + self.filters = filters + self.rowHierarchiesUsage = rowHierarchiesUsage + self.colHierarchiesUsage = colHierarchiesUsage + self.extLst = extLst + self.id = id + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def path(self): + return self._path.format(self._id) + + + def _write(self, archive, manifest): + """ + Add to zipfile and update manifest + """ + self._write_rels(archive, manifest) + xml = tostring(self.to_tree()) + archive.writestr(self.path[1:], xml) + manifest.append(self) + + + def _write_rels(self, archive, manifest): + """ + Write the relevant child objects and add links + """ + if self.cache is None: + return + + rels = RelationshipList() + r = Relationship(Type=self.cache.rel_type, Target=self.cache.path) + rels.append(r) + self.id = r.id + if self.cache.path[1:] not in archive.namelist(): + self.cache._write(archive, manifest) + + path = get_rels_path(self.path) + xml = tostring(rels.to_tree()) + archive.writestr(path[1:], xml) + + + def formatted_fields(self): + """Map fields to associated conditional formats by priority""" + if not self.conditionalFormats: + return {} + fields = defaultdict(list) + for idx, prio in self.conditionalFormats.by_priority(): + name = self.dataFields[idx].name + fields[name].append(prio) + return fields + + + @property + def summary(self): + """ + Provide a simplified summary of the table + """ + + return f"{self.name} {dict(self.location)}" diff --git a/.venv/lib/python3.12/site-packages/openpyxl/reader/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/reader/__init__.py new file mode 100644 index 00000000..ab6cdead --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/reader/__init__.py @@ -0,0 +1 @@ +# Copyright (c) 2010-2024 openpyxl diff --git a/.venv/lib/python3.12/site-packages/openpyxl/reader/drawings.py b/.venv/lib/python3.12/site-packages/openpyxl/reader/drawings.py new file mode 100644 index 00000000..caaa8570 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/reader/drawings.py @@ -0,0 +1,71 @@ + +# Copyright (c) 2010-2024 openpyxl + + +from io import BytesIO +from warnings import warn + +from openpyxl.xml.functions import fromstring +from openpyxl.xml.constants import IMAGE_NS +from openpyxl.packaging.relationship import ( + get_rel, + get_rels_path, + get_dependents, +) +from openpyxl.drawing.spreadsheet_drawing import SpreadsheetDrawing +from openpyxl.drawing.image import Image, PILImage +from openpyxl.chart.chartspace import ChartSpace +from openpyxl.chart.reader import read_chart + + +def find_images(archive, path): + """ + Given the path to a drawing file extract charts and images + + Ignore errors due to unsupported parts of DrawingML + """ + + src = archive.read(path) + tree = fromstring(src) + try: + drawing = SpreadsheetDrawing.from_tree(tree) + except TypeError: + warn("DrawingML support is incomplete and limited to charts and images only. Shapes and drawings will be lost.") + return [], [] + + rels_path = get_rels_path(path) + deps = [] + if rels_path in archive.namelist(): + deps = get_dependents(archive, rels_path) + + charts = [] + for rel in drawing._chart_rels: + try: + cs = get_rel(archive, deps, rel.id, ChartSpace) + except TypeError as e: + warn(f"Unable to read chart {rel.id} from {path} {e}") + continue + chart = read_chart(cs) + chart.anchor = rel.anchor + charts.append(chart) + + images = [] + if not PILImage: # Pillow not installed, drop images + return charts, images + + for rel in drawing._blip_rels: + dep = deps.get(rel.embed) + if dep.Type == IMAGE_NS: + try: + image = Image(BytesIO(archive.read(dep.target))) + except OSError: + msg = "The image {0} will be removed because it cannot be read".format(dep.target) + warn(msg) + continue + if image.format.upper() == "WMF": # cannot save + msg = "{0} image format is not supported so the image is being dropped".format(image.format) + warn(msg) + continue + image.anchor = rel.anchor + images.append(image) + return charts, images diff --git a/.venv/lib/python3.12/site-packages/openpyxl/reader/excel.py b/.venv/lib/python3.12/site-packages/openpyxl/reader/excel.py new file mode 100644 index 00000000..dfd8eeac --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/reader/excel.py @@ -0,0 +1,349 @@ +# Copyright (c) 2010-2024 openpyxl + + +"""Read an xlsx file into Python""" + +# Python stdlib imports +from zipfile import ZipFile, ZIP_DEFLATED +from io import BytesIO +import os.path +import warnings + +from openpyxl.pivot.table import TableDefinition + +# Allow blanket setting of KEEP_VBA for testing +try: + from ..tests import KEEP_VBA +except ImportError: + KEEP_VBA = False + +# package imports +from openpyxl.utils.exceptions import InvalidFileException +from openpyxl.xml.constants import ( + ARC_CORE, + ARC_CUSTOM, + ARC_CONTENT_TYPES, + ARC_WORKBOOK, + ARC_THEME, + COMMENTS_NS, + SHARED_STRINGS, + XLTM, + XLTX, + XLSM, + XLSX, +) +from openpyxl.cell import MergedCell +from openpyxl.comments.comment_sheet import CommentSheet + +from .strings import read_string_table, read_rich_text +from .workbook import WorkbookParser +from openpyxl.styles.stylesheet import apply_stylesheet + +from openpyxl.packaging.core import DocumentProperties +from openpyxl.packaging.custom import CustomPropertyList +from openpyxl.packaging.manifest import Manifest, Override + +from openpyxl.packaging.relationship import ( + RelationshipList, + get_dependents, + get_rels_path, +) + +from openpyxl.worksheet._read_only import ReadOnlyWorksheet +from openpyxl.worksheet._reader import WorksheetReader +from openpyxl.chartsheet import Chartsheet +from openpyxl.worksheet.table import Table +from openpyxl.drawing.spreadsheet_drawing import SpreadsheetDrawing + +from openpyxl.xml.functions import fromstring + +from .drawings import find_images + + +SUPPORTED_FORMATS = ('.xlsx', '.xlsm', '.xltx', '.xltm') + + +def _validate_archive(filename): + """ + Does a first check whether filename is a string or a file-like + object. If it is a string representing a filename, a check is done + for supported formats by checking the given file-extension. If the + file-extension is not in SUPPORTED_FORMATS an InvalidFileException + will raised. Otherwise the filename (resp. file-like object) will + forwarded to zipfile.ZipFile returning a ZipFile-Instance. + """ + is_file_like = hasattr(filename, 'read') + if not is_file_like: + file_format = os.path.splitext(filename)[-1].lower() + if file_format not in SUPPORTED_FORMATS: + if file_format == '.xls': + msg = ('openpyxl does not support the old .xls file format, ' + 'please use xlrd to read this file, or convert it to ' + 'the more recent .xlsx file format.') + elif file_format == '.xlsb': + msg = ('openpyxl does not support binary format .xlsb, ' + 'please convert this file to .xlsx format if you want ' + 'to open it with openpyxl') + else: + msg = ('openpyxl does not support %s file format, ' + 'please check you can open ' + 'it with Excel first. ' + 'Supported formats are: %s') % (file_format, + ','.join(SUPPORTED_FORMATS)) + raise InvalidFileException(msg) + + archive = ZipFile(filename, 'r') + return archive + + +def _find_workbook_part(package): + workbook_types = [XLTM, XLTX, XLSM, XLSX] + for ct in workbook_types: + part = package.find(ct) + if part: + return part + + # some applications reassign the default for application/xml + defaults = {p.ContentType for p in package.Default} + workbook_type = defaults & set(workbook_types) + if workbook_type: + return Override("/" + ARC_WORKBOOK, workbook_type.pop()) + + raise IOError("File contains no valid workbook part") + + +class ExcelReader: + + """ + Read an Excel package and dispatch the contents to the relevant modules + """ + + def __init__(self, fn, read_only=False, keep_vba=KEEP_VBA, + data_only=False, keep_links=True, rich_text=False): + self.archive = _validate_archive(fn) + self.valid_files = self.archive.namelist() + self.read_only = read_only + self.keep_vba = keep_vba + self.data_only = data_only + self.keep_links = keep_links + self.rich_text = rich_text + self.shared_strings = [] + + + def read_manifest(self): + src = self.archive.read(ARC_CONTENT_TYPES) + root = fromstring(src) + self.package = Manifest.from_tree(root) + + + def read_strings(self): + ct = self.package.find(SHARED_STRINGS) + reader = read_string_table + if self.rich_text: + reader = read_rich_text + if ct is not None: + strings_path = ct.PartName[1:] + with self.archive.open(strings_path,) as src: + self.shared_strings = reader(src) + + + def read_workbook(self): + wb_part = _find_workbook_part(self.package) + self.parser = WorkbookParser(self.archive, wb_part.PartName[1:], keep_links=self.keep_links) + self.parser.parse() + wb = self.parser.wb + wb._sheets = [] + wb._data_only = self.data_only + wb._read_only = self.read_only + wb.template = wb_part.ContentType in (XLTX, XLTM) + + # If are going to preserve the vba then attach a copy of the archive to the + # workbook so that is available for the save. + if self.keep_vba: + wb.vba_archive = ZipFile(BytesIO(), 'a', ZIP_DEFLATED) + for name in self.valid_files: + wb.vba_archive.writestr(name, self.archive.read(name)) + + if self.read_only: + wb._archive = self.archive + + self.wb = wb + + + def read_properties(self): + if ARC_CORE in self.valid_files: + src = fromstring(self.archive.read(ARC_CORE)) + self.wb.properties = DocumentProperties.from_tree(src) + + + def read_custom(self): + if ARC_CUSTOM in self.valid_files: + src = fromstring(self.archive.read(ARC_CUSTOM)) + self.wb.custom_doc_props = CustomPropertyList.from_tree(src) + + + def read_theme(self): + if ARC_THEME in self.valid_files: + self.wb.loaded_theme = self.archive.read(ARC_THEME) + + + def read_chartsheet(self, sheet, rel): + sheet_path = rel.target + rels_path = get_rels_path(sheet_path) + rels = [] + if rels_path in self.valid_files: + rels = get_dependents(self.archive, rels_path) + + with self.archive.open(sheet_path, "r") as src: + xml = src.read() + node = fromstring(xml) + cs = Chartsheet.from_tree(node) + cs._parent = self.wb + cs.title = sheet.name + self.wb._add_sheet(cs) + + drawings = rels.find(SpreadsheetDrawing._rel_type) + for rel in drawings: + charts, images = find_images(self.archive, rel.target) + for c in charts: + cs.add_chart(c) + + + def read_worksheets(self): + comment_warning = """Cell '{0}':{1} is part of a merged range but has a comment which will be removed because merged cells cannot contain any data.""" + for sheet, rel in self.parser.find_sheets(): + if rel.target not in self.valid_files: + continue + + if "chartsheet" in rel.Type: + self.read_chartsheet(sheet, rel) + continue + + rels_path = get_rels_path(rel.target) + rels = RelationshipList() + if rels_path in self.valid_files: + rels = get_dependents(self.archive, rels_path) + + if self.read_only: + ws = ReadOnlyWorksheet(self.wb, sheet.name, rel.target, self.shared_strings) + ws.sheet_state = sheet.state + self.wb._sheets.append(ws) + continue + else: + fh = self.archive.open(rel.target) + ws = self.wb.create_sheet(sheet.name) + ws._rels = rels + ws_parser = WorksheetReader(ws, fh, self.shared_strings, self.data_only, self.rich_text) + ws_parser.bind_all() + fh.close() + + # assign any comments to cells + for r in rels.find(COMMENTS_NS): + src = self.archive.read(r.target) + comment_sheet = CommentSheet.from_tree(fromstring(src)) + for ref, comment in comment_sheet.comments: + try: + ws[ref].comment = comment + except AttributeError: + c = ws[ref] + if isinstance(c, MergedCell): + warnings.warn(comment_warning.format(ws.title, c.coordinate)) + continue + + # preserve link to VML file if VBA + if self.wb.vba_archive and ws.legacy_drawing: + ws.legacy_drawing = rels.get(ws.legacy_drawing).target + else: + ws.legacy_drawing = None + + for t in ws_parser.tables: + src = self.archive.read(t) + xml = fromstring(src) + table = Table.from_tree(xml) + ws.add_table(table) + + drawings = rels.find(SpreadsheetDrawing._rel_type) + for rel in drawings: + charts, images = find_images(self.archive, rel.target) + for c in charts: + ws.add_chart(c, c.anchor) + for im in images: + ws.add_image(im, im.anchor) + + pivot_rel = rels.find(TableDefinition.rel_type) + pivot_caches = self.parser.pivot_caches + for r in pivot_rel: + pivot_path = r.Target + src = self.archive.read(pivot_path) + tree = fromstring(src) + pivot = TableDefinition.from_tree(tree) + pivot.cache = pivot_caches[pivot.cacheId] + ws.add_pivot(pivot) + + ws.sheet_state = sheet.state + + + def read(self): + action = "read manifest" + try: + self.read_manifest() + action = "read strings" + self.read_strings() + action = "read workbook" + self.read_workbook() + action = "read properties" + self.read_properties() + action = "read custom properties" + self.read_custom() + action = "read theme" + self.read_theme() + action = "read stylesheet" + apply_stylesheet(self.archive, self.wb) + action = "read worksheets" + self.read_worksheets() + action = "assign names" + self.parser.assign_names() + if not self.read_only: + self.archive.close() + except ValueError as e: + raise ValueError( + f"Unable to read workbook: could not {action} from {self.archive.filename}.\n" + "This is most probably because the workbook source files contain some invalid XML.\n" + "Please see the exception for more details." + ) from e + + +def load_workbook(filename, read_only=False, keep_vba=KEEP_VBA, + data_only=False, keep_links=True, rich_text=False): + """Open the given filename and return the workbook + + :param filename: the path to open or a file-like object + :type filename: string or a file-like object open in binary mode c.f., :class:`zipfile.ZipFile` + + :param read_only: optimised for reading, content cannot be edited + :type read_only: bool + + :param keep_vba: preserve vba content (this does NOT mean you can use it) + :type keep_vba: bool + + :param data_only: controls whether cells with formulae have either the formula (default) or the value stored the last time Excel read the sheet + :type data_only: bool + + :param keep_links: whether links to external workbooks should be preserved. The default is True + :type keep_links: bool + + :param rich_text: if set to True openpyxl will preserve any rich text formatting in cells. The default is False + :type rich_text: bool + + :rtype: :class:`openpyxl.workbook.Workbook` + + .. note:: + + When using lazy load, all worksheets will be :class:`openpyxl.worksheet.iter_worksheet.IterableWorksheet` + and the returned workbook will be read-only. + + """ + reader = ExcelReader(filename, read_only, keep_vba, + data_only, keep_links, rich_text) + reader.read() + return reader.wb diff --git a/.venv/lib/python3.12/site-packages/openpyxl/reader/strings.py b/.venv/lib/python3.12/site-packages/openpyxl/reader/strings.py new file mode 100644 index 00000000..5168f201 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/reader/strings.py @@ -0,0 +1,44 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.cell.text import Text + +from openpyxl.xml.functions import iterparse +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.cell.rich_text import CellRichText + + +def read_string_table(xml_source): + """Read in all shared strings in the table""" + + strings = [] + STRING_TAG = '{%s}si' % SHEET_MAIN_NS + + for _, node in iterparse(xml_source): + if node.tag == STRING_TAG: + text = Text.from_tree(node).content + text = text.replace('x005F_', '') + node.clear() + + strings.append(text) + + return strings + + +def read_rich_text(xml_source): + """Read in all shared strings in the table""" + + strings = [] + STRING_TAG = '{%s}si' % SHEET_MAIN_NS + + for _, node in iterparse(xml_source): + if node.tag == STRING_TAG: + text = CellRichText.from_tree(node) + if len(text) == 0: + text = '' + elif len(text) == 1 and isinstance(text[0], str): + text = text[0] + node.clear() + + strings.append(text) + + return strings diff --git a/.venv/lib/python3.12/site-packages/openpyxl/reader/workbook.py b/.venv/lib/python3.12/site-packages/openpyxl/reader/workbook.py new file mode 100644 index 00000000..2afbfddb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/reader/workbook.py @@ -0,0 +1,133 @@ +# Copyright (c) 2010-2024 openpyxl + +from warnings import warn + +from openpyxl.xml.functions import fromstring + +from openpyxl.packaging.relationship import ( + get_dependents, + get_rels_path, + get_rel, +) +from openpyxl.packaging.workbook import WorkbookPackage +from openpyxl.workbook import Workbook +from openpyxl.workbook.defined_name import DefinedNameList +from openpyxl.workbook.external_link.external import read_external_link +from openpyxl.pivot.cache import CacheDefinition +from openpyxl.pivot.record import RecordList +from openpyxl.worksheet.print_settings import PrintTitles, PrintArea + +from openpyxl.utils.datetime import CALENDAR_MAC_1904 + + +class WorkbookParser: + + _rels = None + + def __init__(self, archive, workbook_part_name, keep_links=True): + self.archive = archive + self.workbook_part_name = workbook_part_name + self.defined_names = DefinedNameList() + self.wb = Workbook() + self.keep_links = keep_links + self.sheets = [] + + + @property + def rels(self): + if self._rels is None: + self._rels = get_dependents(self.archive, get_rels_path(self.workbook_part_name)).to_dict() + return self._rels + + + def parse(self): + src = self.archive.read(self.workbook_part_name) + node = fromstring(src) + package = WorkbookPackage.from_tree(node) + if package.properties.date1904: + self.wb.epoch = CALENDAR_MAC_1904 + + self.wb.code_name = package.properties.codeName + self.wb.active = package.active + self.wb.views = package.bookViews + self.sheets = package.sheets + self.wb.calculation = package.calcPr + self.caches = package.pivotCaches + + # external links contain cached worksheets and can be very big + if not self.keep_links: + package.externalReferences = [] + + for ext_ref in package.externalReferences: + rel = self.rels.get(ext_ref.id) + self.wb._external_links.append( + read_external_link(self.archive, rel.Target) + ) + + if package.definedNames: + self.defined_names = package.definedNames + + self.wb.security = package.workbookProtection + + + def find_sheets(self): + """ + Find all sheets in the workbook and return the link to the source file. + + Older XLSM files sometimes contain invalid sheet elements. + Warn user when these are removed. + """ + + for sheet in self.sheets: + if not sheet.id: + msg = f"File contains an invalid specification for {0}. This will be removed".format(sheet.name) + warn(msg) + continue + yield sheet, self.rels[sheet.id] + + + def assign_names(self): + """ + Bind defined names and other definitions to worksheets or the workbook + """ + + for idx, names in self.defined_names.by_sheet().items(): + if idx == "global": + self.wb.defined_names = names + continue + + try: + sheet = self.wb._sheets[idx] + except IndexError: + warn(f"Defined names for sheet index {idx} cannot be located") + continue + + for name, defn in names.items(): + reserved = defn.is_reserved + if reserved is None: + sheet.defined_names[name] = defn + + elif reserved == "Print_Titles": + titles = PrintTitles.from_string(defn.value) + sheet._print_rows = titles.rows + sheet._print_cols = titles.cols + elif reserved == "Print_Area": + try: + sheet._print_area = PrintArea.from_string(defn.value) + except TypeError: + warn(f"Print area cannot be set to Defined name: {defn.value}.") + continue + + @property + def pivot_caches(self): + """ + Get PivotCache objects + """ + d = {} + for c in self.caches: + cache = get_rel(self.archive, self.rels, id=c.id, cls=CacheDefinition) + if cache.deps: + records = get_rel(self.archive, cache.deps, cache.id, RecordList) + cache.records = records + d[c.cacheId] = cache + return d diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py new file mode 100644 index 00000000..ea20d0d1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .alignment import Alignment +from .borders import Border, Side +from .colors import Color +from .fills import PatternFill, GradientFill, Fill +from .fonts import Font, DEFAULT_FONT +from .numbers import NumberFormatDescriptor, is_date_format, is_builtin +from .protection import Protection +from .named_styles import NamedStyle diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py new file mode 100644 index 00000000..a727f673 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py @@ -0,0 +1,62 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string + +from openpyxl.descriptors import Bool, MinMax, Min, Alias, NoneSet +from openpyxl.descriptors.serialisable import Serialisable + + +horizontal_alignments = ( + "general", "left", "center", "right", "fill", "justify", "centerContinuous", + "distributed", ) +vertical_aligments = ( + "top", "center", "bottom", "justify", "distributed", +) + +class Alignment(Serialisable): + """Alignment options for use in styles.""" + + tagname = "alignment" + + horizontal = NoneSet(values=horizontal_alignments) + vertical = NoneSet(values=vertical_aligments) + textRotation = NoneSet(values=range(181)) + textRotation.values.add(255) + text_rotation = Alias('textRotation') + wrapText = Bool(allow_none=True) + wrap_text = Alias('wrapText') + shrinkToFit = Bool(allow_none=True) + shrink_to_fit = Alias('shrinkToFit') + indent = MinMax(min=0, max=255) + relativeIndent = MinMax(min=-255, max=255) + justifyLastLine = Bool(allow_none=True) + readingOrder = Min(min=0) + + def __init__(self, horizontal=None, vertical=None, + textRotation=0, wrapText=None, shrinkToFit=None, indent=0, relativeIndent=0, + justifyLastLine=None, readingOrder=0, text_rotation=None, + wrap_text=None, shrink_to_fit=None, mergeCell=None): + self.horizontal = horizontal + self.vertical = vertical + self.indent = indent + self.relativeIndent = relativeIndent + self.justifyLastLine = justifyLastLine + self.readingOrder = readingOrder + if text_rotation is not None: + textRotation = text_rotation + if textRotation is not None: + self.textRotation = int(textRotation) + if wrap_text is not None: + wrapText = wrap_text + self.wrapText = wrapText + if shrink_to_fit is not None: + shrinkToFit = shrink_to_fit + self.shrinkToFit = shrinkToFit + # mergeCell is vestigial + + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if value is not None and value != 0: + yield attr, safe_string(value) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/borders.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/borders.py new file mode 100644 index 00000000..f9fce814 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/borders.py @@ -0,0 +1,103 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string +from openpyxl.descriptors import ( + NoneSet, + Typed, + Bool, + Alias, + Sequence, + Integer, +) +from openpyxl.descriptors.serialisable import Serialisable + +from .colors import ColorDescriptor + + +BORDER_NONE = None +BORDER_DASHDOT = 'dashDot' +BORDER_DASHDOTDOT = 'dashDotDot' +BORDER_DASHED = 'dashed' +BORDER_DOTTED = 'dotted' +BORDER_DOUBLE = 'double' +BORDER_HAIR = 'hair' +BORDER_MEDIUM = 'medium' +BORDER_MEDIUMDASHDOT = 'mediumDashDot' +BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot' +BORDER_MEDIUMDASHED = 'mediumDashed' +BORDER_SLANTDASHDOT = 'slantDashDot' +BORDER_THICK = 'thick' +BORDER_THIN = 'thin' + + +class Side(Serialisable): + + """Border options for use in styles. + Caution: if you do not specify a border_style, other attributes will + have no effect !""" + + + color = ColorDescriptor(allow_none=True) + style = NoneSet(values=('dashDot','dashDotDot', 'dashed','dotted', + 'double','hair', 'medium', 'mediumDashDot', 'mediumDashDotDot', + 'mediumDashed', 'slantDashDot', 'thick', 'thin') + ) + border_style = Alias('style') + + def __init__(self, style=None, color=None, border_style=None): + if border_style is not None: + style = border_style + self.style = style + self.color = color + + +class Border(Serialisable): + """Border positioning for use in styles.""" + + tagname = "border" + + __elements__ = ('start', 'end', 'left', 'right', 'top', 'bottom', + 'diagonal', 'vertical', 'horizontal') + + # child elements + start = Typed(expected_type=Side, allow_none=True) + end = Typed(expected_type=Side, allow_none=True) + left = Typed(expected_type=Side, allow_none=True) + right = Typed(expected_type=Side, allow_none=True) + top = Typed(expected_type=Side, allow_none=True) + bottom = Typed(expected_type=Side, allow_none=True) + diagonal = Typed(expected_type=Side, allow_none=True) + vertical = Typed(expected_type=Side, allow_none=True) + horizontal = Typed(expected_type=Side, allow_none=True) + # attributes + outline = Bool() + diagonalUp = Bool() + diagonalDown = Bool() + + def __init__(self, left=None, right=None, top=None, + bottom=None, diagonal=None, diagonal_direction=None, + vertical=None, horizontal=None, diagonalUp=False, diagonalDown=False, + outline=True, start=None, end=None): + self.left = left + self.right = right + self.top = top + self.bottom = bottom + self.diagonal = diagonal + self.vertical = vertical + self.horizontal = horizontal + self.diagonal_direction = diagonal_direction + self.diagonalUp = diagonalUp + self.diagonalDown = diagonalDown + self.outline = outline + self.start = start + self.end = end + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if value and attr != "outline": + yield attr, safe_string(value) + elif attr == "outline" and not value: + yield attr, safe_string(value) + +DEFAULT_BORDER = Border(left=Side(), right=Side(), top=Side(), bottom=Side(), diagonal=Side()) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py new file mode 100644 index 00000000..7095eb32 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py @@ -0,0 +1,1397 @@ +# Copyright (c) 2010-2024 openpyxl + +# Builtins styles as defined in Part 4 Annex G.2 + +from .named_styles import NamedStyle +from openpyxl.xml.functions import fromstring + + +normal = """ + <namedStyle builtinId="0" name="Normal"> + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +comma = """ + <namedStyle builtinId="3" name="Comma"> + <alignment/> + <number_format>_-* #,##0.00\\ _$_-;\\-* #,##0.00\\ _$_-;_-* "-"??\\ _$_-;_-@_-</number_format> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +comma_0 = """ + <namedStyle builtinId="6" name="Comma [0]"> + <alignment/> + <number_format>_-* #,##0\\ _$_-;\\-* #,##0\\ _$_-;_-* "-"\\ _$_-;_-@_-</number_format> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +currency = """ + <namedStyle builtinId="4" name="Currency"> + <alignment/> + <number_format>_-* #,##0.00\\ "$"_-;\\-* #,##0.00\\ "$"_-;_-* "-"??\\ "$"_-;_-@_-</number_format> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +currency_0 = """ + <namedStyle builtinId="7" name="Currency [0]"> + <alignment/> + <number_format>_-* #,##0\\ "$"_-;\\-* #,##0\\ "$"_-;_-* "-"\\ "$"_-;_-@_-</number_format> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +percent = """ + <namedStyle builtinId="5" name="Percent"> + <alignment/> + <number_format>0%</number_format> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +hyperlink = """ + <namedStyle builtinId="8" name="Hyperlink" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="10"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle>""" + +followed_hyperlink = """ + <namedStyle builtinId="9" name="Followed Hyperlink" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="11"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle>""" + +title = """ + <namedStyle builtinId="15" name="Title"> + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Cambria"/> + <family val="2"/> + <b val="1"/> + <color theme="3"/> + <sz val="18"/> + <scheme val="major"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +headline_1 = """ + <namedStyle builtinId="16" name="Headline 1" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom style="thick"> + <color theme="4"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <b val="1"/> + <color theme="3"/> + <sz val="15"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +headline_2 = """ + <namedStyle builtinId="17" name="Headline 2" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom style="thick"> + <color theme="4" tint="0.5"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <b val="1"/> + <color theme="3"/> + <sz val="13"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +headline_3 = """ + <namedStyle builtinId="18" name="Headline 3" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom style="medium"> + <color theme="4" tint="0.4"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <b val="1"/> + <color theme="3"/> + <sz val="11"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> + +""" + +headline_4 = """ + <namedStyle builtinId="19" name="Headline 4"> + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <b val="1"/> + <color theme="3"/> + <sz val="11"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +good = """ + <namedStyle builtinId="26" name="Good" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor rgb="FFC6EFCE"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color rgb="FF006100"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +bad = """ + <namedStyle builtinId="27" name="Bad" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor rgb="FFFFC7CE"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color rgb="FF9C0006"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +neutral = """ + <namedStyle builtinId="28" name="Neutral" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor rgb="FFFFEB9C"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color rgb="FF9C6500"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +input = """ + <namedStyle builtinId="20" name="Input" > + <alignment/> + <border> + <left style="thin"> + <color rgb="FF7F7F7F"/> + </left> + <right style="thin"> + <color rgb="FF7F7F7F"/> + </right> + <top style="thin"> + <color rgb="FF7F7F7F"/> + </top> + <bottom style="thin"> + <color rgb="FF7F7F7F"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor rgb="FFFFCC99"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color rgb="FF3F3F76"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +output = """ + <namedStyle builtinId="21" name="Output" > + <alignment/> + <border> + <left style="thin"> + <color rgb="FF3F3F3F"/> + </left> + <right style="thin"> + <color rgb="FF3F3F3F"/> + </right> + <top style="thin"> + <color rgb="FF3F3F3F"/> + </top> + <bottom style="thin"> + <color rgb="FF3F3F3F"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor rgb="FFF2F2F2"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <b val="1"/> + <color rgb="FF3F3F3F"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +calculation = """ + <namedStyle builtinId="22" name="Calculation" > + <alignment/> + <border> + <left style="thin"> + <color rgb="FF7F7F7F"/> + </left> + <right style="thin"> + <color rgb="FF7F7F7F"/> + </right> + <top style="thin"> + <color rgb="FF7F7F7F"/> + </top> + <bottom style="thin"> + <color rgb="FF7F7F7F"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor rgb="FFF2F2F2"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <b val="1"/> + <color rgb="FFFA7D00"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +linked_cell = """ + <namedStyle builtinId="24" name="Linked Cell" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom style="double"> + <color rgb="FFFF8001"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color rgb="FFFA7D00"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +check_cell = """ + <namedStyle builtinId="23" name="Check Cell" > + <alignment/> + <border> + <left style="double"> + <color rgb="FF3F3F3F"/> + </left> + <right style="double"> + <color rgb="FF3F3F3F"/> + </right> + <top style="double"> + <color rgb="FF3F3F3F"/> + </top> + <bottom style="double"> + <color rgb="FF3F3F3F"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor rgb="FFA5A5A5"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <b val="1"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +warning = """ + <namedStyle builtinId="11" name="Warning Text" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color rgb="FFFF0000"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +note = """ + <namedStyle builtinId="10" name="Note" > + <alignment/> + <border> + <left style="thin"> + <color rgb="FFB2B2B2"/> + </left> + <right style="thin"> + <color rgb="FFB2B2B2"/> + </right> + <top style="thin"> + <color rgb="FFB2B2B2"/> + </top> + <bottom style="thin"> + <color rgb="FFB2B2B2"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor rgb="FFFFFFCC"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +explanatory = """ + <namedStyle builtinId="53" name="Explanatory Text" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <i val="1"/> + <color rgb="FF7F7F7F"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +total = """ + <namedStyle builtinId="25" name="Total" > + <alignment/> + <border> + <left/> + <right/> + <top style="thin"> + <color theme="4"/> + </top> + <bottom style="double"> + <color theme="4"/> + </bottom> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <b val="1"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_1 = """ + <namedStyle builtinId="29" name="Accent1" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="4"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_1_20 = """ + <namedStyle builtinId="30" name="20 % - Accent1" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="4" tint="0.7999816888943144"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_1_40 = """ + <namedStyle builtinId="31" name="40 % - Accent1" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="4" tint="0.5999938962981048"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_1_60 = """ + <namedStyle builtinId="32" name="60 % - Accent1" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="4" tint="0.3999755851924192"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_2 = """<namedStyle builtinId="33" name="Accent2" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="5"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle>""" + +accent_2_20 = """ + <namedStyle builtinId="34" name="20 % - Accent2" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="5" tint="0.7999816888943144"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle>""" + +accent_2_40 = """ +<namedStyle builtinId="35" name="40 % - Accent2" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="5" tint="0.5999938962981048"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle>""" + +accent_2_60 = """ +<namedStyle builtinId="36" name="60 % - Accent2" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="5" tint="0.3999755851924192"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle>""" + +accent_3 = """ +<namedStyle builtinId="37" name="Accent3" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="6"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle>""" + +accent_3_20 = """ + <namedStyle builtinId="38" name="20 % - Accent3" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="6" tint="0.7999816888943144"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle>""" + +accent_3_40 = """ + <namedStyle builtinId="39" name="40 % - Accent3" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="6" tint="0.5999938962981048"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" +accent_3_60 = """ + <namedStyle builtinId="40" name="60 % - Accent3" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="6" tint="0.3999755851924192"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" +accent_4 = """ + <namedStyle builtinId="41" name="Accent4" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="7"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_4_20 = """ + <namedStyle builtinId="42" name="20 % - Accent4" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="7" tint="0.7999816888943144"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_4_40 = """ + <namedStyle builtinId="43" name="40 % - Accent4" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="7" tint="0.5999938962981048"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_4_60 = """ +<namedStyle builtinId="44" name="60 % - Accent4" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="7" tint="0.3999755851924192"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_5 = """ + <namedStyle builtinId="45" name="Accent5" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="8"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_5_20 = """ + <namedStyle builtinId="46" name="20 % - Accent5" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="8" tint="0.7999816888943144"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_5_40 = """ + <namedStyle builtinId="47" name="40 % - Accent5" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="8" tint="0.5999938962981048"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_5_60 = """ + <namedStyle builtinId="48" name="60 % - Accent5" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="8" tint="0.3999755851924192"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_6 = """ + <namedStyle builtinId="49" name="Accent6" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="9"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_6_20 = """ + <namedStyle builtinId="50" name="20 % - Accent6" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="9" tint="0.7999816888943144"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_6_40 = """ + <namedStyle builtinId="51" name="40 % - Accent6" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="9" tint="0.5999938962981048"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="1"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +accent_6_60 = """ + <namedStyle builtinId="52" name="60 % - Accent6" > + <alignment/> + <border> + <left/> + <right/> + <top/> + <bottom/> + <diagonal/> + </border> + <fill> + <patternFill patternType="solid"> + <fgColor theme="9" tint="0.3999755851924192"/> + <bgColor indexed="65"/> + </patternFill> + </fill> + <font> + <name val="Calibri"/> + <family val="2"/> + <color theme="0"/> + <sz val="12"/> + <scheme val="minor"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +pandas_highlight = """ + <namedStyle hidden="0" name="Pandas"> + <alignment horizontal="center"/> + <border> + <left style="thin"><color rgb="00000000"/></left> + <right style="thin"><color rgb="00000000"/></right> + <top style="thin"><color rgb="00000000"/></top> + <bottom style="thin"><color rgb="00000000"/></bottom> + <diagonal/> + </border> + <fill> + <patternFill/> + </fill> + <font> + <b val="1"/> + </font> + <protection hidden="0" locked="1"/> + </namedStyle> +""" + +styles = dict( + [ + ('Normal', NamedStyle.from_tree(fromstring(normal))), + ('Comma', NamedStyle.from_tree(fromstring(comma))), + ('Currency', NamedStyle.from_tree(fromstring(currency))), + ('Percent', NamedStyle.from_tree(fromstring(percent))), + ('Comma [0]', NamedStyle.from_tree(fromstring(comma_0))), + ('Currency [0]', NamedStyle.from_tree(fromstring(currency_0))), + ('Hyperlink', NamedStyle.from_tree(fromstring(hyperlink))), + ('Followed Hyperlink', NamedStyle.from_tree(fromstring(followed_hyperlink))), + ('Note', NamedStyle.from_tree(fromstring(note))), + ('Warning Text', NamedStyle.from_tree(fromstring(warning))), + ('Title', NamedStyle.from_tree(fromstring(title))), + ('Headline 1', NamedStyle.from_tree(fromstring(headline_1))), + ('Headline 2', NamedStyle.from_tree(fromstring(headline_2))), + ('Headline 3', NamedStyle.from_tree(fromstring(headline_3))), + ('Headline 4', NamedStyle.from_tree(fromstring(headline_4))), + ('Input', NamedStyle.from_tree(fromstring(input))), + ('Output', NamedStyle.from_tree(fromstring(output))), + ('Calculation',NamedStyle.from_tree(fromstring(calculation))), + ('Check Cell', NamedStyle.from_tree(fromstring(check_cell))), + ('Linked Cell', NamedStyle.from_tree(fromstring(linked_cell))), + ('Total', NamedStyle.from_tree(fromstring(total))), + ('Good', NamedStyle.from_tree(fromstring(good))), + ('Bad', NamedStyle.from_tree(fromstring(bad))), + ('Neutral', NamedStyle.from_tree(fromstring(neutral))), + ('Accent1', NamedStyle.from_tree(fromstring(accent_1))), + ('20 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_20))), + ('40 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_40))), + ('60 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_60))), + ('Accent2', NamedStyle.from_tree(fromstring(accent_2))), + ('20 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_20))), + ('40 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_40))), + ('60 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_60))), + ('Accent3', NamedStyle.from_tree(fromstring(accent_3))), + ('20 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_20))), + ('40 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_40))), + ('60 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_60))), + ('Accent4', NamedStyle.from_tree(fromstring(accent_4))), + ('20 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_20))), + ('40 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_40))), + ('60 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_60))), + ('Accent5', NamedStyle.from_tree(fromstring(accent_5))), + ('20 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_20))), + ('40 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_40))), + ('60 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_60))), + ('Accent6', NamedStyle.from_tree(fromstring(accent_6))), + ('20 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_20))), + ('40 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_40))), + ('60 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_60))), + ('Explanatory Text', NamedStyle.from_tree(fromstring(explanatory))), + ('Pandas', NamedStyle.from_tree(fromstring(pandas_highlight))) + ] +) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py new file mode 100644 index 00000000..51091aa5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py @@ -0,0 +1,206 @@ +# Copyright (c) 2010-2024 openpyxl + +from array import array + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + Bool, + Integer, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.utils.indexed_list import IndexedList + + +from .alignment import Alignment +from .protection import Protection + + +class ArrayDescriptor: + + def __init__(self, key): + self.key = key + + def __get__(self, instance, cls): + return instance[self.key] + + def __set__(self, instance, value): + instance[self.key] = value + + +class StyleArray(array): + """ + Simplified named tuple with an array + """ + + __slots__ = () + tagname = 'xf' + + fontId = ArrayDescriptor(0) + fillId = ArrayDescriptor(1) + borderId = ArrayDescriptor(2) + numFmtId = ArrayDescriptor(3) + protectionId = ArrayDescriptor(4) + alignmentId = ArrayDescriptor(5) + pivotButton = ArrayDescriptor(6) + quotePrefix = ArrayDescriptor(7) + xfId = ArrayDescriptor(8) + + + def __new__(cls, args=[0]*9): + return array.__new__(cls, 'i', args) + + + def __hash__(self): + return hash(tuple(self)) + + + def __copy__(self): + return StyleArray((self)) + + + def __deepcopy__(self, memo): + return StyleArray((self)) + + +class CellStyle(Serialisable): + + tagname = "xf" + + numFmtId = Integer() + fontId = Integer() + fillId = Integer() + borderId = Integer() + xfId = Integer(allow_none=True) + quotePrefix = Bool(allow_none=True) + pivotButton = Bool(allow_none=True) + applyNumberFormat = Bool(allow_none=True) + applyFont = Bool(allow_none=True) + applyFill = Bool(allow_none=True) + applyBorder = Bool(allow_none=True) + applyAlignment = Bool(allow_none=True) + applyProtection = Bool(allow_none=True) + alignment = Typed(expected_type=Alignment, allow_none=True) + protection = Typed(expected_type=Protection, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('alignment', 'protection') + __attrs__ = ("numFmtId", "fontId", "fillId", "borderId", + "applyAlignment", "applyProtection", "pivotButton", "quotePrefix", "xfId") + + def __init__(self, + numFmtId=0, + fontId=0, + fillId=0, + borderId=0, + xfId=None, + quotePrefix=None, + pivotButton=None, + applyNumberFormat=None, + applyFont=None, + applyFill=None, + applyBorder=None, + applyAlignment=None, + applyProtection=None, + alignment=None, + protection=None, + extLst=None, + ): + self.numFmtId = numFmtId + self.fontId = fontId + self.fillId = fillId + self.borderId = borderId + self.xfId = xfId + self.quotePrefix = quotePrefix + self.pivotButton = pivotButton + self.applyNumberFormat = applyNumberFormat + self.applyFont = applyFont + self.applyFill = applyFill + self.applyBorder = applyBorder + self.alignment = alignment + self.protection = protection + + + def to_array(self): + """ + Convert to StyleArray + """ + style = StyleArray() + for k in ("fontId", "fillId", "borderId", "numFmtId", "pivotButton", + "quotePrefix", "xfId"): + v = getattr(self, k, 0) + if v is not None: + setattr(style, k, v) + return style + + + @classmethod + def from_array(cls, style): + """ + Convert from StyleArray + """ + return cls(numFmtId=style.numFmtId, fontId=style.fontId, + fillId=style.fillId, borderId=style.borderId, xfId=style.xfId, + quotePrefix=style.quotePrefix, pivotButton=style.pivotButton,) + + + @property + def applyProtection(self): + return self.protection is not None or None + + + @property + def applyAlignment(self): + return self.alignment is not None or None + + +class CellStyleList(Serialisable): + + tagname = "cellXfs" + + __attrs__ = ("count",) + + count = Integer(allow_none=True) + xf = Sequence(expected_type=CellStyle) + alignment = Sequence(expected_type=Alignment) + protection = Sequence(expected_type=Protection) + + __elements__ = ('xf',) + + def __init__(self, + count=None, + xf=(), + ): + self.xf = xf + + + @property + def count(self): + return len(self.xf) + + + def __getitem__(self, idx): + try: + return self.xf[idx] + except IndexError: + print((f"{idx} is out of range")) + return self.xf[idx] + + + def _to_array(self): + """ + Extract protection and alignments, convert to style array + """ + self.prots = IndexedList([Protection()]) + self.alignments = IndexedList([Alignment()]) + styles = [] # allow duplicates + for xf in self.xf: + style = xf.to_array() + if xf.alignment is not None: + style.alignmentId = self.alignments.add(xf.alignment) + if xf.protection is not None: + style.protectionId = self.prots.add(xf.protection) + styles.append(style) + return IndexedList(styles) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/colors.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/colors.py new file mode 100644 index 00000000..6fa7476d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/colors.py @@ -0,0 +1,172 @@ +# Copyright (c) 2010-2024 openpyxl + +import re +from openpyxl.compat import safe_string +from openpyxl.descriptors import ( + String, + Bool, + MinMax, + Integer, + Typed, +) +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.descriptors.serialisable import Serialisable + +# Default Color Index as per 18.8.27 of ECMA Part 4 +COLOR_INDEX = ( + '00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', #0-4 + '00FFFF00', '00FF00FF', '0000FFFF', '00000000', '00FFFFFF', #5-9 + '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', #10-14 + '0000FFFF', '00800000', '00008000', '00000080', '00808000', #15-19 + '00800080', '00008080', '00C0C0C0', '00808080', '009999FF', #20-24 + '00993366', '00FFFFCC', '00CCFFFF', '00660066', '00FF8080', #25-29 + '000066CC', '00CCCCFF', '00000080', '00FF00FF', '00FFFF00', #30-34 + '0000FFFF', '00800080', '00800000', '00008080', '000000FF', #35-39 + '0000CCFF', '00CCFFFF', '00CCFFCC', '00FFFF99', '0099CCFF', #40-44 + '00FF99CC', '00CC99FF', '00FFCC99', '003366FF', '0033CCCC', #45-49 + '0099CC00', '00FFCC00', '00FF9900', '00FF6600', '00666699', #50-54 + '00969696', '00003366', '00339966', '00003300', '00333300', #55-59 + '00993300', '00993366', '00333399', '00333333', #60-63 +) +# indices 64 and 65 are reserved for the system foreground and background colours respectively + +# Will remove these definitions in a future release +BLACK = COLOR_INDEX[0] +WHITE = COLOR_INDEX[1] +#RED = COLOR_INDEX[2] +#DARKRED = COLOR_INDEX[8] +BLUE = COLOR_INDEX[4] +#DARKBLUE = COLOR_INDEX[12] +#GREEN = COLOR_INDEX[3] +#DARKGREEN = COLOR_INDEX[9] +#YELLOW = COLOR_INDEX[5] +#DARKYELLOW = COLOR_INDEX[19] + + +aRGB_REGEX = re.compile("^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$") + + +class RGB(Typed): + """ + Descriptor for aRGB values + If not supplied alpha is 00 + """ + + expected_type = str + + def __set__(self, instance, value): + if not self.allow_none: + m = aRGB_REGEX.match(value) + if m is None: + raise ValueError("Colors must be aRGB hex values") + if len(value) == 6: + value = "00" + value + super().__set__(instance, value) + + +class Color(Serialisable): + """Named colors for use in styles.""" + + tagname = "color" + + rgb = RGB() + indexed = Integer() + auto = Bool() + theme = Integer() + tint = MinMax(min=-1, max=1, expected_type=float) + type = String() + + + def __init__(self, rgb=BLACK, indexed=None, auto=None, theme=None, tint=0.0, index=None, type='rgb'): + if index is not None: + indexed = index + if indexed is not None: + self.type = 'indexed' + self.indexed = indexed + elif theme is not None: + self.type = 'theme' + self.theme = theme + elif auto is not None: + self.type = 'auto' + self.auto = auto + else: + self.rgb = rgb + self.type = 'rgb' + self.tint = tint + + @property + def value(self): + return getattr(self, self.type) + + @value.setter + def value(self, value): + setattr(self, self.type, value) + + def __iter__(self): + attrs = [(self.type, self.value)] + if self.tint != 0: + attrs.append(('tint', self.tint)) + for k, v in attrs: + yield k, safe_string(v) + + @property + def index(self): + # legacy + return self.value + + + def __add__(self, other): + """ + Adding colours is undefined behaviour best do nothing + """ + if not isinstance(other, Color): + return super().__add__(other) + return self + + +class ColorDescriptor(Typed): + + expected_type = Color + + def __set__(self, instance, value): + if isinstance(value, str): + value = Color(rgb=value) + super().__set__(instance, value) + + +class RgbColor(Serialisable): + + tagname = "rgbColor" + + rgb = RGB() + + def __init__(self, + rgb=None, + ): + self.rgb = rgb + + +class ColorList(Serialisable): + + tagname = "colors" + + indexedColors = NestedSequence(expected_type=RgbColor) + mruColors = NestedSequence(expected_type=Color) + + __elements__ = ('indexedColors', 'mruColors') + + def __init__(self, + indexedColors=(), + mruColors=(), + ): + self.indexedColors = indexedColors + self.mruColors = mruColors + + + def __bool__(self): + return bool(self.indexedColors) or bool(self.mruColors) + + + @property + def index(self): + return [val.rgb for val in self.indexedColors] diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/differential.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/differential.py new file mode 100644 index 00000000..109577e4 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/differential.py @@ -0,0 +1,95 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Typed, + Sequence, + Alias, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.styles import ( + Font, + Fill, + Border, + Alignment, + Protection, + ) +from .numbers import NumberFormat + + +class DifferentialStyle(Serialisable): + + tagname = "dxf" + + __elements__ = ("font", "numFmt", "fill", "alignment", "border", "protection") + + font = Typed(expected_type=Font, allow_none=True) + numFmt = Typed(expected_type=NumberFormat, allow_none=True) + fill = Typed(expected_type=Fill, allow_none=True) + alignment = Typed(expected_type=Alignment, allow_none=True) + border = Typed(expected_type=Border, allow_none=True) + protection = Typed(expected_type=Protection, allow_none=True) + + def __init__(self, + font=None, + numFmt=None, + fill=None, + alignment=None, + border=None, + protection=None, + extLst=None, + ): + self.font = font + self.numFmt = numFmt + self.fill = fill + self.alignment = alignment + self.border = border + self.protection = protection + self.extLst = extLst + + +class DifferentialStyleList(Serialisable): + """ + Dedupable container for differential styles. + """ + + tagname = "dxfs" + + dxf = Sequence(expected_type=DifferentialStyle) + styles = Alias("dxf") + __attrs__ = ("count",) + + + def __init__(self, dxf=(), count=None): + self.dxf = dxf + + + def append(self, dxf): + """ + Check to see whether style already exists and append it if does not. + """ + if not isinstance(dxf, DifferentialStyle): + raise TypeError('expected ' + str(DifferentialStyle)) + if dxf in self.styles: + return + self.styles.append(dxf) + + + def add(self, dxf): + """ + Add a differential style and return its index + """ + self.append(dxf) + return self.styles.index(dxf) + + + def __bool__(self): + return bool(self.styles) + + + def __getitem__(self, idx): + return self.styles[idx] + + + @property + def count(self): + return len(self.dxf) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/fills.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/fills.py new file mode 100644 index 00000000..7071abd6 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/fills.py @@ -0,0 +1,224 @@ + +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Float, + Set, + Alias, + NoneSet, + Sequence, + Integer, + MinMax, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.compat import safe_string + +from .colors import ColorDescriptor, Color + +from openpyxl.xml.functions import Element, localname +from openpyxl.xml.constants import SHEET_MAIN_NS + + +FILL_NONE = 'none' +FILL_SOLID = 'solid' +FILL_PATTERN_DARKDOWN = 'darkDown' +FILL_PATTERN_DARKGRAY = 'darkGray' +FILL_PATTERN_DARKGRID = 'darkGrid' +FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal' +FILL_PATTERN_DARKTRELLIS = 'darkTrellis' +FILL_PATTERN_DARKUP = 'darkUp' +FILL_PATTERN_DARKVERTICAL = 'darkVertical' +FILL_PATTERN_GRAY0625 = 'gray0625' +FILL_PATTERN_GRAY125 = 'gray125' +FILL_PATTERN_LIGHTDOWN = 'lightDown' +FILL_PATTERN_LIGHTGRAY = 'lightGray' +FILL_PATTERN_LIGHTGRID = 'lightGrid' +FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal' +FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis' +FILL_PATTERN_LIGHTUP = 'lightUp' +FILL_PATTERN_LIGHTVERTICAL = 'lightVertical' +FILL_PATTERN_MEDIUMGRAY = 'mediumGray' + +fills = (FILL_SOLID, FILL_PATTERN_DARKDOWN, FILL_PATTERN_DARKGRAY, + FILL_PATTERN_DARKGRID, FILL_PATTERN_DARKHORIZONTAL, FILL_PATTERN_DARKTRELLIS, + FILL_PATTERN_DARKUP, FILL_PATTERN_DARKVERTICAL, FILL_PATTERN_GRAY0625, + FILL_PATTERN_GRAY125, FILL_PATTERN_LIGHTDOWN, FILL_PATTERN_LIGHTGRAY, + FILL_PATTERN_LIGHTGRID, FILL_PATTERN_LIGHTHORIZONTAL, + FILL_PATTERN_LIGHTTRELLIS, FILL_PATTERN_LIGHTUP, FILL_PATTERN_LIGHTVERTICAL, + FILL_PATTERN_MEDIUMGRAY) + + +class Fill(Serialisable): + + """Base class""" + + tagname = "fill" + + @classmethod + def from_tree(cls, el): + children = [c for c in el] + if not children: + return + child = children[0] + if "patternFill" in child.tag: + return PatternFill._from_tree(child) + return super(Fill, GradientFill).from_tree(child) + + +class PatternFill(Fill): + """Area fill patterns for use in styles. + Caution: if you do not specify a fill_type, other attributes will have + no effect !""" + + tagname = "patternFill" + + __elements__ = ('fgColor', 'bgColor') + + patternType = NoneSet(values=fills) + fill_type = Alias("patternType") + fgColor = ColorDescriptor() + start_color = Alias("fgColor") + bgColor = ColorDescriptor() + end_color = Alias("bgColor") + + def __init__(self, patternType=None, fgColor=Color(), bgColor=Color(), + fill_type=None, start_color=None, end_color=None): + if fill_type is not None: + patternType = fill_type + self.patternType = patternType + if start_color is not None: + fgColor = start_color + self.fgColor = fgColor + if end_color is not None: + bgColor = end_color + self.bgColor = bgColor + + @classmethod + def _from_tree(cls, el): + attrib = dict(el.attrib) + for child in el: + desc = localname(child) + attrib[desc] = Color.from_tree(child) + return cls(**attrib) + + + def to_tree(self, tagname=None, idx=None): + parent = Element("fill") + el = Element(self.tagname) + if self.patternType is not None: + el.set('patternType', self.patternType) + for c in self.__elements__: + value = getattr(self, c) + if value != Color(): + el.append(value.to_tree(c)) + parent.append(el) + return parent + + +DEFAULT_EMPTY_FILL = PatternFill() +DEFAULT_GRAY_FILL = PatternFill(patternType='gray125') + + +class Stop(Serialisable): + + tagname = "stop" + + position = MinMax(min=0, max=1) + color = ColorDescriptor() + + def __init__(self, color, position): + self.position = position + self.color = color + + +def _assign_position(values): + """ + Automatically assign positions if a list of colours is provided. + + It is not permitted to mix colours and stops + """ + n_values = len(values) + n_stops = sum(isinstance(value, Stop) for value in values) + + if n_stops == 0: + interval = 1 + if n_values > 2: + interval = 1 / (n_values - 1) + values = [Stop(value, i * interval) + for i, value in enumerate(values)] + + elif n_stops < n_values: + raise ValueError('Cannot interpret mix of Stops and Colors in GradientFill') + + pos = set() + for stop in values: + if stop.position in pos: + raise ValueError("Duplicate position {0}".format(stop.position)) + pos.add(stop.position) + + return values + + +class StopList(Sequence): + + expected_type = Stop + + def __set__(self, obj, values): + values = _assign_position(values) + super().__set__(obj, values) + + +class GradientFill(Fill): + """Fill areas with gradient + + Two types of gradient fill are supported: + + - A type='linear' gradient interpolates colours between + a set of specified Stops, across the length of an area. + The gradient is left-to-right by default, but this + orientation can be modified with the degree + attribute. A list of Colors can be provided instead + and they will be positioned with equal distance between them. + + - A type='path' gradient applies a linear gradient from each + edge of the area. Attributes top, right, bottom, left specify + the extent of fill from the respective borders. Thus top="0.2" + will fill the top 20% of the cell. + + """ + + tagname = "gradientFill" + + type = Set(values=('linear', 'path')) + fill_type = Alias("type") + degree = Float() + left = Float() + right = Float() + top = Float() + bottom = Float() + stop = StopList() + + + def __init__(self, type="linear", degree=0, left=0, right=0, top=0, + bottom=0, stop=()): + self.degree = degree + self.left = left + self.right = right + self.top = top + self.bottom = bottom + self.stop = stop + self.type = type + + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if value: + yield attr, safe_string(value) + + + def to_tree(self, tagname=None, namespace=None, idx=None): + parent = Element("fill") + el = super().to_tree() + parent.append(el) + return parent diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py new file mode 100644 index 00000000..06e343fc --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py @@ -0,0 +1,113 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors import ( + Alias, + Sequence, + Integer +) +from openpyxl.descriptors.serialisable import Serialisable + +from openpyxl.descriptors.nested import ( + NestedValue, + NestedBool, + NestedNoneSet, + NestedMinMax, + NestedString, + NestedInteger, + NestedFloat, +) +from .colors import ColorDescriptor, Color, BLACK + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element, SubElement +from openpyxl.xml.constants import SHEET_MAIN_NS + + +def _no_value(tagname, value, namespace=None): + if value: + return Element(tagname, val=safe_string(value)) + + +class Font(Serialisable): + """Font options used in styles.""" + + UNDERLINE_DOUBLE = 'double' + UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting' + UNDERLINE_SINGLE = 'single' + UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting' + + name = NestedString(allow_none=True) + charset = NestedInteger(allow_none=True) + family = NestedMinMax(min=0, max=14, allow_none=True) + sz = NestedFloat(allow_none=True) + size = Alias("sz") + b = NestedBool(to_tree=_no_value) + bold = Alias("b") + i = NestedBool(to_tree=_no_value) + italic = Alias("i") + strike = NestedBool(allow_none=True) + strikethrough = Alias("strike") + outline = NestedBool(allow_none=True) + shadow = NestedBool(allow_none=True) + condense = NestedBool(allow_none=True) + extend = NestedBool(allow_none=True) + u = NestedNoneSet(values=('single', 'double', 'singleAccounting', + 'doubleAccounting')) + underline = Alias("u") + vertAlign = NestedNoneSet(values=('superscript', 'subscript', 'baseline')) + color = ColorDescriptor(allow_none=True) + scheme = NestedNoneSet(values=("major", "minor")) + + tagname = "font" + + __elements__ = ('name', 'charset', 'family', 'b', 'i', 'strike', 'outline', + 'shadow', 'condense', 'color', 'extend', 'sz', 'u', 'vertAlign', + 'scheme') + + + def __init__(self, name=None, sz=None, b=None, i=None, charset=None, + u=None, strike=None, color=None, scheme=None, family=None, size=None, + bold=None, italic=None, strikethrough=None, underline=None, + vertAlign=None, outline=None, shadow=None, condense=None, + extend=None): + self.name = name + self.family = family + if size is not None: + sz = size + self.sz = sz + if bold is not None: + b = bold + self.b = b + if italic is not None: + i = italic + self.i = i + if underline is not None: + u = underline + self.u = u + if strikethrough is not None: + strike = strikethrough + self.strike = strike + self.color = color + self.vertAlign = vertAlign + self.charset = charset + self.outline = outline + self.shadow = shadow + self.condense = condense + self.extend = extend + self.scheme = scheme + + + @classmethod + def from_tree(cls, node): + """ + Set default value for underline if child element is present + """ + underline = node.find("{%s}u" % SHEET_MAIN_NS) + if underline is not None and underline.get('val') is None: + underline.set("val", "single") + return super().from_tree(node) + + +DEFAULT_FONT = Font(name="Calibri", sz=11, family=2, b=False, i=False, + color=Color(theme=1), scheme="minor") diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py new file mode 100644 index 00000000..221d333b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py @@ -0,0 +1,282 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string + +from openpyxl.descriptors import ( + Typed, + Integer, + Bool, + String, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.serialisable import Serialisable + +from .fills import PatternFill, Fill +from .fonts import Font +from .borders import Border +from .alignment import Alignment +from .protection import Protection +from .numbers import ( + NumberFormatDescriptor, + BUILTIN_FORMATS_MAX_SIZE, + BUILTIN_FORMATS_REVERSE, +) +from .cell_style import ( + StyleArray, + CellStyle, +) + + +class NamedStyle(Serialisable): + + """ + Named and editable styles + """ + + font = Typed(expected_type=Font) + fill = Typed(expected_type=Fill) + border = Typed(expected_type=Border) + alignment = Typed(expected_type=Alignment) + number_format = NumberFormatDescriptor() + protection = Typed(expected_type=Protection) + builtinId = Integer(allow_none=True) + hidden = Bool(allow_none=True) + name = String() + _wb = None + _style = StyleArray() + + + def __init__(self, + name="Normal", + font=None, + fill=None, + border=None, + alignment=None, + number_format=None, + protection=None, + builtinId=None, + hidden=False, + ): + self.name = name + self.font = font or Font() + self.fill = fill or PatternFill() + self.border = border or Border() + self.alignment = alignment or Alignment() + self.number_format = number_format + self.protection = protection or Protection() + self.builtinId = builtinId + self.hidden = hidden + self._wb = None + self._style = StyleArray() + + + def __setattr__(self, attr, value): + super().__setattr__(attr, value) + if getattr(self, '_wb', None) and attr in ( + 'font', 'fill', 'border', 'alignment', 'number_format', 'protection', + ): + self._recalculate() + + + def __iter__(self): + for key in ('name', 'builtinId', 'hidden', 'xfId'): + value = getattr(self, key, None) + if value is not None: + yield key, safe_string(value) + + + def bind(self, wb): + """ + Bind a named style to a workbook + """ + self._wb = wb + self._recalculate() + + + def _recalculate(self): + self._style.fontId = self._wb._fonts.add(self.font) + self._style.borderId = self._wb._borders.add(self.border) + self._style.fillId = self._wb._fills.add(self.fill) + self._style.protectionId = self._wb._protections.add(self.protection) + self._style.alignmentId = self._wb._alignments.add(self.alignment) + fmt = self.number_format + if fmt in BUILTIN_FORMATS_REVERSE: + fmt = BUILTIN_FORMATS_REVERSE[fmt] + else: + fmt = self._wb._number_formats.add(self.number_format) + ( + BUILTIN_FORMATS_MAX_SIZE) + self._style.numFmtId = fmt + + + def as_tuple(self): + """Return a style array representing the current style""" + return self._style + + + def as_xf(self): + """ + Return equivalent XfStyle + """ + xf = CellStyle.from_array(self._style) + xf.xfId = None + xf.pivotButton = None + xf.quotePrefix = None + if self.alignment != Alignment(): + xf.alignment = self.alignment + if self.protection != Protection(): + xf.protection = self.protection + return xf + + + def as_name(self): + """ + Return relevant named style + + """ + named = _NamedCellStyle( + name=self.name, + builtinId=self.builtinId, + hidden=self.hidden, + xfId=self._style.xfId + ) + return named + + +class NamedStyleList(list): + """ + Named styles are editable and can be applied to multiple objects + + As only the index is stored in referencing objects the order mus + be preserved. + + Returns a list of NamedStyles + """ + + def __init__(self, iterable=()): + """ + Allow a list of named styles to be passed in and index them. + """ + + for idx, s in enumerate(iterable, len(self)): + s._style.xfId = idx + super().__init__(iterable) + + + @property + def names(self): + return [s.name for s in self] + + + def __getitem__(self, key): + if isinstance(key, int): + return super().__getitem__(key) + + + for idx, name in enumerate(self.names): + if name == key: + return self[idx] + + raise KeyError("No named style with the name{0} exists".format(key)) + + def append(self, style): + if not isinstance(style, NamedStyle): + raise TypeError("""Only NamedStyle instances can be added""") + elif style.name in self.names: # hotspot + raise ValueError("""Style {0} exists already""".format(style.name)) + style._style.xfId = (len(self)) + super().append(style) + + +class _NamedCellStyle(Serialisable): + + """ + Pointer-based representation of named styles in XML + xfId refers to the corresponding CellStyleXfs + + Not used in client code. + """ + + tagname = "cellStyle" + + name = String() + xfId = Integer() + builtinId = Integer(allow_none=True) + iLevel = Integer(allow_none=True) + hidden = Bool(allow_none=True) + customBuiltin = Bool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + + def __init__(self, + name=None, + xfId=None, + builtinId=None, + iLevel=None, + hidden=None, + customBuiltin=None, + extLst=None, + ): + self.name = name + self.xfId = xfId + self.builtinId = builtinId + self.iLevel = iLevel + self.hidden = hidden + self.customBuiltin = customBuiltin + + +class _NamedCellStyleList(Serialisable): + """ + Container for named cell style objects + + Not used in client code + """ + + tagname = "cellStyles" + + count = Integer(allow_none=True) + cellStyle = Sequence(expected_type=_NamedCellStyle) + + __attrs__ = ("count",) + + def __init__(self, + count=None, + cellStyle=(), + ): + self.cellStyle = cellStyle + + + @property + def count(self): + return len(self.cellStyle) + + + def remove_duplicates(self): + """ + Some applications contain duplicate definitions either by name or + referenced style. + + As the references are 0-based indices, styles are sorted by + index. + + Returns a list of style references with duplicates removed + """ + + def sort_fn(v): + return v.xfId + + styles = [] + names = set() + ids = set() + + for ns in sorted(self.cellStyle, key=sort_fn): + if ns.xfId in ids or ns.name in names: # skip duplicates + continue + ids.add(ns.xfId) + names.add(ns.name) + + styles.append(ns) + + return styles diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py new file mode 100644 index 00000000..b548cc7c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py @@ -0,0 +1,200 @@ +# Copyright (c) 2010-2024 openpyxl + +import re + +from openpyxl.descriptors import ( + String, + Sequence, + Integer, +) +from openpyxl.descriptors.serialisable import Serialisable + + +BUILTIN_FORMATS = { + 0: 'General', + 1: '0', + 2: '0.00', + 3: '#,##0', + 4: '#,##0.00', + 5: '"$"#,##0_);("$"#,##0)', + 6: '"$"#,##0_);[Red]("$"#,##0)', + 7: '"$"#,##0.00_);("$"#,##0.00)', + 8: '"$"#,##0.00_);[Red]("$"#,##0.00)', + 9: '0%', + 10: '0.00%', + 11: '0.00E+00', + 12: '# ?/?', + 13: '# ??/??', + 14: 'mm-dd-yy', + 15: 'd-mmm-yy', + 16: 'd-mmm', + 17: 'mmm-yy', + 18: 'h:mm AM/PM', + 19: 'h:mm:ss AM/PM', + 20: 'h:mm', + 21: 'h:mm:ss', + 22: 'm/d/yy h:mm', + + 37: '#,##0_);(#,##0)', + 38: '#,##0_);[Red](#,##0)', + 39: '#,##0.00_);(#,##0.00)', + 40: '#,##0.00_);[Red](#,##0.00)', + + 41: r'_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)', + 42: r'_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)', + 43: r'_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)', + + 44: r'_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)', + 45: 'mm:ss', + 46: '[h]:mm:ss', + 47: 'mmss.0', + 48: '##0.0E+0', + 49: '@', } + +BUILTIN_FORMATS_MAX_SIZE = 164 +BUILTIN_FORMATS_REVERSE = dict( + [(value, key) for key, value in BUILTIN_FORMATS.items()]) + +FORMAT_GENERAL = BUILTIN_FORMATS[0] +FORMAT_TEXT = BUILTIN_FORMATS[49] +FORMAT_NUMBER = BUILTIN_FORMATS[1] +FORMAT_NUMBER_00 = BUILTIN_FORMATS[2] +FORMAT_NUMBER_COMMA_SEPARATED1 = BUILTIN_FORMATS[4] +FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-' +FORMAT_PERCENTAGE = BUILTIN_FORMATS[9] +FORMAT_PERCENTAGE_00 = BUILTIN_FORMATS[10] +FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd' +FORMAT_DATE_YYMMDD = 'yy-mm-dd' +FORMAT_DATE_DDMMYY = 'dd/mm/yy' +FORMAT_DATE_DMYSLASH = 'd/m/y' +FORMAT_DATE_DMYMINUS = 'd-m-y' +FORMAT_DATE_DMMINUS = 'd-m' +FORMAT_DATE_MYMINUS = 'm-y' +FORMAT_DATE_XLSX14 = BUILTIN_FORMATS[14] +FORMAT_DATE_XLSX15 = BUILTIN_FORMATS[15] +FORMAT_DATE_XLSX16 = BUILTIN_FORMATS[16] +FORMAT_DATE_XLSX17 = BUILTIN_FORMATS[17] +FORMAT_DATE_XLSX22 = BUILTIN_FORMATS[22] +FORMAT_DATE_DATETIME = 'yyyy-mm-dd h:mm:ss' +FORMAT_DATE_TIME1 = BUILTIN_FORMATS[18] +FORMAT_DATE_TIME2 = BUILTIN_FORMATS[19] +FORMAT_DATE_TIME3 = BUILTIN_FORMATS[20] +FORMAT_DATE_TIME4 = BUILTIN_FORMATS[21] +FORMAT_DATE_TIME5 = BUILTIN_FORMATS[45] +FORMAT_DATE_TIME6 = BUILTIN_FORMATS[21] +FORMAT_DATE_TIME7 = 'i:s.S' +FORMAT_DATE_TIME8 = 'h:mm:ss@' +FORMAT_DATE_TIMEDELTA = '[hh]:mm:ss' +FORMAT_DATE_YYMMDDSLASH = 'yy/mm/dd@' +FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-' +FORMAT_CURRENCY_USD = '$#,##0_-' +FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-' + + +COLORS = r"\[(BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW)\]" +LITERAL_GROUP = r'".*?"' # anything in quotes +LOCALE_GROUP = r'\[(?!hh?\]|mm?\]|ss?\])[^\]]*\]' # anything in square brackets, except hours or minutes or seconds +STRIP_RE = re.compile(f"{LITERAL_GROUP}|{LOCALE_GROUP}") +TIMEDELTA_RE = re.compile(r'\[hh?\](:mm(:ss(\.0*)?)?)?|\[mm?\](:ss(\.0*)?)?|\[ss?\](\.0*)?', re.I) + + +# Spec 18.8.31 numFmts +# +ve;-ve;zero;text + +def is_date_format(fmt): + if fmt is None: + return False + fmt = fmt.split(";")[0] # only look at the first format + fmt = STRIP_RE.sub("", fmt) # ignore some formats + return re.search(r"(?<![_\\])[dmhysDMHYS]", fmt) is not None + + +def is_timedelta_format(fmt): + if fmt is None: + return False + fmt = fmt.split(";")[0] # only look at the first format + return TIMEDELTA_RE.search(fmt) is not None + + +def is_datetime(fmt): + """ + Return date, time or datetime + """ + if not is_date_format(fmt): + return + + DATE = TIME = False + + if any((x in fmt for x in 'dy')): + DATE = True + if any((x in fmt for x in 'hs')): + TIME = True + + if DATE and TIME: + return "datetime" + if DATE: + return "date" + return "time" + + +def is_builtin(fmt): + return fmt in BUILTIN_FORMATS.values() + + +def builtin_format_code(index): + """Return one of the standard format codes by index.""" + try: + fmt = BUILTIN_FORMATS[index] + except KeyError: + fmt = None + return fmt + + +def builtin_format_id(fmt): + """Return the id of a standard style.""" + return BUILTIN_FORMATS_REVERSE.get(fmt) + + +class NumberFormatDescriptor(String): + + def __set__(self, instance, value): + if value is None: + value = FORMAT_GENERAL + super().__set__(instance, value) + + +class NumberFormat(Serialisable): + + numFmtId = Integer() + formatCode = String() + + def __init__(self, + numFmtId=None, + formatCode=None, + ): + self.numFmtId = numFmtId + self.formatCode = formatCode + + +class NumberFormatList(Serialisable): + + count = Integer(allow_none=True) + numFmt = Sequence(expected_type=NumberFormat) + + __elements__ = ('numFmt',) + __attrs__ = ("count",) + + def __init__(self, + count=None, + numFmt=(), + ): + self.numFmt = numFmt + + + @property + def count(self): + return len(self.numFmt) + + + def __getitem__(self, idx): + return self.numFmt[idx] diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/protection.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/protection.py new file mode 100644 index 00000000..7c9238ce --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/protection.py @@ -0,0 +1,17 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import Bool +from openpyxl.descriptors.serialisable import Serialisable + + +class Protection(Serialisable): + """Protection options for use in styles.""" + + tagname = "protection" + + locked = Bool() + hidden = Bool() + + def __init__(self, locked=True, hidden=False): + self.locked = locked + self.hidden = hidden diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/proxy.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/proxy.py new file mode 100644 index 00000000..bee780cd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/proxy.py @@ -0,0 +1,62 @@ +# Copyright (c) 2010-2024 openpyxl + +from copy import copy + +from openpyxl.compat import deprecated + + +class StyleProxy: + """ + Proxy formatting objects so that they cannot be altered + """ + + __slots__ = ('__target') + + def __init__(self, target): + self.__target = target + + + def __repr__(self): + return repr(self.__target) + + + def __getattr__(self, attr): + return getattr(self.__target, attr) + + + def __setattr__(self, attr, value): + if attr != "_StyleProxy__target": + raise AttributeError("Style objects are immutable and cannot be changed." + "Reassign the style with a copy") + super().__setattr__(attr, value) + + + def __copy__(self): + """ + Return a copy of the proxied object. + """ + return copy(self.__target) + + + def __add__(self, other): + """ + Add proxied object to another instance and return the combined object + """ + return self.__target + other + + + @deprecated("Use copy(obj) or cell.obj = cell.obj + other") + def copy(self, **kw): + """Return a copy of the proxied object. Keyword args will be passed through""" + cp = copy(self.__target) + for k, v in kw.items(): + setattr(cp, k, v) + return cp + + + def __eq__(self, other): + return self.__target == other + + + def __ne__(self, other): + return not self == other diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/styleable.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/styleable.py new file mode 100644 index 00000000..2703096d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/styleable.py @@ -0,0 +1,151 @@ +# Copyright (c) 2010-2024 openpyxl + +from copy import copy + +from .numbers import ( + BUILTIN_FORMATS, + BUILTIN_FORMATS_MAX_SIZE, + BUILTIN_FORMATS_REVERSE, +) +from .proxy import StyleProxy +from .cell_style import StyleArray +from .named_styles import NamedStyle +from .builtins import styles + + +class StyleDescriptor: + + def __init__(self, collection, key): + self.collection = collection + self.key = key + + def __set__(self, instance, value): + coll = getattr(instance.parent.parent, self.collection) + if not getattr(instance, "_style"): + instance._style = StyleArray() + setattr(instance._style, self.key, coll.add(value)) + + + def __get__(self, instance, cls): + coll = getattr(instance.parent.parent, self.collection) + if not getattr(instance, "_style"): + instance._style = StyleArray() + idx = getattr(instance._style, self.key) + return StyleProxy(coll[idx]) + + +class NumberFormatDescriptor: + + key = "numFmtId" + collection = '_number_formats' + + def __set__(self, instance, value): + coll = getattr(instance.parent.parent, self.collection) + if value in BUILTIN_FORMATS_REVERSE: + idx = BUILTIN_FORMATS_REVERSE[value] + else: + idx = coll.add(value) + BUILTIN_FORMATS_MAX_SIZE + + if not getattr(instance, "_style"): + instance._style = StyleArray() + setattr(instance._style, self.key, idx) + + + def __get__(self, instance, cls): + if not getattr(instance, "_style"): + instance._style = StyleArray() + idx = getattr(instance._style, self.key) + if idx < BUILTIN_FORMATS_MAX_SIZE: + return BUILTIN_FORMATS.get(idx, "General") + coll = getattr(instance.parent.parent, self.collection) + return coll[idx - BUILTIN_FORMATS_MAX_SIZE] + + +class NamedStyleDescriptor: + + key = "xfId" + collection = "_named_styles" + + + def __set__(self, instance, value): + if not getattr(instance, "_style"): + instance._style = StyleArray() + coll = getattr(instance.parent.parent, self.collection) + if isinstance(value, NamedStyle): + style = value + if style not in coll: + instance.parent.parent.add_named_style(style) + elif value not in coll.names: + if value in styles: # is it builtin? + style = styles[value] + if style not in coll: + instance.parent.parent.add_named_style(style) + else: + raise ValueError("{0} is not a known style".format(value)) + else: + style = coll[value] + instance._style = copy(style.as_tuple()) + + + def __get__(self, instance, cls): + if not getattr(instance, "_style"): + instance._style = StyleArray() + idx = getattr(instance._style, self.key) + coll = getattr(instance.parent.parent, self.collection) + return coll.names[idx] + + +class StyleArrayDescriptor: + + def __init__(self, key): + self.key = key + + def __set__(self, instance, value): + if instance._style is None: + instance._style = StyleArray() + setattr(instance._style, self.key, value) + + + def __get__(self, instance, cls): + if instance._style is None: + return False + return bool(getattr(instance._style, self.key)) + + +class StyleableObject: + """ + Base class for styleble objects implementing proxy and lookup functions + """ + + font = StyleDescriptor('_fonts', "fontId") + fill = StyleDescriptor('_fills', "fillId") + border = StyleDescriptor('_borders', "borderId") + number_format = NumberFormatDescriptor() + protection = StyleDescriptor('_protections', "protectionId") + alignment = StyleDescriptor('_alignments', "alignmentId") + style = NamedStyleDescriptor() + quotePrefix = StyleArrayDescriptor('quotePrefix') + pivotButton = StyleArrayDescriptor('pivotButton') + + __slots__ = ('parent', '_style') + + def __init__(self, sheet, style_array=None): + self.parent = sheet + if style_array is not None: + style_array = StyleArray(style_array) + self._style = style_array + + + @property + def style_id(self): + if self._style is None: + self._style = StyleArray() + return self.parent.parent._cell_styles.add(self._style) + + + @property + def has_style(self): + if self._style is None: + return False + return any(self._style) + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py new file mode 100644 index 00000000..dfaf875d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py @@ -0,0 +1,274 @@ +# Copyright (c) 2010-2024 openpyxl + +from warnings import warn + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, +) +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.utils.indexed_list import IndexedList +from openpyxl.xml.constants import ARC_STYLE, SHEET_MAIN_NS +from openpyxl.xml.functions import fromstring + +from .builtins import styles +from .colors import ColorList +from .differential import DifferentialStyle +from .table import TableStyleList +from .borders import Border +from .fills import Fill +from .fonts import Font +from .numbers import ( + NumberFormatList, + BUILTIN_FORMATS, + BUILTIN_FORMATS_MAX_SIZE, + BUILTIN_FORMATS_REVERSE, + is_date_format, + is_timedelta_format, + builtin_format_code +) +from .named_styles import ( + _NamedCellStyleList, + NamedStyleList, + NamedStyle, +) +from .cell_style import CellStyle, CellStyleList + + +class Stylesheet(Serialisable): + + tagname = "styleSheet" + + numFmts = Typed(expected_type=NumberFormatList) + fonts = NestedSequence(expected_type=Font, count=True) + fills = NestedSequence(expected_type=Fill, count=True) + borders = NestedSequence(expected_type=Border, count=True) + cellStyleXfs = Typed(expected_type=CellStyleList) + cellXfs = Typed(expected_type=CellStyleList) + cellStyles = Typed(expected_type=_NamedCellStyleList) + dxfs = NestedSequence(expected_type=DifferentialStyle, count=True) + tableStyles = Typed(expected_type=TableStyleList, allow_none=True) + colors = Typed(expected_type=ColorList, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('numFmts', 'fonts', 'fills', 'borders', 'cellStyleXfs', + 'cellXfs', 'cellStyles', 'dxfs', 'tableStyles', 'colors') + + def __init__(self, + numFmts=None, + fonts=(), + fills=(), + borders=(), + cellStyleXfs=None, + cellXfs=None, + cellStyles=None, + dxfs=(), + tableStyles=None, + colors=None, + extLst=None, + ): + if numFmts is None: + numFmts = NumberFormatList() + self.numFmts = numFmts + self.number_formats = IndexedList() + self.fonts = fonts + self.fills = fills + self.borders = borders + if cellStyleXfs is None: + cellStyleXfs = CellStyleList() + self.cellStyleXfs = cellStyleXfs + if cellXfs is None: + cellXfs = CellStyleList() + self.cellXfs = cellXfs + if cellStyles is None: + cellStyles = _NamedCellStyleList() + self.cellStyles = cellStyles + + self.dxfs = dxfs + self.tableStyles = tableStyles + self.colors = colors + + self.cell_styles = self.cellXfs._to_array() + self.alignments = self.cellXfs.alignments + self.protections = self.cellXfs.prots + self._normalise_numbers() + self.named_styles = self._merge_named_styles() + + + @classmethod + def from_tree(cls, node): + # strip all attribs + attrs = dict(node.attrib) + for k in attrs: + del node.attrib[k] + return super().from_tree(node) + + + def _merge_named_styles(self): + """ + Merge named style names "cellStyles" with their associated styles + "cellStyleXfs" + """ + style_refs = self.cellStyles.remove_duplicates() + from_ref = [self._expand_named_style(style_ref) for style_ref in style_refs] + + return NamedStyleList(from_ref) + + + def _expand_named_style(self, style_ref): + """ + Expand a named style reference element to a + named style object by binding the relevant + objects from the stylesheet + """ + xf = self.cellStyleXfs[style_ref.xfId] + named_style = NamedStyle( + name=style_ref.name, + hidden=style_ref.hidden, + builtinId=style_ref.builtinId, + ) + + named_style.font = self.fonts[xf.fontId] + named_style.fill = self.fills[xf.fillId] + named_style.border = self.borders[xf.borderId] + if xf.numFmtId < BUILTIN_FORMATS_MAX_SIZE: + formats = BUILTIN_FORMATS + else: + formats = self.custom_formats + + if xf.numFmtId in formats: + named_style.number_format = formats[xf.numFmtId] + if xf.alignment: + named_style.alignment = xf.alignment + if xf.protection: + named_style.protection = xf.protection + + return named_style + + + def _split_named_styles(self, wb): + """ + Convert NamedStyle into separate CellStyle and Xf objects + + """ + for style in wb._named_styles: + self.cellStyles.cellStyle.append(style.as_name()) + self.cellStyleXfs.xf.append(style.as_xf()) + + + @property + def custom_formats(self): + return dict([(n.numFmtId, n.formatCode) for n in self.numFmts.numFmt]) + + + def _normalise_numbers(self): + """ + Rebase custom numFmtIds with a floor of 164 when reading stylesheet + And index datetime formats + """ + date_formats = set() + timedelta_formats = set() + custom = self.custom_formats + formats = self.number_formats + for idx, style in enumerate(self.cell_styles): + if style.numFmtId in custom: + fmt = custom[style.numFmtId] + if fmt in BUILTIN_FORMATS_REVERSE: # remove builtins + style.numFmtId = BUILTIN_FORMATS_REVERSE[fmt] + else: + style.numFmtId = formats.add(fmt) + BUILTIN_FORMATS_MAX_SIZE + else: + fmt = builtin_format_code(style.numFmtId) + if is_date_format(fmt): + # Create an index of which styles refer to datetimes + date_formats.add(idx) + if is_timedelta_format(fmt): + # Create an index of which styles refer to timedeltas + timedelta_formats.add(idx) + self.date_formats = date_formats + self.timedelta_formats = timedelta_formats + + + def to_tree(self, tagname=None, idx=None, namespace=None): + tree = super().to_tree(tagname, idx, namespace) + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + +def apply_stylesheet(archive, wb): + """ + Add styles to workbook if present + """ + try: + src = archive.read(ARC_STYLE) + except KeyError: + return wb + + node = fromstring(src) + stylesheet = Stylesheet.from_tree(node) + + if stylesheet.cell_styles: + + wb._borders = IndexedList(stylesheet.borders) + wb._fonts = IndexedList(stylesheet.fonts) + wb._fills = IndexedList(stylesheet.fills) + wb._differential_styles.styles = stylesheet.dxfs + wb._number_formats = stylesheet.number_formats + wb._protections = stylesheet.protections + wb._alignments = stylesheet.alignments + wb._table_styles = stylesheet.tableStyles + + # need to overwrite openpyxl defaults in case workbook has different ones + wb._cell_styles = stylesheet.cell_styles + wb._named_styles = stylesheet.named_styles + wb._date_formats = stylesheet.date_formats + wb._timedelta_formats = stylesheet.timedelta_formats + + for ns in wb._named_styles: + ns.bind(wb) + + else: + warn("Workbook contains no stylesheet, using openpyxl's defaults") + + if not wb._named_styles: + normal = styles['Normal'] + wb.add_named_style(normal) + warn("Workbook contains no default style, apply openpyxl's default") + + if stylesheet.colors is not None: + wb._colors = stylesheet.colors.index + + +def write_stylesheet(wb): + stylesheet = Stylesheet() + stylesheet.fonts = wb._fonts + stylesheet.fills = wb._fills + stylesheet.borders = wb._borders + stylesheet.dxfs = wb._differential_styles.styles + stylesheet.colors = ColorList(indexedColors=wb._colors) + + from .numbers import NumberFormat + fmts = [] + for idx, code in enumerate(wb._number_formats, BUILTIN_FORMATS_MAX_SIZE): + fmt = NumberFormat(idx, code) + fmts.append(fmt) + + stylesheet.numFmts.numFmt = fmts + + xfs = [] + for style in wb._cell_styles: + xf = CellStyle.from_array(style) + + if style.alignmentId: + xf.alignment = wb._alignments[style.alignmentId] + + if style.protectionId: + xf.protection = wb._protections[style.protectionId] + xfs.append(xf) + stylesheet.cellXfs = CellStyleList(xf=xfs) + + stylesheet._split_named_styles(wb) + stylesheet.tableStyles = wb._table_styles + + return stylesheet.to_tree() diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/table.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/table.py new file mode 100644 index 00000000..18307198 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/table.py @@ -0,0 +1,94 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + Bool, + Set, + Integer, + NoneSet, + String, + Sequence +) + +from .colors import Color + + +class TableStyleElement(Serialisable): + + tagname = "tableStyleElement" + + type = Set(values=(['wholeTable', 'headerRow', 'totalRow', 'firstColumn', + 'lastColumn', 'firstRowStripe', 'secondRowStripe', 'firstColumnStripe', + 'secondColumnStripe', 'firstHeaderCell', 'lastHeaderCell', + 'firstTotalCell', 'lastTotalCell', 'firstSubtotalColumn', + 'secondSubtotalColumn', 'thirdSubtotalColumn', 'firstSubtotalRow', + 'secondSubtotalRow', 'thirdSubtotalRow', 'blankRow', + 'firstColumnSubheading', 'secondColumnSubheading', + 'thirdColumnSubheading', 'firstRowSubheading', 'secondRowSubheading', + 'thirdRowSubheading', 'pageFieldLabels', 'pageFieldValues'])) + size = Integer(allow_none=True) + dxfId = Integer(allow_none=True) + + def __init__(self, + type=None, + size=None, + dxfId=None, + ): + self.type = type + self.size = size + self.dxfId = dxfId + + +class TableStyle(Serialisable): + + tagname = "tableStyle" + + name = String() + pivot = Bool(allow_none=True) + table = Bool(allow_none=True) + count = Integer(allow_none=True) + tableStyleElement = Sequence(expected_type=TableStyleElement, allow_none=True) + + __elements__ = ('tableStyleElement',) + + def __init__(self, + name=None, + pivot=None, + table=None, + count=None, + tableStyleElement=(), + ): + self.name = name + self.pivot = pivot + self.table = table + self.count = count + self.tableStyleElement = tableStyleElement + + +class TableStyleList(Serialisable): + + tagname = "tableStyles" + + defaultTableStyle = String(allow_none=True) + defaultPivotStyle = String(allow_none=True) + tableStyle = Sequence(expected_type=TableStyle, allow_none=True) + + __elements__ = ('tableStyle',) + __attrs__ = ("count", "defaultTableStyle", "defaultPivotStyle") + + def __init__(self, + count=None, + defaultTableStyle="TableStyleMedium9", + defaultPivotStyle="PivotStyleLight16", + tableStyle=(), + ): + self.defaultTableStyle = defaultTableStyle + self.defaultPivotStyle = defaultPivotStyle + self.tableStyle = tableStyle + + + @property + def count(self): + return len(self.tableStyle) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/__init__.py new file mode 100644 index 00000000..f6132636 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .cell import ( + absolute_coordinate, + cols_from_range, + column_index_from_string, + coordinate_to_tuple, + get_column_letter, + get_column_interval, + quote_sheetname, + range_boundaries, + range_to_tuple, + rows_from_range, +) + +from .formulas import FORMULAE diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/bound_dictionary.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/bound_dictionary.py new file mode 100644 index 00000000..20cbd1c4 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/bound_dictionary.py @@ -0,0 +1,26 @@ +# Copyright (c) 2010-2024 openpyxl + +from collections import defaultdict + + +class BoundDictionary(defaultdict): + """ + A default dictionary where elements are tightly coupled. + + The factory method is responsible for binding the parent object to the child. + + If a reference attribute is assigned then child objects will have the key assigned to this. + + Otherwise it's just a defaultdict. + """ + + def __init__(self, reference=None, *args, **kw): + self.reference = reference + super().__init__(*args, **kw) + + + def __getitem__(self, key): + value = super().__getitem__(key) + if self.reference is not None: + setattr(value, self.reference, key) + return value diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py new file mode 100644 index 00000000..f1ccc7d2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py @@ -0,0 +1,240 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Collection of utilities used within the package and also available for client code +""" +from functools import lru_cache +from itertools import chain, product +from string import ascii_uppercase, digits +import re + +from .exceptions import CellCoordinatesException + +# constants +COORD_RE = re.compile(r'^[$]?([A-Za-z]{1,3})[$]?(\d+)$') +COL_RANGE = """[A-Z]{1,3}:[A-Z]{1,3}:""" +ROW_RANGE = r"""\d+:\d+:""" +RANGE_EXPR = r""" +[$]?(?P<min_col>[A-Za-z]{1,3})? +[$]?(?P<min_row>\d+)? +(:[$]?(?P<max_col>[A-Za-z]{1,3})? +[$]?(?P<max_row>\d+)?)? +""" +ABSOLUTE_RE = re.compile('^' + RANGE_EXPR +'$', re.VERBOSE) +SHEET_TITLE = r""" +(('(?P<quoted>([^']|'')*)')|(?P<notquoted>[^'^ ^!]*))!""" +SHEETRANGE_RE = re.compile("""{0}(?P<cells>{1})(?=,?)""".format( + SHEET_TITLE, RANGE_EXPR), re.VERBOSE) + + +def get_column_interval(start, end): + """ + Given the start and end columns, return all the columns in the series. + + The start and end columns can be either column letters or 1-based + indexes. + """ + if isinstance(start, str): + start = column_index_from_string(start) + if isinstance(end, str): + end = column_index_from_string(end) + return [get_column_letter(x) for x in range(start, end + 1)] + + +def coordinate_from_string(coord_string): + """Convert a coordinate string like 'B12' to a tuple ('B', 12)""" + match = COORD_RE.match(coord_string) + if not match: + msg = f"Invalid cell coordinates ({coord_string})" + raise CellCoordinatesException(msg) + column, row = match.groups() + row = int(row) + if not row: + msg = f"There is no row 0 ({coord_string})" + raise CellCoordinatesException(msg) + return column, row + + +def absolute_coordinate(coord_string): + """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)""" + m = ABSOLUTE_RE.match(coord_string) + if not m: + raise ValueError(f"{coord_string} is not a valid coordinate range") + + d = m.groupdict('') + for k, v in d.items(): + if v: + d[k] = f"${v}" + + if d['max_col'] or d['max_row']: + fmt = "{min_col}{min_row}:{max_col}{max_row}" + else: + fmt = "{min_col}{min_row}" + return fmt.format(**d) + + +__decimal_to_alpha = [""] + list(ascii_uppercase) + +@lru_cache(maxsize=None) +def get_column_letter(col_idx): + """ + Convert decimal column position to its ASCII (base 26) form. + + Because column indices are 1-based, strides are actually pow(26, n) + 26 + Hence, a correction is applied between pow(26, n) and pow(26, 2) + 26 to + prevent and additional column letter being prepended + + "A" == 1 == pow(26, 0) + "Z" == 26 == pow(26, 0) + 26 // decimal equivalent 10 + "AA" == 27 == pow(26, 1) + 1 + "ZZ" == 702 == pow(26, 2) + 26 // decimal equivalent 100 + """ + + if not 1 <= col_idx <= 18278: + raise ValueError("Invalid column index {0}".format(col_idx)) + + result = [] + + if col_idx < 26: + return __decimal_to_alpha[col_idx] + + while col_idx: + col_idx, remainder = divmod(col_idx, 26) + result.insert(0, __decimal_to_alpha[remainder]) + if not remainder: + col_idx -= 1 + result.insert(0, "Z") + + return "".join(result) + + +__alpha_to_decimal = {letter:pos for pos, letter in enumerate(ascii_uppercase, 1)} +__powers = (1, 26, 676) + +@lru_cache(maxsize=None) +def column_index_from_string(col): + """ + Convert ASCII column name (base 26) to decimal with 1-based index + + Characters represent descending multiples of powers of 26 + + "AFZ" == 26 * pow(26, 0) + 6 * pow(26, 1) + 1 * pow(26, 2) + """ + error_msg = f"'{col}' is not a valid column name. Column names are from A to ZZZ" + if len(col) > 3: + raise ValueError(error_msg) + idx = 0 + col = reversed(col.upper()) + for letter, power in zip(col, __powers): + try: + pos = __alpha_to_decimal[letter] + except KeyError: + raise ValueError(error_msg) + idx += pos * power + if not 0 < idx < 18279: + raise ValueError(error_msg) + return idx + + +def range_boundaries(range_string): + """ + Convert a range string into a tuple of boundaries: + (min_col, min_row, max_col, max_row) + Cell coordinates will be converted into a range with the cell at both end + """ + msg = "{0} is not a valid coordinate or range".format(range_string) + m = ABSOLUTE_RE.match(range_string) + if not m: + raise ValueError(msg) + + min_col, min_row, sep, max_col, max_row = m.groups() + + if sep: + cols = min_col, max_col + rows = min_row, max_row + + if not ( + all(cols + rows) or + all(cols) and not any(rows) or + all(rows) and not any(cols) + ): + raise ValueError(msg) + + if min_col is not None: + min_col = column_index_from_string(min_col) + + if min_row is not None: + min_row = int(min_row) + + if max_col is not None: + max_col = column_index_from_string(max_col) + else: + max_col = min_col + + if max_row is not None: + max_row = int(max_row) + else: + max_row = min_row + + return min_col, min_row, max_col, max_row + + +def rows_from_range(range_string): + """ + Get individual addresses for every cell in a range. + Yields one row at a time. + """ + min_col, min_row, max_col, max_row = range_boundaries(range_string) + rows = range(min_row, max_row + 1) + cols = [get_column_letter(col) for col in range(min_col, max_col + 1)] + for row in rows: + yield tuple('{0}{1}'.format(col, row) for col in cols) + + +def cols_from_range(range_string): + """ + Get individual addresses for every cell in a range. + Yields one row at a time. + """ + min_col, min_row, max_col, max_row = range_boundaries(range_string) + rows = range(min_row, max_row+1) + cols = (get_column_letter(col) for col in range(min_col, max_col+1)) + for col in cols: + yield tuple('{0}{1}'.format(col, row) for row in rows) + + +def coordinate_to_tuple(coordinate): + """ + Convert an Excel style coordinate to (row, column) tuple + """ + for idx, c in enumerate(coordinate): + if c in digits: + break + col = coordinate[:idx] + row = coordinate[idx:] + return int(row), column_index_from_string(col) + + +def range_to_tuple(range_string): + """ + Convert a worksheet range to the sheetname and maximum and minimum + coordinate indices + """ + m = SHEETRANGE_RE.match(range_string) + if m is None: + raise ValueError("Value must be of the form sheetname!A1:E4") + sheetname = m.group("quoted") or m.group("notquoted") + cells = m.group("cells") + boundaries = range_boundaries(cells) + return sheetname, boundaries + + +def quote_sheetname(sheetname): + """ + Add quotes around sheetnames if they contain spaces. + """ + if "'" in sheetname: + sheetname = sheetname.replace("'", "''") + + sheetname = u"'{0}'".format(sheetname) + return sheetname diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py new file mode 100644 index 00000000..f56a4887 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py @@ -0,0 +1,87 @@ +# Copyright (c) 2010-2024 openpyxl + +from itertools import accumulate +import operator +import numpy +from openpyxl.compat.product import prod + + +def dataframe_to_rows(df, index=True, header=True): + """ + Convert a Pandas dataframe into something suitable for passing into a worksheet. + If index is True then the index will be included, starting one row below the header. + If header is True then column headers will be included starting one column to the right. + Formatting should be done by client code. + """ + from pandas import Timestamp + + if header: + if df.columns.nlevels > 1: + rows = expand_index(df.columns, header) + else: + rows = [list(df.columns.values)] + for row in rows: + n = [] + for v in row: + if isinstance(v, numpy.datetime64): + v = Timestamp(v) + n.append(v) + row = n + if index: + row = [None]*df.index.nlevels + row + yield row + + if index: + yield df.index.names + + expanded = ([v] for v in df.index) + if df.index.nlevels > 1: + expanded = expand_index(df.index) + + # Using the expanded index is preferable to df.itertuples(index=True) so that we have 'None' inserted where applicable + for (df_index, row) in zip(expanded, df.itertuples(index=False)): + row = list(row) + if index: + row = df_index + row + yield row + + +def expand_index(index, header=False): + """ + Expand axis or column Multiindex + For columns use header = True + For axes use header = False (default) + """ + + # For each element of the index, zip the members with the previous row + # If the 2 elements of the zipped list do not match, we can insert the new value into the row + # or if an earlier member was different, all later members should be added to the row + values = list(index.values) + previous_value = [None] * len(values[0]) + result = [] + + for value in values: + row = [None] * len(value) + + # Once there's a difference in member of an index with the prior index, we need to store all subsequent members in the row + prior_change = False + for idx, (current_index_member, previous_index_member) in enumerate(zip(value, previous_value)): + + if current_index_member != previous_index_member or prior_change: + row[idx] = current_index_member + prior_change = True + + previous_value = value + + # If this is for a row index, we're already returning a row so just yield + if not header: + yield row + else: + result.append(row) + + # If it's for a header, we need to transpose to get it in row order + # Example: result = [['A', 'A'], [None, 'B']] -> [['A', None], ['A', 'B']] + if header: + result = numpy.array(result).transpose().tolist() + for row in result: + yield row diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py new file mode 100644 index 00000000..bf7e5006 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py @@ -0,0 +1,140 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Manage Excel date weirdness.""" + +# Python stdlib imports +import datetime +from math import isnan +import re + + +# constants +MAC_EPOCH = datetime.datetime(1904, 1, 1) +WINDOWS_EPOCH = datetime.datetime(1899, 12, 30) +CALENDAR_WINDOWS_1900 = 2415018.5 # Julian date of WINDOWS_EPOCH +CALENDAR_MAC_1904 = 2416480.5 # Julian date of MAC_EPOCH +CALENDAR_WINDOWS_1900 = WINDOWS_EPOCH +CALENDAR_MAC_1904 = MAC_EPOCH +SECS_PER_DAY = 86400 + +ISO_FORMAT = '%Y-%m-%dT%H:%M:%SZ' +ISO_REGEX = re.compile(r''' +(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?T? +(?P<time>(?P<hour>\d{2}):(?P<minute>\d{2})(:(?P<second>\d{2})(?P<microsecond>\.\d{1,3})?)?)?Z?''', + re.VERBOSE) +ISO_DURATION = re.compile(r'PT((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+(\.\d{1,3})?)S)?') + + +def to_ISO8601(dt): + """Convert from a datetime to a timestamp string.""" + if hasattr(dt, "microsecond") and dt.microsecond: + return dt.isoformat(timespec="milliseconds") + return dt.isoformat() + + +def from_ISO8601(formatted_string): + """Convert from a timestamp string to a datetime object. According to + 18.17.4 in the specification the following ISO 8601 formats are + supported. + + Dates B.1.1 and B.2.1 + Times B.1.2 and B.2.2 + Datetimes B.1.3 and B.2.3 + + There is no concept of timedeltas in the specification, but Excel + writes them (in strict OOXML mode), so these are also understood. + """ + if not formatted_string: + return None + + match = ISO_REGEX.match(formatted_string) + if match and any(match.groups()): + parts = match.groupdict(0) + for key in ["year", "month", "day", "hour", "minute", "second"]: + if parts[key]: + parts[key] = int(parts[key]) + + if parts["microsecond"]: + parts["microsecond"] = int(float(parts['microsecond']) * 1_000_000) + + if not parts["date"]: + dt = datetime.time(parts['hour'], parts['minute'], parts['second'], parts["microsecond"]) + elif not parts["time"]: + dt = datetime.date(parts['year'], parts['month'], parts['day']) + else: + del parts["time"] + del parts["date"] + dt = datetime.datetime(**parts) + return dt + + match = ISO_DURATION.match(formatted_string) + if match and any(match.groups()): + parts = match.groupdict(0) + for key, val in parts.items(): + if val: + parts[key] = float(val) + return datetime.timedelta(**parts) + + raise ValueError("Invalid datetime value {}".format(formatted_string)) + + +def to_excel(dt, epoch=WINDOWS_EPOCH): + """Convert Python datetime to Excel serial""" + if isinstance(dt, datetime.time): + return time_to_days(dt) + if isinstance(dt, datetime.timedelta): + return timedelta_to_days(dt) + if isnan(dt.year): # Pandas supports Not a Date + return + + if not hasattr(dt, "date"): + dt = datetime.datetime.combine(dt, datetime.time()) + + # rebase on epoch and adjust for < 1900-03-01 + days = (dt - epoch).days + if 0 < days <= 60 and epoch == WINDOWS_EPOCH: + days -= 1 + return days + time_to_days(dt) + + +def from_excel(value, epoch=WINDOWS_EPOCH, timedelta=False): + """Convert Excel serial to Python datetime""" + if value is None: + return + + if timedelta: + td = datetime.timedelta(days=value) + if td.microseconds: + # round to millisecond precision + td = datetime.timedelta(seconds=td.total_seconds() // 1, + microseconds=round(td.microseconds, -3)) + return td + + day, fraction = divmod(value, 1) + diff = datetime.timedelta(milliseconds=round(fraction * SECS_PER_DAY * 1000)) + if 0 <= value < 1 and diff.days == 0: + return days_to_time(diff) + if 0 < value < 60 and epoch == WINDOWS_EPOCH: + day += 1 + return epoch + datetime.timedelta(days=day) + diff + + +def time_to_days(value): + """Convert a time value to fractions of day""" + return ( + (value.hour * 3600) + + (value.minute * 60) + + value.second + + value.microsecond / 10**6 + ) / SECS_PER_DAY + + +def timedelta_to_days(value): + """Convert a timedelta value to fractions of a day""" + return value.total_seconds() / SECS_PER_DAY + + +def days_to_time(value): + mins, seconds = divmod(value.seconds, 60) + hours, mins = divmod(mins, 60) + return datetime.time(hours, mins, seconds, value.microseconds) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/escape.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/escape.py new file mode 100644 index 00000000..a8985343 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/escape.py @@ -0,0 +1,43 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +OOXML has non-standard escaping for characters < \031 +""" + +import re + + +def escape(value): + r""" + Convert ASCII < 31 to OOXML: \n == _x + hex(ord(\n)) + _ + """ + + CHAR_REGEX = re.compile(r"[\001-\031]") + + def _sub(match): + """ + Callback to escape chars + """ + return "_x{:0>4x}_".format(ord(match.group(0))) + + return CHAR_REGEX.sub(_sub, value) + + +def unescape(value): + r""" + Convert escaped strings to ASCIII: _x000a_ == \n + """ + + + ESCAPED_REGEX = re.compile("_x([0-9A-Fa-f]{4})_") + + def _sub(match): + """ + Callback to unescape chars + """ + return chr(int(match.group(1), 16)) + + if "_x" in value: + value = ESCAPED_REGEX.sub(_sub, value) + + return value diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/exceptions.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/exceptions.py new file mode 100644 index 00000000..7b05f742 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/exceptions.py @@ -0,0 +1,34 @@ +# Copyright (c) 2010-2024 openpyxl + + +"""Definitions for openpyxl shared exception classes.""" + + +class CellCoordinatesException(Exception): + """Error for converting between numeric and A1-style cell references.""" + + +class IllegalCharacterError(Exception): + """The data submitted which cannot be used directly in Excel files. It + must be removed or escaped.""" + + +class NamedRangeException(Exception): + """Error for badly formatted named ranges.""" + + +class SheetTitleException(Exception): + """Error for bad sheet names.""" + + +class InvalidFileException(Exception): + """Error for trying to open a non-ooxml file.""" + + +class ReadOnlyWorkbookException(Exception): + """Error for trying to modify a read-only workbook""" + + +class WorkbookAlreadySaved(Exception): + """Error when attempting to perform operations on a dump workbook + while it has already been dumped once""" diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/formulas.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/formulas.py new file mode 100644 index 00000000..aab9961b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/formulas.py @@ -0,0 +1,24 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +List of builtin formulae +""" + +FORMULAE = ("CUBEKPIMEMBER", "CUBEMEMBER", "CUBEMEMBERPROPERTY", "CUBERANKEDMEMBER", "CUBESET", "CUBESETCOUNT", "CUBEVALUE", "DAVERAGE", "DCOUNT", "DCOUNTA", "DGET", "DMAX", "DMIN", "DPRODUCT", "DSTDEV", "DSTDEVP", "DSUM", "DVAR", "DVARP", "DATE", "DATEDIF", "DATEVALUE", "DAY", "DAYS360", "EDATE", "EOMONTH", "HOUR", "MINUTE", "MONTH", "NETWORKDAYS", "NETWORKDAYS.INTL", "NOW", "SECOND", "TIME", "TIMEVALUE", "TODAY", "WEEKDAY", "WEEKNUM", "WORKDAY", "WORKDAY.INTL", "YEAR", "YEARFRAC", "BESSELI", "BESSELJ", "BESSELK", "BESSELY", "BIN2DEC", "BIN2HEX", "BIN2OCT", "COMPLEX", "CONVERT", "DEC2BIN", "DEC2HEX", "DEC2OCT", "DELTA", "ERF", "ERFC", "GESTEP", "HEX2BIN", "HEX2DEC", "HEX2OCT", "IMABS", "IMAGINARY", "IMARGUMENT", "IMCONJUGATE", "IMCOS", "IMDIV", "IMEXP", "IMLN", "IMLOG10", "IMLOG2", "IMPOWER", "IMPRODUCT", "IMREAL", "IMSIN", "IMSQRT", "IMSUB", "IMSUM", "OCT2BIN", "OCT2DEC", "OCT2HEX", "ACCRINT", "ACCRINTM", "AMORDEGRC", "AMORLINC", "COUPDAYBS", "COUPDAYS", "COUPDAYSNC", "COUPNCD", "COUPNUM", "COUPPCD", "CUMIPMT", "CUMPRINC", "DB", "DDB", "DISC", "DOLLARDE", "DOLLARFR", "DURATION", "EFFECT", "FV", "FVSCHEDULE", "INTRATE", "IPMT", "IRR", "ISPMT", "MDURATION", "MIRR", "NOMINAL", "NPER", "NPV", "ODDFPRICE", "ODDFYIELD", "ODDLPRICE", "ODDLYIELD", "PMT", "PPMT", "PRICE", "PRICEDISC", "PRICEMAT", "PV", "RATE", "RECEIVED", "SLN", "SYD", "TBILLEQ", "TBILLPRICE", "TBILLYIELD", "VDB", "XIRR", "XNPV", "YIELD", "YIELDDISC", "YIELDMAT", "CELL", "ERROR.TYPE", "INFO", "ISBLANK", "ISERR", "ISERROR", "ISEVEN", "ISLOGICAL", "ISNA", "ISNONTEXT", "ISNUMBER", "ISODD", "ISREF", "ISTEXT", "N", "NA", "TYPE", "AND", "FALSE", "IF", "IFERROR", "NOT", "OR", "TRUE", "ADDRESS", "AREAS", "CHOOSE", "COLUMN", "COLUMNS", "GETPIVOTDATA", "HLOOKUP", "HYPERLINK", "INDEX", "INDIRECT", "LOOKUP", "MATCH", "OFFSET", "ROW", "ROWS", "RTD", "TRANSPOSE", "VLOOKUP", "ABS", "ACOS", "ACOSH", "ASIN", "ASINH", "ATAN", "ATAN2", "ATANH", "CEILING", "COMBIN", "COS", "COSH", "DEGREES", "ECMA.CEILING", "EVEN", "EXP", "FACT", "FACTDOUBLE", "FLOOR", "GCD", "INT", "ISO.CEILING", "LCM", "LN", "LOG", "LOG10", "MDETERM", "MINVERSE", "MMULT", "MOD", "MROUND", "MULTINOMIAL", "ODD", "PI", "POWER", "PRODUCT", "QUOTIENT", "RADIANS", "RAND", "RANDBETWEEN", "ROMAN", "ROUND", "ROUNDDOWN", "ROUNDUP", "SERIESSUM", "SIGN", "SIN", "SINH", "SQRT", "SQRTPI", "SUBTOTAL", "SUM", "SUMIF", "SUMIFS", "SUMPRODUCT", "SUMSQ", "SUMX2MY2", "SUMX2PY2", "SUMXMY2", "TAN", "TANH", "TRUNC", "AVEDEV", "AVERAGE", "AVERAGEA", "AVERAGEIF", "AVERAGEIFS", "BETADIST", "BETAINV", "BINOMDIST", "CHIDIST", "CHIINV", "CHITEST", "CONFIDENCE", "CORREL", "COUNT", "COUNTA", "COUNTBLANK", "COUNTIF", "COUNTIFS", "COVAR", "CRITBINOM", "DEVSQ", "EXPONDIST", "FDIST", "FINV", "FISHER", "FISHERINV", "FORECAST", "FREQUENCY", "FTEST", "GAMMADIST", "GAMMAINV", "GAMMALN", "GEOMEAN", "GROWTH", "HARMEAN", "HYPGEOMDIST", "INTERCEPT", "KURT", "LARGE", "LINEST", "LOGEST", "LOGINV", "LOGNORMDIST", "MAX", "MAXA", "MEDIAN", "MIN", "MINA", "MODE", "NEGBINOMDIST", "NORMDIST", "NORMINV", "NORMSDIST", "NORMSINV", "PEARSON", "PERCENTILE", "PERCENTRANK", "PERMUT", "POISSON", "PROB", "QUARTILE", "RANK", "RSQ", "SKEW", "SLOPE", "SMALL", "STANDARDIZE", "STDEV", "STDEVA", "STDEVP", "STDEVPA", "STEYX", "TDIST", "TINV", "TREND", "TRIMMEAN", "TTEST", "VAR", "VARA", "VARP", "VARPA", "WEIBULL", "ZTEST", "ASC", "BAHTTEXT", "CHAR", "CLEAN", "CODE", "CONCATENATE", "DOLLAR", "EXACT", "FIND", "FINDB", "FIXED", "JIS", "LEFT", "LEFTB", "LEN", "LENB", "LOWER", "MID", "MIDB", "PHONETIC", "PROPER", "REPLACE", "REPLACEB", "REPT", "RIGHT", "RIGHTB", "SEARCH", "SEARCHB", "SUBSTITUTE", "T", "TEXT", "TRIM", "UPPER", "VALUE") + +FORMULAE = frozenset(FORMULAE) + + +from openpyxl.formula import Tokenizer + + +def validate(formula): + """ + Utility function for checking whether a formula is syntactically correct + """ + assert formula.startswith("=") + formula = Tokenizer(formula) + for t in formula.items: + if t.type == "FUNC" and t.subtype == "OPEN": + if not t.value.startswith("_xlfn.") and t.value[:-1] not in FORMULAE: + raise ValueError(f"Unknown function {t.value} in {formula.formula}. The function may need a prefix") diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/indexed_list.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/indexed_list.py new file mode 100644 index 00000000..753acf09 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/indexed_list.py @@ -0,0 +1,49 @@ +# Copyright (c) 2010-2024 openpyxl + + +class IndexedList(list): + """ + List with optimised access by value + Based on Alex Martelli's recipe + + http://code.activestate.com/recipes/52303-the-auxiliary-dictionary-idiom-for-sequences-with-/ + """ + + _dict = {} + + def __init__(self, iterable=None): + self.clean = True + self._dict = {} + if iterable is not None: + self.clean = False + for idx, val in enumerate(iterable): + self._dict[val] = idx + list.append(self, val) + + def _rebuild_dict(self): + self._dict = {} + idx = 0 + for value in self: + if value not in self._dict: + self._dict[value] = idx + idx += 1 + self.clean = True + + def __contains__(self, value): + if not self.clean: + self._rebuild_dict() + return value in self._dict + + def index(self, value): + if value in self: + return self._dict[value] + raise ValueError + + def append(self, value): + if value not in self._dict: + self._dict[value] = len(self) + list.append(self, value) + + def add(self, value): + self.append(value) + return self._dict[value] diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/inference.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/inference.py new file mode 100644 index 00000000..aff02a2b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/inference.py @@ -0,0 +1,60 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Type inference functions +""" +import datetime +import re + +from openpyxl.styles import numbers + +PERCENT_REGEX = re.compile(r'^(?P<number>\-?[0-9]*\.?[0-9]*\s?)\%$') +TIME_REGEX = re.compile(r""" +^(?: # HH:MM and HH:MM:SS +(?P<hour>[0-1]{0,1}[0-9]{2}): +(?P<minute>[0-5][0-9]):? +(?P<second>[0-5][0-9])?$) +| +^(?: # MM:SS. +([0-5][0-9]): +([0-5][0-9])?\. +(?P<microsecond>\d{1,6})) +""", re.VERBOSE) +NUMBER_REGEX = re.compile(r'^-?([\d]|[\d]+\.[\d]*|\.[\d]+|[1-9][\d]+\.?[\d]*)((E|e)[-+]?[\d]+)?$') + + +def cast_numeric(value): + """Explicitly convert a string to a numeric value""" + if NUMBER_REGEX.match(value): + try: + return int(value) + except ValueError: + return float(value) + + +def cast_percentage(value): + """Explicitly convert a string to numeric value and format as a + percentage""" + match = PERCENT_REGEX.match(value) + if match: + return float(match.group('number')) / 100 + + + +def cast_time(value): + """Explicitly convert a string to a number and format as datetime or + time""" + match = TIME_REGEX.match(value) + if match: + if match.group("microsecond") is not None: + value = value[:12] + pattern = "%M:%S.%f" + #fmt = numbers.FORMAT_DATE_TIME5 + elif match.group('second') is None: + #fmt = numbers.FORMAT_DATE_TIME3 + pattern = "%H:%M" + else: + pattern = "%H:%M:%S" + #fmt = numbers.FORMAT_DATE_TIME6 + value = datetime.datetime.strptime(value, pattern) + return value.time() diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/protection.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/protection.py new file mode 100644 index 00000000..cc7707ee --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/protection.py @@ -0,0 +1,22 @@ +# Copyright (c) 2010-2024 openpyxl + + +def hash_password(plaintext_password=''): + """ + Create a password hash from a given string for protecting a worksheet + only. This will not work for encrypting a workbook. + + This method is based on the algorithm provided by + Daniel Rentz of OpenOffice and the PEAR package + Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>. + See also http://blogs.msdn.com/b/ericwhite/archive/2008/02/23/the-legacy-hashing-algorithm-in-open-xml.aspx + """ + password = 0x0000 + for idx, char in enumerate(plaintext_password, 1): + value = ord(char) << idx + rotated_bits = value >> 15 + value &= 0x7fff + password ^= (value | rotated_bits) + password ^= len(plaintext_password) + password ^= 0xCE4B + return str(hex(password)).upper()[2:] diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/units.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/units.py new file mode 100644 index 00000000..19f23c5b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/units.py @@ -0,0 +1,108 @@ + +# Copyright (c) 2010-2024 openpyxl + +import math + + +#constants + +DEFAULT_ROW_HEIGHT = 15. # Default row height measured in point size. +BASE_COL_WIDTH = 8 # in characters +DEFAULT_COLUMN_WIDTH = BASE_COL_WIDTH + 5 +# = baseColumnWidth + {margin padding (2 pixels on each side, totalling 4 pixels)} + {gridline (1pixel)} + + +DEFAULT_LEFT_MARGIN = 0.7 # in inches, = right margin +DEFAULT_TOP_MARGIN = 0.7874 # in inches = bottom margin +DEFAULT_HEADER = 0.3 # in inches + + +# Conversion functions +""" +From the ECMA Spec (4th Edition part 1) +Page setup: "Left Page Margin in inches" p. 1647 + +Docs from +http://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/ + +See also http://msdn.microsoft.com/en-us/library/dd560821(v=office.12).aspx + +dxa: The main unit in OOXML is a twentieth of a point. Also called twips. +pt: point. In Excel there are 72 points to an inch +hp: half-points are used to specify font sizes. A font-size of 12pt equals 24 half points +pct: Half-points are used to specify font sizes. A font-size of 12pt equals 24 half points + +EMU: English Metric Unit, EMUs are used for coordinates in vector-based +drawings and embedded pictures. One inch equates to 914400 EMUs and a +centimeter is 360000. For bitmaps the default resolution is 96 dpi (known as +PixelsPerInch in Excel). Spec p. 1122 + +For radial geometry Excel uses integer units of 1/60000th of a degree. +""" + + + +def inch_to_dxa(value): + """1 inch = 72 * 20 dxa""" + return int(value * 20 * 72) + +def dxa_to_inch(value): + return value / 72 / 20 + + +def dxa_to_cm(value): + return 2.54 * dxa_to_inch(value) + +def cm_to_dxa(value): + emu = cm_to_EMU(value) + inch = EMU_to_inch(emu) + return inch_to_dxa(inch) + + +def pixels_to_EMU(value): + """1 pixel = 9525 EMUs""" + return int(value * 9525) + +def EMU_to_pixels(value): + return round(value / 9525) + + +def cm_to_EMU(value): + """1 cm = 360000 EMUs""" + return int(value * 360000) + +def EMU_to_cm(value): + return round(value / 360000, 4) + + +def inch_to_EMU(value): + """1 inch = 914400 EMUs""" + return int(value * 914400) + +def EMU_to_inch(value): + return round(value / 914400, 4) + + +def pixels_to_points(value, dpi=96): + """96 dpi, 72i""" + return value * 72 / dpi + + +def points_to_pixels(value, dpi=96): + return int(math.ceil(value * dpi / 72)) + + +def degrees_to_angle(value): + """1 degree = 60000 angles""" + return int(round(value * 60000)) + + +def angle_to_degrees(value): + return round(value / 60000, 2) + + +def short_color(color): + """ format a color to its short size """ + if len(color) > 6: + return color[2:] + return color diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/__init__.py new file mode 100644 index 00000000..8ae4d80d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .workbook import Workbook diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/_writer.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/_writer.py new file mode 100644 index 00000000..1aa6aacf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/_writer.py @@ -0,0 +1,197 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Write the workbook global settings to the archive.""" + +from openpyxl.utils import quote_sheetname +from openpyxl.xml.constants import ( + ARC_APP, + ARC_CORE, + ARC_CUSTOM, + ARC_WORKBOOK, + PKG_REL_NS, + CUSTOMUI_NS, + ARC_ROOT_RELS, +) +from openpyxl.xml.functions import tostring, fromstring + +from openpyxl.packaging.relationship import Relationship, RelationshipList +from openpyxl.workbook.defined_name import ( + DefinedName, + DefinedNameList, +) +from openpyxl.workbook.external_reference import ExternalReference +from openpyxl.packaging.workbook import ChildSheet, WorkbookPackage, PivotCache +from openpyxl.workbook.properties import WorkbookProperties +from openpyxl.utils.datetime import CALENDAR_MAC_1904 + + +def get_active_sheet(wb): + """ + Return the index of the active sheet. + If the sheet set to active is hidden return the next visible sheet or None + """ + visible_sheets = [idx for idx, sheet in enumerate(wb._sheets) if sheet.sheet_state == "visible"] + if not visible_sheets: + raise IndexError("At least one sheet must be visible") + + idx = wb._active_sheet_index + sheet = wb.active + if sheet and sheet.sheet_state == "visible": + return idx + + for idx in visible_sheets[idx:]: + wb.active = idx + return idx + + return None + + +class WorkbookWriter: + + def __init__(self, wb): + self.wb = wb + self.rels = RelationshipList() + self.package = WorkbookPackage() + self.package.workbookProtection = wb.security + self.package.calcPr = wb.calculation + + + def write_properties(self): + + props = WorkbookProperties() # needs a mapping to the workbook for preservation + if self.wb.code_name is not None: + props.codeName = self.wb.code_name + if self.wb.excel_base_date == CALENDAR_MAC_1904: + props.date1904 = True + self.package.workbookPr = props + + + def write_worksheets(self): + for idx, sheet in enumerate(self.wb._sheets, 1): + sheet_node = ChildSheet(name=sheet.title, sheetId=idx, id="rId{0}".format(idx)) + rel = Relationship(type=sheet._rel_type, Target=sheet.path) + self.rels.append(rel) + + if not sheet.sheet_state == 'visible': + if len(self.wb._sheets) == 1: + raise ValueError("The only worksheet of a workbook cannot be hidden") + sheet_node.state = sheet.sheet_state + self.package.sheets.append(sheet_node) + + + def write_refs(self): + for link in self.wb._external_links: + # need to match a counter with a workbook's relations + rId = len(self.wb.rels) + 1 + rel = Relationship(type=link._rel_type, Target=link.path) + self.rels.append(rel) + ext = ExternalReference(id=rel.id) + self.package.externalReferences.append(ext) + + + def write_names(self): + defined_names = list(self.wb.defined_names.values()) + + for idx, sheet in enumerate(self.wb.worksheets): + quoted = quote_sheetname(sheet.title) + + # local names + if sheet.defined_names: + names = sheet.defined_names.values() + for n in names: + n.localSheetId = idx + defined_names.extend(names) + + if sheet.auto_filter: + name = DefinedName(name='_FilterDatabase', localSheetId=idx, hidden=True) + name.value = f"{quoted}!{sheet.auto_filter}" + defined_names.append(name) + + if sheet.print_titles: + name = DefinedName(name="Print_Titles", localSheetId=idx) + name.value = sheet.print_titles + defined_names.append(name) + + if sheet.print_area: + name = DefinedName(name="Print_Area", localSheetId=idx) + name.value = sheet.print_area + defined_names.append(name) + + self.package.definedNames = DefinedNameList(definedName=defined_names) + + + def write_pivots(self): + pivot_caches = set() + for pivot in self.wb._pivots: + if pivot.cache not in pivot_caches: + pivot_caches.add(pivot.cache) + c = PivotCache(cacheId=pivot.cacheId) + self.package.pivotCaches.append(c) + rel = Relationship(Type=pivot.cache.rel_type, Target=pivot.cache.path) + self.rels.append(rel) + c.id = rel.id + #self.wb._pivots = [] # reset + + + def write_views(self): + active = get_active_sheet(self.wb) + if self.wb.views: + self.wb.views[0].activeTab = active + self.package.bookViews = self.wb.views + + + def write(self): + """Write the core workbook xml.""" + + self.write_properties() + self.write_worksheets() + self.write_names() + self.write_pivots() + self.write_views() + self.write_refs() + + return tostring(self.package.to_tree()) + + + def write_rels(self): + """Write the workbook relationships xml.""" + + styles = Relationship(type='styles', Target='styles.xml') + self.rels.append(styles) + + theme = Relationship(type='theme', Target='theme/theme1.xml') + self.rels.append(theme) + + if self.wb.vba_archive: + vba = Relationship(type='', Target='vbaProject.bin') + vba.Type ='http://schemas.microsoft.com/office/2006/relationships/vbaProject' + self.rels.append(vba) + + return tostring(self.rels.to_tree()) + + + def write_root_rels(self): + """Write the package relationships""" + + rels = RelationshipList() + + rel = Relationship(type="officeDocument", Target=ARC_WORKBOOK) + rels.append(rel) + rel = Relationship(Type=f"{PKG_REL_NS}/metadata/core-properties", Target=ARC_CORE) + rels.append(rel) + + rel = Relationship(type="extended-properties", Target=ARC_APP) + rels.append(rel) + + if len(self.wb.custom_doc_props) >= 1: + rel = Relationship(type="custom-properties", Target=ARC_CUSTOM) + rels.append(rel) + + if self.wb.vba_archive is not None: + # See if there was a customUI relation and reuse it + xml = fromstring(self.wb.vba_archive.read(ARC_ROOT_RELS)) + root_rels = RelationshipList.from_tree(xml) + for rel in root_rels.find(CUSTOMUI_NS): + rels.append(rel) + + return tostring(rels.to_tree()) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/child.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/child.py new file mode 100644 index 00000000..19dd29fb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/child.py @@ -0,0 +1,166 @@ +# Copyright (c) 2010-2024 openpyxl + +import re +import warnings + +from openpyxl.worksheet.header_footer import HeaderFooter + +""" +Base class for worksheets, chartsheets, etc. that can be added to workbooks +""" + +INVALID_TITLE_REGEX = re.compile(r'[\\*?:/\[\]]') + + +def avoid_duplicate_name(names, value): + """ + Naive check to see whether name already exists. + If name does exist suggest a name using an incrementer + Duplicates are case insensitive + """ + # Check for an absolute match in which case we need to find an alternative + match = [n for n in names if n.lower() == value.lower()] + if match: + names = u",".join(names) + sheet_title_regex = re.compile(f'(?P<title>{re.escape(value)})(?P<count>\\d*),?', re.I) + matches = sheet_title_regex.findall(names) + if matches: + # use name, but append with the next highest integer + counts = [int(idx) for (t, idx) in matches if idx.isdigit()] + highest = 0 + if counts: + highest = max(counts) + value = u"{0}{1}".format(value, highest + 1) + return value + + +class _WorkbookChild: + + __title = "" + _id = None + _path = "{0}" + _parent = None + _default_title = "Sheet" + + def __init__(self, parent=None, title=None): + self._parent = parent + self.title = title or self._default_title + self.HeaderFooter = HeaderFooter() + + + def __repr__(self): + return '<{0} "{1}">'.format(self.__class__.__name__, self.title) + + + @property + def parent(self): + return self._parent + + + @property + def encoding(self): + return self._parent.encoding + + + @property + def title(self): + return self.__title + + + @title.setter + def title(self, value): + """ + Set a sheet title, ensuring it is valid. + Limited to 31 characters, no special characters. + Duplicate titles will be incremented numerically + """ + if not self._parent: + return + + if not value: + raise ValueError("Title must have at least one character") + + if hasattr(value, "decode"): + if not isinstance(value, str): + try: + value = value.decode("ascii") + except UnicodeDecodeError: + raise ValueError("Worksheet titles must be str") + + m = INVALID_TITLE_REGEX.search(value) + if m: + msg = "Invalid character {0} found in sheet title".format(m.group(0)) + raise ValueError(msg) + + if self.title is not None and self.title != value: + value = avoid_duplicate_name(self.parent.sheetnames, value) + + if len(value) > 31: + warnings.warn("Title is more than 31 characters. Some applications may not be able to read the file") + + self.__title = value + + + @property + def oddHeader(self): + return self.HeaderFooter.oddHeader + + + @oddHeader.setter + def oddHeader(self, value): + self.HeaderFooter.oddHeader = value + + + @property + def oddFooter(self): + return self.HeaderFooter.oddFooter + + + @oddFooter.setter + def oddFooter(self, value): + self.HeaderFooter.oddFooter = value + + + @property + def evenHeader(self): + return self.HeaderFooter.evenHeader + + + @evenHeader.setter + def evenHeader(self, value): + self.HeaderFooter.evenHeader = value + + + @property + def evenFooter(self): + return self.HeaderFooter.evenFooter + + + @evenFooter.setter + def evenFooter(self, value): + self.HeaderFooter.evenFooter = value + + + @property + def firstHeader(self): + return self.HeaderFooter.firstHeader + + + @firstHeader.setter + def firstHeader(self, value): + self.HeaderFooter.firstHeader = value + + + @property + def firstFooter(self): + return self.HeaderFooter.firstFooter + + + @firstFooter.setter + def firstFooter(self, value): + self.HeaderFooter.firstFooter = value + + + @property + def path(self): + return self._path.format(self._id) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/defined_name.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/defined_name.py new file mode 100644 index 00000000..15f0bd30 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/defined_name.py @@ -0,0 +1,189 @@ +# Copyright (c) 2010-2024 openpyxl + +from collections import defaultdict +import re + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + String, + Integer, + Bool, + Sequence, + Descriptor, +) +from openpyxl.compat import safe_string +from openpyxl.formula import Tokenizer +from openpyxl.utils.cell import SHEETRANGE_RE + +RESERVED = frozenset(["Print_Area", "Print_Titles", "Criteria", + "_FilterDatabase", "Extract", "Consolidate_Area", + "Sheet_Title"]) + +_names = "|".join(RESERVED) +RESERVED_REGEX = re.compile(r"^_xlnm\.(?P<name>{0})".format(_names)) + + +class DefinedName(Serialisable): + + tagname = "definedName" + + name = String() # unique per workbook/worksheet + comment = String(allow_none=True) + customMenu = String(allow_none=True) + description = String(allow_none=True) + help = String(allow_none=True) + statusBar = String(allow_none=True) + localSheetId = Integer(allow_none=True) + hidden = Bool(allow_none=True) + function = Bool(allow_none=True) + vbProcedure = Bool(allow_none=True) + xlm = Bool(allow_none=True) + functionGroupId = Integer(allow_none=True) + shortcutKey = String(allow_none=True) + publishToServer = Bool(allow_none=True) + workbookParameter = Bool(allow_none=True) + attr_text = Descriptor() + value = Alias("attr_text") + + + def __init__(self, + name=None, + comment=None, + customMenu=None, + description=None, + help=None, + statusBar=None, + localSheetId=None, + hidden=None, + function=None, + vbProcedure=None, + xlm=None, + functionGroupId=None, + shortcutKey=None, + publishToServer=None, + workbookParameter=None, + attr_text=None + ): + self.name = name + self.comment = comment + self.customMenu = customMenu + self.description = description + self.help = help + self.statusBar = statusBar + self.localSheetId = localSheetId + self.hidden = hidden + self.function = function + self.vbProcedure = vbProcedure + self.xlm = xlm + self.functionGroupId = functionGroupId + self.shortcutKey = shortcutKey + self.publishToServer = publishToServer + self.workbookParameter = workbookParameter + self.attr_text = attr_text + + + @property + def type(self): + tok = Tokenizer("=" + self.value) + parsed = tok.items[0] + if parsed.type == "OPERAND": + return parsed.subtype + return parsed.type + + + @property + def destinations(self): + if self.type == "RANGE": + tok = Tokenizer("=" + self.value) + for part in tok.items: + if part.subtype == "RANGE": + m = SHEETRANGE_RE.match(part.value) + sheetname = m.group('notquoted') or m.group('quoted') + yield sheetname, m.group('cells') + + + @property + def is_reserved(self): + m = RESERVED_REGEX.match(self.name) + if m: + return m.group("name") + + + @property + def is_external(self): + return re.compile(r"^\[\d+\].*").match(self.value) is not None + + + def __iter__(self): + for key in self.__attrs__: + if key == "attr_text": + continue + v = getattr(self, key) + if v is not None: + if v in RESERVED: + v = "_xlnm." + v + yield key, safe_string(v) + + +class DefinedNameDict(dict): + + """ + Utility class for storing defined names. + Allows access by name and separation of global and scoped names + """ + + def __setitem__(self, key, value): + if not isinstance(value, DefinedName): + raise TypeError("Value must be a an instance of DefinedName") + elif value.name != key: + raise ValueError("Key must be the same as the name") + super().__setitem__(key, value) + + + def add(self, value): + """ + Add names without worrying about key and name matching. + """ + self[value.name] = value + + +class DefinedNameList(Serialisable): + + tagname = "definedNames" + + definedName = Sequence(expected_type=DefinedName) + + + def __init__(self, definedName=()): + self.definedName = definedName + + + def by_sheet(self): + """ + Break names down into sheet locals and globals + """ + names = defaultdict(DefinedNameDict) + for defn in self.definedName: + if defn.localSheetId is None: + if defn.name in ("_xlnm.Print_Titles", "_xlnm.Print_Area", "_xlnm._FilterDatabase"): + continue + names["global"][defn.name] = defn + else: + sheet = int(defn.localSheetId) + names[sheet][defn.name] = defn + return names + + + def _duplicate(self, defn): + """ + Check for whether DefinedName with the same name and scope already + exists + """ + for d in self.definedName: + if d.name == defn.name and d.localSheetId == defn.localSheetId: + return True + + + def __len__(self): + return len(self.definedName) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/__init__.py new file mode 100644 index 00000000..c3cb6211 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2010-2024 openpyxl + +from .external import ExternalLink diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/external.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/external.py new file mode 100644 index 00000000..7e2e5b20 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/external.py @@ -0,0 +1,190 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Bool, + Integer, + NoneSet, + Sequence, +) +from openpyxl.descriptors.excel import Relation +from openpyxl.descriptors.nested import NestedText +from openpyxl.descriptors.sequence import NestedSequence, ValueSequence + +from openpyxl.packaging.relationship import ( + Relationship, + get_rels_path, + get_dependents + ) +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.xml.functions import fromstring + + +"""Manage links to external Workbooks""" + + +class ExternalCell(Serialisable): + + r = String() + t = NoneSet(values=(['b', 'd', 'n', 'e', 's', 'str', 'inlineStr'])) + vm = Integer(allow_none=True) + v = NestedText(allow_none=True, expected_type=str) + + def __init__(self, + r=None, + t=None, + vm=None, + v=None, + ): + self.r = r + self.t = t + self.vm = vm + self.v = v + + +class ExternalRow(Serialisable): + + r = Integer() + cell = Sequence(expected_type=ExternalCell) + + __elements__ = ('cell',) + + def __init__(self, + r=(), + cell=None, + ): + self.r = r + self.cell = cell + + +class ExternalSheetData(Serialisable): + + sheetId = Integer() + refreshError = Bool(allow_none=True) + row = Sequence(expected_type=ExternalRow) + + __elements__ = ('row',) + + def __init__(self, + sheetId=None, + refreshError=None, + row=(), + ): + self.sheetId = sheetId + self.refreshError = refreshError + self.row = row + + +class ExternalSheetDataSet(Serialisable): + + sheetData = Sequence(expected_type=ExternalSheetData, ) + + __elements__ = ('sheetData',) + + def __init__(self, + sheetData=None, + ): + self.sheetData = sheetData + + +class ExternalSheetNames(Serialisable): + + sheetName = ValueSequence(expected_type=str) + + __elements__ = ('sheetName',) + + def __init__(self, + sheetName=(), + ): + self.sheetName = sheetName + + +class ExternalDefinedName(Serialisable): + + tagname = "definedName" + + name = String() + refersTo = String(allow_none=True) + sheetId = Integer(allow_none=True) + + def __init__(self, + name=None, + refersTo=None, + sheetId=None, + ): + self.name = name + self.refersTo = refersTo + self.sheetId = sheetId + + +class ExternalBook(Serialisable): + + tagname = "externalBook" + + sheetNames = Typed(expected_type=ExternalSheetNames, allow_none=True) + definedNames = NestedSequence(expected_type=ExternalDefinedName) + sheetDataSet = Typed(expected_type=ExternalSheetDataSet, allow_none=True) + id = Relation() + + __elements__ = ('sheetNames', 'definedNames', 'sheetDataSet') + + def __init__(self, + sheetNames=None, + definedNames=(), + sheetDataSet=None, + id=None, + ): + self.sheetNames = sheetNames + self.definedNames = definedNames + self.sheetDataSet = sheetDataSet + self.id = id + + +class ExternalLink(Serialisable): + + tagname = "externalLink" + + _id = None + _path = "/xl/externalLinks/externalLink{0}.xml" + _rel_type = "externalLink" + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml" + + externalBook = Typed(expected_type=ExternalBook, allow_none=True) + file_link = Typed(expected_type=Relationship, allow_none=True) # link to external file + + __elements__ = ('externalBook', ) + + def __init__(self, + externalBook=None, + ddeLink=None, + oleLink=None, + extLst=None, + ): + self.externalBook = externalBook + # ignore other items for the moment. + + + def to_tree(self): + node = super().to_tree() + node.set("xmlns", SHEET_MAIN_NS) + return node + + + @property + def path(self): + return self._path.format(self._id) + + +def read_external_link(archive, book_path): + src = archive.read(book_path) + node = fromstring(src) + book = ExternalLink.from_tree(node) + + link_path = get_rels_path(book_path) + deps = get_dependents(archive, link_path) + book.file_link = deps[0] + + return book diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_reference.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_reference.py new file mode 100644 index 00000000..f05802da --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_reference.py @@ -0,0 +1,18 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence +) +from openpyxl.descriptors.excel import ( + Relation, +) + +class ExternalReference(Serialisable): + + tagname = "externalReference" + + id = Relation() + + def __init__(self, id): + self.id = id diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/function_group.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/function_group.py new file mode 100644 index 00000000..5d7e8557 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/function_group.py @@ -0,0 +1,36 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence, + String, + Integer, +) + +class FunctionGroup(Serialisable): + + tagname = "functionGroup" + + name = String() + + def __init__(self, + name=None, + ): + self.name = name + + +class FunctionGroupList(Serialisable): + + tagname = "functionGroups" + + builtInGroupCount = Integer(allow_none=True) + functionGroup = Sequence(expected_type=FunctionGroup, allow_none=True) + + __elements__ = ('functionGroup',) + + def __init__(self, + builtInGroupCount=16, + functionGroup=(), + ): + self.builtInGroupCount = builtInGroupCount + self.functionGroup = functionGroup diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/properties.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/properties.py new file mode 100644 index 00000000..bdc9d614 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/properties.py @@ -0,0 +1,151 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + String, + Float, + Integer, + Bool, + NoneSet, + Set, +) + +from openpyxl.descriptors.excel import Guid + + +class WorkbookProperties(Serialisable): + + tagname = "workbookPr" + + date1904 = Bool(allow_none=True) + dateCompatibility = Bool(allow_none=True) + showObjects = NoneSet(values=(['all', 'placeholders'])) + showBorderUnselectedTables = Bool(allow_none=True) + filterPrivacy = Bool(allow_none=True) + promptedSolutions = Bool(allow_none=True) + showInkAnnotation = Bool(allow_none=True) + backupFile = Bool(allow_none=True) + saveExternalLinkValues = Bool(allow_none=True) + updateLinks = NoneSet(values=(['userSet', 'never', 'always'])) + codeName = String(allow_none=True) + hidePivotFieldList = Bool(allow_none=True) + showPivotChartFilter = Bool(allow_none=True) + allowRefreshQuery = Bool(allow_none=True) + publishItems = Bool(allow_none=True) + checkCompatibility = Bool(allow_none=True) + autoCompressPictures = Bool(allow_none=True) + refreshAllConnections = Bool(allow_none=True) + defaultThemeVersion = Integer(allow_none=True) + + def __init__(self, + date1904=None, + dateCompatibility=None, + showObjects=None, + showBorderUnselectedTables=None, + filterPrivacy=None, + promptedSolutions=None, + showInkAnnotation=None, + backupFile=None, + saveExternalLinkValues=None, + updateLinks=None, + codeName=None, + hidePivotFieldList=None, + showPivotChartFilter=None, + allowRefreshQuery=None, + publishItems=None, + checkCompatibility=None, + autoCompressPictures=None, + refreshAllConnections=None, + defaultThemeVersion=None, + ): + self.date1904 = date1904 + self.dateCompatibility = dateCompatibility + self.showObjects = showObjects + self.showBorderUnselectedTables = showBorderUnselectedTables + self.filterPrivacy = filterPrivacy + self.promptedSolutions = promptedSolutions + self.showInkAnnotation = showInkAnnotation + self.backupFile = backupFile + self.saveExternalLinkValues = saveExternalLinkValues + self.updateLinks = updateLinks + self.codeName = codeName + self.hidePivotFieldList = hidePivotFieldList + self.showPivotChartFilter = showPivotChartFilter + self.allowRefreshQuery = allowRefreshQuery + self.publishItems = publishItems + self.checkCompatibility = checkCompatibility + self.autoCompressPictures = autoCompressPictures + self.refreshAllConnections = refreshAllConnections + self.defaultThemeVersion = defaultThemeVersion + + +class CalcProperties(Serialisable): + + tagname = "calcPr" + + calcId = Integer() + calcMode = NoneSet(values=(['manual', 'auto', 'autoNoTable'])) + fullCalcOnLoad = Bool(allow_none=True) + refMode = NoneSet(values=(['A1', 'R1C1'])) + iterate = Bool(allow_none=True) + iterateCount = Integer(allow_none=True) + iterateDelta = Float(allow_none=True) + fullPrecision = Bool(allow_none=True) + calcCompleted = Bool(allow_none=True) + calcOnSave = Bool(allow_none=True) + concurrentCalc = Bool(allow_none=True) + concurrentManualCount = Integer(allow_none=True) + forceFullCalc = Bool(allow_none=True) + + def __init__(self, + calcId=124519, + calcMode=None, + fullCalcOnLoad=True, + refMode=None, + iterate=None, + iterateCount=None, + iterateDelta=None, + fullPrecision=None, + calcCompleted=None, + calcOnSave=None, + concurrentCalc=None, + concurrentManualCount=None, + forceFullCalc=None, + ): + self.calcId = calcId + self.calcMode = calcMode + self.fullCalcOnLoad = fullCalcOnLoad + self.refMode = refMode + self.iterate = iterate + self.iterateCount = iterateCount + self.iterateDelta = iterateDelta + self.fullPrecision = fullPrecision + self.calcCompleted = calcCompleted + self.calcOnSave = calcOnSave + self.concurrentCalc = concurrentCalc + self.concurrentManualCount = concurrentManualCount + self.forceFullCalc = forceFullCalc + + +class FileVersion(Serialisable): + + tagname = "fileVersion" + + appName = String(allow_none=True) + lastEdited = String(allow_none=True) + lowestEdited = String(allow_none=True) + rupBuild = String(allow_none=True) + codeName = Guid(allow_none=True) + + def __init__(self, + appName=None, + lastEdited=None, + lowestEdited=None, + rupBuild=None, + codeName=None, + ): + self.appName = appName + self.lastEdited = lastEdited + self.lowestEdited = lowestEdited + self.rupBuild = rupBuild + self.codeName = codeName diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/protection.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/protection.py new file mode 100644 index 00000000..d77d64bb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/protection.py @@ -0,0 +1,163 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + String, + Float, + Integer, + Bool, + NoneSet, + Set, +) +from openpyxl.descriptors.excel import ( + ExtensionList, + HexBinary, + Guid, + Relation, + Base64Binary, +) +from openpyxl.utils.protection import hash_password + + +class WorkbookProtection(Serialisable): + + _workbook_password, _revisions_password = None, None + + tagname = "workbookPr" + + workbook_password = Alias("workbookPassword") + workbookPasswordCharacterSet = String(allow_none=True) + revision_password = Alias("revisionsPassword") + revisionsPasswordCharacterSet = String(allow_none=True) + lockStructure = Bool(allow_none=True) + lock_structure = Alias("lockStructure") + lockWindows = Bool(allow_none=True) + lock_windows = Alias("lockWindows") + lockRevision = Bool(allow_none=True) + lock_revision = Alias("lockRevision") + revisionsAlgorithmName = String(allow_none=True) + revisionsHashValue = Base64Binary(allow_none=True) + revisionsSaltValue = Base64Binary(allow_none=True) + revisionsSpinCount = Integer(allow_none=True) + workbookAlgorithmName = String(allow_none=True) + workbookHashValue = Base64Binary(allow_none=True) + workbookSaltValue = Base64Binary(allow_none=True) + workbookSpinCount = Integer(allow_none=True) + + __attrs__ = ('workbookPassword', 'workbookPasswordCharacterSet', 'revisionsPassword', + 'revisionsPasswordCharacterSet', 'lockStructure', 'lockWindows', 'lockRevision', + 'revisionsAlgorithmName', 'revisionsHashValue', 'revisionsSaltValue', + 'revisionsSpinCount', 'workbookAlgorithmName', 'workbookHashValue', + 'workbookSaltValue', 'workbookSpinCount') + + def __init__(self, + workbookPassword=None, + workbookPasswordCharacterSet=None, + revisionsPassword=None, + revisionsPasswordCharacterSet=None, + lockStructure=None, + lockWindows=None, + lockRevision=None, + revisionsAlgorithmName=None, + revisionsHashValue=None, + revisionsSaltValue=None, + revisionsSpinCount=None, + workbookAlgorithmName=None, + workbookHashValue=None, + workbookSaltValue=None, + workbookSpinCount=None, + ): + if workbookPassword is not None: + self.workbookPassword = workbookPassword + self.workbookPasswordCharacterSet = workbookPasswordCharacterSet + if revisionsPassword is not None: + self.revisionsPassword = revisionsPassword + self.revisionsPasswordCharacterSet = revisionsPasswordCharacterSet + self.lockStructure = lockStructure + self.lockWindows = lockWindows + self.lockRevision = lockRevision + self.revisionsAlgorithmName = revisionsAlgorithmName + self.revisionsHashValue = revisionsHashValue + self.revisionsSaltValue = revisionsSaltValue + self.revisionsSpinCount = revisionsSpinCount + self.workbookAlgorithmName = workbookAlgorithmName + self.workbookHashValue = workbookHashValue + self.workbookSaltValue = workbookSaltValue + self.workbookSpinCount = workbookSpinCount + + def set_workbook_password(self, value='', already_hashed=False): + """Set a password on this workbook.""" + if not already_hashed: + value = hash_password(value) + self._workbook_password = value + + @property + def workbookPassword(self): + """Return the workbook password value, regardless of hash.""" + return self._workbook_password + + @workbookPassword.setter + def workbookPassword(self, value): + """Set a workbook password directly, forcing a hash step.""" + self.set_workbook_password(value) + + def set_revisions_password(self, value='', already_hashed=False): + """Set a revision password on this workbook.""" + if not already_hashed: + value = hash_password(value) + self._revisions_password = value + + @property + def revisionsPassword(self): + """Return the revisions password value, regardless of hash.""" + return self._revisions_password + + @revisionsPassword.setter + def revisionsPassword(self, value): + """Set a revisions password directly, forcing a hash step.""" + self.set_revisions_password(value) + + @classmethod + def from_tree(cls, node): + """Don't hash passwords when deserialising from XML""" + self = super().from_tree(node) + if self.workbookPassword: + self.set_workbook_password(node.get('workbookPassword'), already_hashed=True) + if self.revisionsPassword: + self.set_revisions_password(node.get('revisionsPassword'), already_hashed=True) + return self + +# Backwards compatibility +DocumentSecurity = WorkbookProtection + + +class FileSharing(Serialisable): + + tagname = "fileSharing" + + readOnlyRecommended = Bool(allow_none=True) + userName = String(allow_none=True) + reservationPassword = HexBinary(allow_none=True) + algorithmName = String(allow_none=True) + hashValue = Base64Binary(allow_none=True) + saltValue = Base64Binary(allow_none=True) + spinCount = Integer(allow_none=True) + + def __init__(self, + readOnlyRecommended=None, + userName=None, + reservationPassword=None, + algorithmName=None, + hashValue=None, + saltValue=None, + spinCount=None, + ): + self.readOnlyRecommended = readOnlyRecommended + self.userName = userName + self.reservationPassword = reservationPassword + self.algorithmName = algorithmName + self.hashValue = hashValue + self.saltValue = saltValue + self.spinCount = spinCount diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/smart_tags.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/smart_tags.py new file mode 100644 index 00000000..873e98bf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/smart_tags.py @@ -0,0 +1,56 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence, + String, + Bool, + NoneSet, + +) + +class SmartTag(Serialisable): + + tagname = "smartTagType" + + namespaceUri = String(allow_none=True) + name = String(allow_none=True) + url = String(allow_none=True) + + def __init__(self, + namespaceUri=None, + name=None, + url=None, + ): + self.namespaceUri = namespaceUri + self.name = name + self.url = url + + +class SmartTagList(Serialisable): + + tagname = "smartTagTypes" + + smartTagType = Sequence(expected_type=SmartTag, allow_none=True) + + __elements__ = ('smartTagType',) + + def __init__(self, + smartTagType=(), + ): + self.smartTagType = smartTagType + + +class SmartTagProperties(Serialisable): + + tagname = "smartTagPr" + + embed = Bool(allow_none=True) + show = NoneSet(values=(['all', 'noIndicator'])) + + def __init__(self, + embed=None, + show=None, + ): + self.embed = embed + self.show = show diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/views.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/views.py new file mode 100644 index 00000000..bcbf0267 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/views.py @@ -0,0 +1,155 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Sequence, + String, + Float, + Integer, + Bool, + NoneSet, + Set, +) +from openpyxl.descriptors.excel import ( + ExtensionList, + Guid, +) + + +class BookView(Serialisable): + + tagname = "workbookView" + + visibility = NoneSet(values=(['visible', 'hidden', 'veryHidden'])) + minimized = Bool(allow_none=True) + showHorizontalScroll = Bool(allow_none=True) + showVerticalScroll = Bool(allow_none=True) + showSheetTabs = Bool(allow_none=True) + xWindow = Integer(allow_none=True) + yWindow = Integer(allow_none=True) + windowWidth = Integer(allow_none=True) + windowHeight = Integer(allow_none=True) + tabRatio = Integer(allow_none=True) + firstSheet = Integer(allow_none=True) + activeTab = Integer(allow_none=True) + autoFilterDateGrouping = Bool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + visibility="visible", + minimized=False, + showHorizontalScroll=True, + showVerticalScroll=True, + showSheetTabs=True, + xWindow=None, + yWindow=None, + windowWidth=None, + windowHeight=None, + tabRatio=600, + firstSheet=0, + activeTab=0, + autoFilterDateGrouping=True, + extLst=None, + ): + self.visibility = visibility + self.minimized = minimized + self.showHorizontalScroll = showHorizontalScroll + self.showVerticalScroll = showVerticalScroll + self.showSheetTabs = showSheetTabs + self.xWindow = xWindow + self.yWindow = yWindow + self.windowWidth = windowWidth + self.windowHeight = windowHeight + self.tabRatio = tabRatio + self.firstSheet = firstSheet + self.activeTab = activeTab + self.autoFilterDateGrouping = autoFilterDateGrouping + + +class CustomWorkbookView(Serialisable): + + tagname = "customWorkbookView" + + name = String() + guid = Guid() + autoUpdate = Bool(allow_none=True) + mergeInterval = Integer(allow_none=True) + changesSavedWin = Bool(allow_none=True) + onlySync = Bool(allow_none=True) + personalView = Bool(allow_none=True) + includePrintSettings = Bool(allow_none=True) + includeHiddenRowCol = Bool(allow_none=True) + maximized = Bool(allow_none=True) + minimized = Bool(allow_none=True) + showHorizontalScroll = Bool(allow_none=True) + showVerticalScroll = Bool(allow_none=True) + showSheetTabs = Bool(allow_none=True) + xWindow = Integer(allow_none=True) + yWindow = Integer(allow_none=True) + windowWidth = Integer() + windowHeight = Integer() + tabRatio = Integer(allow_none=True) + activeSheetId = Integer() + showFormulaBar = Bool(allow_none=True) + showStatusbar = Bool(allow_none=True) + showComments = NoneSet(values=(['commNone', 'commIndicator', + 'commIndAndComment'])) + showObjects = NoneSet(values=(['all', 'placeholders'])) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + name=None, + guid=None, + autoUpdate=None, + mergeInterval=None, + changesSavedWin=None, + onlySync=None, + personalView=None, + includePrintSettings=None, + includeHiddenRowCol=None, + maximized=None, + minimized=None, + showHorizontalScroll=None, + showVerticalScroll=None, + showSheetTabs=None, + xWindow=None, + yWindow=None, + windowWidth=None, + windowHeight=None, + tabRatio=None, + activeSheetId=None, + showFormulaBar=None, + showStatusbar=None, + showComments="commIndicator", + showObjects="all", + extLst=None, + ): + self.name = name + self.guid = guid + self.autoUpdate = autoUpdate + self.mergeInterval = mergeInterval + self.changesSavedWin = changesSavedWin + self.onlySync = onlySync + self.personalView = personalView + self.includePrintSettings = includePrintSettings + self.includeHiddenRowCol = includeHiddenRowCol + self.maximized = maximized + self.minimized = minimized + self.showHorizontalScroll = showHorizontalScroll + self.showVerticalScroll = showVerticalScroll + self.showSheetTabs = showSheetTabs + self.xWindow = xWindow + self.yWindow = yWindow + self.windowWidth = windowWidth + self.windowHeight = windowHeight + self.tabRatio = tabRatio + self.activeSheetId = activeSheetId + self.showFormulaBar = showFormulaBar + self.showStatusbar = showStatusbar + self.showComments = showComments + self.showObjects = showObjects diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/web.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/web.py new file mode 100644 index 00000000..e30e761a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/web.py @@ -0,0 +1,98 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Sequence, + String, + Float, + Integer, + Bool, + NoneSet, +) + + +class WebPublishObject(Serialisable): + + tagname = "webPublishingObject" + + id = Integer() + divId = String() + sourceObject = String(allow_none=True) + destinationFile = String() + title = String(allow_none=True) + autoRepublish = Bool(allow_none=True) + + def __init__(self, + id=None, + divId=None, + sourceObject=None, + destinationFile=None, + title=None, + autoRepublish=None, + ): + self.id = id + self.divId = divId + self.sourceObject = sourceObject + self.destinationFile = destinationFile + self.title = title + self.autoRepublish = autoRepublish + + +class WebPublishObjectList(Serialisable): + + tagname ="webPublishingObjects" + + count = Integer(allow_none=True) + webPublishObject = Sequence(expected_type=WebPublishObject) + + __elements__ = ('webPublishObject',) + + def __init__(self, + count=None, + webPublishObject=(), + ): + self.webPublishObject = webPublishObject + + + @property + def count(self): + return len(self.webPublishObject) + + +class WebPublishing(Serialisable): + + tagname = "webPublishing" + + css = Bool(allow_none=True) + thicket = Bool(allow_none=True) + longFileNames = Bool(allow_none=True) + vml = Bool(allow_none=True) + allowPng = Bool(allow_none=True) + targetScreenSize = NoneSet(values=(['544x376', '640x480', '720x512', '800x600', + '1024x768', '1152x882', '1152x900', '1280x1024', '1600x1200', + '1800x1440', '1920x1200'])) + dpi = Integer(allow_none=True) + codePage = Integer(allow_none=True) + characterSet = String(allow_none=True) + + def __init__(self, + css=None, + thicket=None, + longFileNames=None, + vml=None, + allowPng=None, + targetScreenSize='800x600', + dpi=None, + codePage=None, + characterSet=None, + ): + self.css = css + self.thicket = thicket + self.longFileNames = longFileNames + self.vml = vml + self.allowPng = allowPng + self.targetScreenSize = targetScreenSize + self.dpi = dpi + self.codePage = codePage + self.characterSet = characterSet diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/workbook.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/workbook.py new file mode 100644 index 00000000..b83ac442 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/workbook.py @@ -0,0 +1,438 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Workbook is the top-level container for all document information.""" +from copy import copy + +from openpyxl.compat import deprecated +from openpyxl.worksheet.worksheet import Worksheet +from openpyxl.worksheet._read_only import ReadOnlyWorksheet +from openpyxl.worksheet._write_only import WriteOnlyWorksheet +from openpyxl.worksheet.copier import WorksheetCopy + +from openpyxl.utils import quote_sheetname +from openpyxl.utils.indexed_list import IndexedList +from openpyxl.utils.datetime import WINDOWS_EPOCH, MAC_EPOCH +from openpyxl.utils.exceptions import ReadOnlyWorkbookException + +from openpyxl.writer.excel import save_workbook + +from openpyxl.styles.cell_style import StyleArray +from openpyxl.styles.named_styles import NamedStyle +from openpyxl.styles.differential import DifferentialStyleList +from openpyxl.styles.alignment import Alignment +from openpyxl.styles.borders import DEFAULT_BORDER +from openpyxl.styles.fills import DEFAULT_EMPTY_FILL, DEFAULT_GRAY_FILL +from openpyxl.styles.fonts import DEFAULT_FONT +from openpyxl.styles.protection import Protection +from openpyxl.styles.colors import COLOR_INDEX +from openpyxl.styles.named_styles import NamedStyleList +from openpyxl.styles.table import TableStyleList + +from openpyxl.chartsheet import Chartsheet +from .defined_name import DefinedName, DefinedNameDict +from openpyxl.packaging.core import DocumentProperties +from openpyxl.packaging.custom import CustomPropertyList +from openpyxl.packaging.relationship import RelationshipList +from .child import _WorkbookChild +from .protection import DocumentSecurity +from .properties import CalcProperties +from .views import BookView + + +from openpyxl.xml.constants import ( + XLSM, + XLSX, + XLTM, + XLTX +) + +INTEGER_TYPES = (int,) + +class Workbook: + """Workbook is the container for all other parts of the document.""" + + _read_only = False + _data_only = False + template = False + path = "/xl/workbook.xml" + + def __init__(self, + write_only=False, + iso_dates=False, + ): + self._sheets = [] + self._pivots = [] + self._active_sheet_index = 0 + self.defined_names = DefinedNameDict() + self._external_links = [] + self.properties = DocumentProperties() + self.custom_doc_props = CustomPropertyList() + self.security = DocumentSecurity() + self.__write_only = write_only + self.shared_strings = IndexedList() + + self._setup_styles() + + self.loaded_theme = None + self.vba_archive = None + self.is_template = False + self.code_name = None + self.epoch = WINDOWS_EPOCH + self.encoding = "utf-8" + self.iso_dates = iso_dates + + if not self.write_only: + self._sheets.append(Worksheet(self)) + + self.rels = RelationshipList() + self.calculation = CalcProperties() + self.views = [BookView()] + + + def _setup_styles(self): + """Bootstrap styles""" + + self._fonts = IndexedList() + self._fonts.add(DEFAULT_FONT) + + self._alignments = IndexedList([Alignment()]) + + self._borders = IndexedList() + self._borders.add(DEFAULT_BORDER) + + self._fills = IndexedList() + self._fills.add(DEFAULT_EMPTY_FILL) + self._fills.add(DEFAULT_GRAY_FILL) + + self._number_formats = IndexedList() + self._date_formats = {} + self._timedelta_formats = {} + + self._protections = IndexedList([Protection()]) + + self._colors = COLOR_INDEX + self._cell_styles = IndexedList([StyleArray()]) + self._named_styles = NamedStyleList() + self.add_named_style(NamedStyle(font=copy(DEFAULT_FONT), border=copy(DEFAULT_BORDER), builtinId=0)) + self._table_styles = TableStyleList() + self._differential_styles = DifferentialStyleList() + + + @property + def epoch(self): + if self._epoch == WINDOWS_EPOCH: + return WINDOWS_EPOCH + return MAC_EPOCH + + + @epoch.setter + def epoch(self, value): + if value not in (WINDOWS_EPOCH, MAC_EPOCH): + raise ValueError("The epoch must be either 1900 or 1904") + self._epoch = value + + + @property + def read_only(self): + return self._read_only + + @property + def data_only(self): + return self._data_only + + @property + def write_only(self): + return self.__write_only + + + @property + def excel_base_date(self): + return self.epoch + + @property + def active(self): + """Get the currently active sheet or None + + :type: :class:`openpyxl.worksheet.worksheet.Worksheet` + """ + try: + return self._sheets[self._active_sheet_index] + except IndexError: + pass + + @active.setter + def active(self, value): + """Set the active sheet""" + if not isinstance(value, (_WorkbookChild, INTEGER_TYPES)): + raise TypeError("Value must be either a worksheet, chartsheet or numerical index") + if isinstance(value, INTEGER_TYPES): + self._active_sheet_index = value + return + #if self._sheets and 0 <= value < len(self._sheets): + #value = self._sheets[value] + #else: + #raise ValueError("Sheet index is outside the range of possible values", value) + if value not in self._sheets: + raise ValueError("Worksheet is not in the workbook") + if value.sheet_state != "visible": + raise ValueError("Only visible sheets can be made active") + + idx = self._sheets.index(value) + self._active_sheet_index = idx + + + def create_sheet(self, title=None, index=None): + """Create a worksheet (at an optional index). + + :param title: optional title of the sheet + :type title: str + :param index: optional position at which the sheet will be inserted + :type index: int + + """ + if self.read_only: + raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook') + + if self.write_only : + new_ws = WriteOnlyWorksheet(parent=self, title=title) + else: + new_ws = Worksheet(parent=self, title=title) + + self._add_sheet(sheet=new_ws, index=index) + return new_ws + + + def _add_sheet(self, sheet, index=None): + """Add an worksheet (at an optional index).""" + + if not isinstance(sheet, (Worksheet, WriteOnlyWorksheet, Chartsheet)): + raise TypeError("Cannot be added to a workbook") + + if sheet.parent != self: + raise ValueError("You cannot add worksheets from another workbook.") + + if index is None: + self._sheets.append(sheet) + else: + self._sheets.insert(index, sheet) + + + def move_sheet(self, sheet, offset=0): + """ + Move a sheet or sheetname + """ + if not isinstance(sheet, Worksheet): + sheet = self[sheet] + idx = self._sheets.index(sheet) + del self._sheets[idx] + new_pos = idx + offset + self._sheets.insert(new_pos, sheet) + + + def remove(self, worksheet): + """Remove `worksheet` from this workbook.""" + idx = self._sheets.index(worksheet) + self._sheets.remove(worksheet) + + + @deprecated("Use wb.remove(worksheet) or del wb[sheetname]") + def remove_sheet(self, worksheet): + """Remove `worksheet` from this workbook.""" + self.remove(worksheet) + + + def create_chartsheet(self, title=None, index=None): + if self.read_only: + raise ReadOnlyWorkbookException("Cannot create new sheet in a read-only workbook") + cs = Chartsheet(parent=self, title=title) + + self._add_sheet(cs, index) + return cs + + + @deprecated("Use wb[sheetname]") + def get_sheet_by_name(self, name): + """Returns a worksheet by its name. + + :param name: the name of the worksheet to look for + :type name: string + + """ + return self[name] + + def __contains__(self, key): + return key in self.sheetnames + + + def index(self, worksheet): + """Return the index of a worksheet.""" + return self.worksheets.index(worksheet) + + + @deprecated("Use wb.index(worksheet)") + def get_index(self, worksheet): + """Return the index of the worksheet.""" + return self.index(worksheet) + + def __getitem__(self, key): + """Returns a worksheet by its name. + + :param name: the name of the worksheet to look for + :type name: string + + """ + for sheet in self.worksheets + self.chartsheets: + if sheet.title == key: + return sheet + raise KeyError("Worksheet {0} does not exist.".format(key)) + + def __delitem__(self, key): + sheet = self[key] + self.remove(sheet) + + def __iter__(self): + return iter(self.worksheets) + + + @deprecated("Use wb.sheetnames") + def get_sheet_names(self): + return self.sheetnames + + @property + def worksheets(self): + """A list of sheets in this workbook + + :type: list of :class:`openpyxl.worksheet.worksheet.Worksheet` + """ + return [s for s in self._sheets if isinstance(s, (Worksheet, ReadOnlyWorksheet, WriteOnlyWorksheet))] + + @property + def chartsheets(self): + """A list of Chartsheets in this workbook + + :type: list of :class:`openpyxl.chartsheet.chartsheet.Chartsheet` + """ + return [s for s in self._sheets if isinstance(s, Chartsheet)] + + @property + def sheetnames(self): + """Returns the list of the names of worksheets in this workbook. + + Names are returned in the worksheets order. + + :type: list of strings + + """ + return [s.title for s in self._sheets] + + + @deprecated("Assign scoped named ranges directly to worksheets or global ones to the workbook. Deprecated in 3.1") + def create_named_range(self, name, worksheet=None, value=None, scope=None): + """Create a new named_range on a worksheet + + """ + defn = DefinedName(name=name) + if worksheet is not None: + defn.value = "{0}!{1}".format(quote_sheetname(worksheet.title), value) + else: + defn.value = value + + self.defined_names[name] = defn + + + def add_named_style(self, style): + """ + Add a named style + """ + self._named_styles.append(style) + style.bind(self) + + + @property + def named_styles(self): + """ + List available named styles + """ + return self._named_styles.names + + + @property + def mime_type(self): + """ + The mime type is determined by whether a workbook is a template or + not and whether it contains macros or not. Excel requires the file + extension to match but openpyxl does not enforce this. + + """ + ct = self.template and XLTX or XLSX + if self.vba_archive: + ct = self.template and XLTM or XLSM + return ct + + + def save(self, filename): + """Save the current workbook under the given `filename`. + Use this function instead of using an `ExcelWriter`. + + .. warning:: + When creating your workbook using `write_only` set to True, + you will only be able to call this function once. Subsequent attempts to + modify or save the file will raise an :class:`openpyxl.shared.exc.WorkbookAlreadySaved` exception. + """ + if self.read_only: + raise TypeError("""Workbook is read-only""") + if self.write_only and not self.worksheets: + self.create_sheet() + save_workbook(self, filename) + + + @property + def style_names(self): + """ + List of named styles + """ + return [s.name for s in self._named_styles] + + + def copy_worksheet(self, from_worksheet): + """Copy an existing worksheet in the current workbook + + .. warning:: + This function cannot copy worksheets between workbooks. + worksheets can only be copied within the workbook that they belong + + :param from_worksheet: the worksheet to be copied from + :return: copy of the initial worksheet + """ + if self.__write_only or self._read_only: + raise ValueError("Cannot copy worksheets in read-only or write-only mode") + + new_title = u"{0} Copy".format(from_worksheet.title) + to_worksheet = self.create_sheet(title=new_title) + cp = WorksheetCopy(source_worksheet=from_worksheet, target_worksheet=to_worksheet) + cp.copy_worksheet() + return to_worksheet + + + def close(self): + """ + Close workbook file if open. Only affects read-only and write-only modes. + """ + if hasattr(self, '_archive'): + self._archive.close() + + + def _duplicate_name(self, name): + """ + Check for duplicate name in defined name list and table list of each worksheet. + Names are not case sensitive. + """ + name = name.lower() + for sheet in self.worksheets: + for t in sheet.tables: + if name == t.lower(): + return True + + if name in self.defined_names: + return True + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/__init__.py new file mode 100644 index 00000000..ab6cdead --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/__init__.py @@ -0,0 +1 @@ +# Copyright (c) 2010-2024 openpyxl diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_read_only.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_read_only.py new file mode 100644 index 00000000..95852f21 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_read_only.py @@ -0,0 +1,190 @@ +# Copyright (c) 2010-2024 openpyxl + +""" Read worksheets on-demand +""" + +from .worksheet import Worksheet +from openpyxl.cell.read_only import ReadOnlyCell, EMPTY_CELL +from openpyxl.utils import get_column_letter + +from ._reader import WorkSheetParser +from openpyxl.workbook.defined_name import DefinedNameDict + + +def read_dimension(source): + parser = WorkSheetParser(source, []) + return parser.parse_dimensions() + + +class ReadOnlyWorksheet: + + _min_column = 1 + _min_row = 1 + _max_column = _max_row = None + + # from Standard Worksheet + # Methods from Worksheet + cell = Worksheet.cell + iter_rows = Worksheet.iter_rows + values = Worksheet.values + rows = Worksheet.rows + __getitem__ = Worksheet.__getitem__ + __iter__ = Worksheet.__iter__ + + + def __init__(self, parent_workbook, title, worksheet_path, shared_strings): + self.parent = parent_workbook + self.title = title + self.sheet_state = 'visible' + self._current_row = None + self._worksheet_path = worksheet_path + self._shared_strings = shared_strings + self._get_size() + self.defined_names = DefinedNameDict() + + + def _get_size(self): + src = self._get_source() + parser = WorkSheetParser(src, []) + dimensions = parser.parse_dimensions() + src.close() + if dimensions is not None: + self._min_column, self._min_row, self._max_column, self._max_row = dimensions + + + def _get_source(self): + """Parse xml source on demand, must close after use""" + return self.parent._archive.open(self._worksheet_path) + + + def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False): + """ + The source worksheet file may have columns or rows missing. + Missing cells will be created. + """ + filler = EMPTY_CELL + if values_only: + filler = None + + max_col = max_col or self.max_column + max_row = max_row or self.max_row + empty_row = [] + if max_col is not None: + empty_row = (filler,) * (max_col + 1 - min_col) + + counter = min_row + idx = 1 + with self._get_source() as src: + parser = WorkSheetParser(src, + self._shared_strings, + data_only=self.parent.data_only, + epoch=self.parent.epoch, + date_formats=self.parent._date_formats, + timedelta_formats=self.parent._timedelta_formats) + + for idx, row in parser.parse(): + if max_row is not None and idx > max_row: + break + + # some rows are missing + for _ in range(counter, idx): + counter += 1 + yield empty_row + + # return cells from a row + if counter <= idx: + row = self._get_row(row, min_col, max_col, values_only) + counter += 1 + yield row + + if max_row is not None and max_row < idx: + for _ in range(counter, max_row+1): + yield empty_row + + + def _get_row(self, row, min_col=1, max_col=None, values_only=False): + """ + Make sure a row contains always the same number of cells or values + """ + if not row and not max_col: # in case someone wants to force rows where there aren't any + return () + + max_col = max_col or row[-1]['column'] + row_width = max_col + 1 - min_col + + new_row = [EMPTY_CELL] * row_width + if values_only: + new_row = [None] * row_width + + for cell in row: + counter = cell['column'] + if min_col <= counter <= max_col: + idx = counter - min_col # position in list of cells returned + new_row[idx] = cell['value'] + if not values_only: + new_row[idx] = ReadOnlyCell(self, **cell) + + return tuple(new_row) + + + def _get_cell(self, row, column): + """Cells are returned by a generator which can be empty""" + for row in self._cells_by_row(column, row, column, row): + if row: + return row[0] + return EMPTY_CELL + + + def calculate_dimension(self, force=False): + if not all([self.max_column, self.max_row]): + if force: + self._calculate_dimension() + else: + raise ValueError("Worksheet is unsized, use calculate_dimension(force=True)") + return f"{get_column_letter(self.min_column)}{self.min_row}:{get_column_letter(self.max_column)}{self.max_row}" + + + def _calculate_dimension(self): + """ + Loop through all the cells to get the size of a worksheet. + Do this only if it is explicitly requested. + """ + + max_col = 0 + for r in self.rows: + if not r: + continue + cell = r[-1] + max_col = max(max_col, cell.column) + + self._max_row = cell.row + self._max_column = max_col + + + def reset_dimensions(self): + """ + Remove worksheet dimensions if these are incorrect in the worksheet source. + NB. This probably indicates a bug in the library or application that created + the workbook. + """ + self._max_row = self._max_column = None + + + @property + def min_row(self): + return self._min_row + + + @property + def max_row(self): + return self._max_row + + + @property + def min_column(self): + return self._min_column + + + @property + def max_column(self): + return self._max_column diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_reader.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_reader.py new file mode 100644 index 00000000..38240f03 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_reader.py @@ -0,0 +1,472 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Reader for a single worksheet.""" +from copy import copy +from warnings import warn + +# compatibility imports +from openpyxl.xml.functions import iterparse + +# package imports +from openpyxl.cell import Cell, MergedCell +from openpyxl.cell.text import Text +from openpyxl.worksheet.dimensions import ( + ColumnDimension, + RowDimension, + SheetFormatProperties, +) + +from openpyxl.xml.constants import ( + SHEET_MAIN_NS, + EXT_TYPES, +) +from openpyxl.formatting.formatting import ConditionalFormatting +from openpyxl.formula.translate import Translator +from openpyxl.utils import ( + get_column_letter, + coordinate_to_tuple, + ) +from openpyxl.utils.datetime import from_excel, from_ISO8601, WINDOWS_EPOCH +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.cell.rich_text import CellRichText + +from .formula import DataTableFormula, ArrayFormula +from .filters import AutoFilter +from .header_footer import HeaderFooter +from .hyperlink import HyperlinkList +from .merge import MergeCells +from .page import PageMargins, PrintOptions, PrintPageSetup +from .pagebreak import RowBreak, ColBreak +from .protection import SheetProtection +from .scenario import ScenarioList +from .views import SheetViewList +from .datavalidation import DataValidationList +from .table import TablePartList +from .properties import WorksheetProperties +from .dimensions import SheetDimension +from .related import Related + + +CELL_TAG = '{%s}c' % SHEET_MAIN_NS +VALUE_TAG = '{%s}v' % SHEET_MAIN_NS +FORMULA_TAG = '{%s}f' % SHEET_MAIN_NS +MERGE_TAG = '{%s}mergeCells' % SHEET_MAIN_NS +INLINE_STRING = "{%s}is" % SHEET_MAIN_NS +COL_TAG = '{%s}col' % SHEET_MAIN_NS +ROW_TAG = '{%s}row' % SHEET_MAIN_NS +CF_TAG = '{%s}conditionalFormatting' % SHEET_MAIN_NS +LEGACY_TAG = '{%s}legacyDrawing' % SHEET_MAIN_NS +PROT_TAG = '{%s}sheetProtection' % SHEET_MAIN_NS +EXT_TAG = "{%s}extLst" % SHEET_MAIN_NS +HYPERLINK_TAG = "{%s}hyperlinks" % SHEET_MAIN_NS +TABLE_TAG = "{%s}tableParts" % SHEET_MAIN_NS +PRINT_TAG = '{%s}printOptions' % SHEET_MAIN_NS +MARGINS_TAG = '{%s}pageMargins' % SHEET_MAIN_NS +PAGE_TAG = '{%s}pageSetup' % SHEET_MAIN_NS +HEADER_TAG = '{%s}headerFooter' % SHEET_MAIN_NS +FILTER_TAG = '{%s}autoFilter' % SHEET_MAIN_NS +VALIDATION_TAG = '{%s}dataValidations' % SHEET_MAIN_NS +PROPERTIES_TAG = '{%s}sheetPr' % SHEET_MAIN_NS +VIEWS_TAG = '{%s}sheetViews' % SHEET_MAIN_NS +FORMAT_TAG = '{%s}sheetFormatPr' % SHEET_MAIN_NS +ROW_BREAK_TAG = '{%s}rowBreaks' % SHEET_MAIN_NS +COL_BREAK_TAG = '{%s}colBreaks' % SHEET_MAIN_NS +SCENARIOS_TAG = '{%s}scenarios' % SHEET_MAIN_NS +DATA_TAG = '{%s}sheetData' % SHEET_MAIN_NS +DIMENSION_TAG = '{%s}dimension' % SHEET_MAIN_NS +CUSTOM_VIEWS_TAG = '{%s}customSheetViews' % SHEET_MAIN_NS + + +def _cast_number(value): + "Convert numbers as string to an int or float" + if "." in value or "E" in value or "e" in value: + return float(value) + return int(value) + + +def parse_richtext_string(element): + """ + Parse inline string and preserve rich text formatting + """ + value = CellRichText.from_tree(element) or "" + if len(value) == 1 and isinstance(value[0], str): + value = value[0] + return value + + +class WorkSheetParser: + + def __init__(self, src, shared_strings, data_only=False, + epoch=WINDOWS_EPOCH, date_formats=set(), + timedelta_formats=set(), rich_text=False): + self.min_row = self.min_col = None + self.epoch = epoch + self.source = src + self.shared_strings = shared_strings + self.data_only = data_only + self.shared_formulae = {} + self.row_counter = self.col_counter = 0 + self.tables = TablePartList() + self.date_formats = date_formats + self.timedelta_formats = timedelta_formats + self.row_dimensions = {} + self.column_dimensions = {} + self.number_formats = [] + self.keep_vba = False + self.hyperlinks = HyperlinkList() + self.formatting = [] + self.legacy_drawing = None + self.merged_cells = None + self.row_breaks = RowBreak() + self.col_breaks = ColBreak() + self.rich_text = rich_text + + + def parse(self): + dispatcher = { + COL_TAG: self.parse_column_dimensions, + PROT_TAG: self.parse_sheet_protection, + EXT_TAG: self.parse_extensions, + CF_TAG: self.parse_formatting, + LEGACY_TAG: self.parse_legacy, + ROW_BREAK_TAG: self.parse_row_breaks, + COL_BREAK_TAG: self.parse_col_breaks, + CUSTOM_VIEWS_TAG: self.parse_custom_views, + } + + properties = { + PRINT_TAG: ('print_options', PrintOptions), + MARGINS_TAG: ('page_margins', PageMargins), + PAGE_TAG: ('page_setup', PrintPageSetup), + HEADER_TAG: ('HeaderFooter', HeaderFooter), + FILTER_TAG: ('auto_filter', AutoFilter), + VALIDATION_TAG: ('data_validations', DataValidationList), + PROPERTIES_TAG: ('sheet_properties', WorksheetProperties), + VIEWS_TAG: ('views', SheetViewList), + FORMAT_TAG: ('sheet_format', SheetFormatProperties), + SCENARIOS_TAG: ('scenarios', ScenarioList), + TABLE_TAG: ('tables', TablePartList), + HYPERLINK_TAG: ('hyperlinks', HyperlinkList), + MERGE_TAG: ('merged_cells', MergeCells), + + } + + it = iterparse(self.source) # add a finaliser to close the source when this becomes possible + + for _, element in it: + tag_name = element.tag + if tag_name in dispatcher: + dispatcher[tag_name](element) + element.clear() + elif tag_name in properties: + prop = properties[tag_name] + obj = prop[1].from_tree(element) + setattr(self, prop[0], obj) + element.clear() + elif tag_name == ROW_TAG: + row = self.parse_row(element) + element.clear() + yield row + + + def parse_dimensions(self): + """ + Get worksheet dimensions if they are provided. + """ + it = iterparse(self.source) + + for _event, element in it: + if element.tag == DIMENSION_TAG: + dim = SheetDimension.from_tree(element) + return dim.boundaries + + elif element.tag == DATA_TAG: + # Dimensions missing + break + element.clear() + + + def parse_cell(self, element): + data_type = element.get('t', 'n') + coordinate = element.get('r') + style_id = element.get('s', 0) + if style_id: + style_id = int(style_id) + + if data_type == "inlineStr": + value = None + else: + value = element.findtext(VALUE_TAG, None) or None + + if coordinate: + row, column = coordinate_to_tuple(coordinate) + self.col_counter = column + else: + self.col_counter += 1 + row, column = self.row_counter, self.col_counter + + if not self.data_only and element.find(FORMULA_TAG) is not None: + data_type = 'f' + value = self.parse_formula(element) + + elif value is not None: + if data_type == 'n': + value = _cast_number(value) + if style_id in self.date_formats: + data_type = 'd' + try: + value = from_excel( + value, self.epoch, timedelta=style_id in self.timedelta_formats + ) + except (OverflowError, ValueError): + msg = f"""Cell {coordinate} is marked as a date but the serial value {value} is outside the limits for dates. The cell will be treated as an error.""" + warn(msg) + data_type = "e" + value = "#VALUE!" + elif data_type == 's': + value = self.shared_strings[int(value)] + elif data_type == 'b': + value = bool(int(value)) + elif data_type == "str": + data_type = "s" + elif data_type == 'd': + value = from_ISO8601(value) + + elif data_type == 'inlineStr': + child = element.find(INLINE_STRING) + if child is not None: + data_type = 's' + if self.rich_text: + value = parse_richtext_string(child) + else: + value = Text.from_tree(child).content + + return {'row':row, 'column':column, 'value':value, 'data_type':data_type, 'style_id':style_id} + + + def parse_formula(self, element): + """ + possible formulae types: shared, array, datatable + """ + formula = element.find(FORMULA_TAG) + formula_type = formula.get('t') + coordinate = element.get('r') + value = "=" + if formula.text is not None: + value += formula.text + + if formula_type == "array": + value = ArrayFormula(ref=formula.get('ref'), text=value) + + elif formula_type == "shared": + idx = formula.get('si') + if idx in self.shared_formulae: + trans = self.shared_formulae[idx] + value = trans.translate_formula(coordinate) + elif value != "=": + self.shared_formulae[idx] = Translator(value, coordinate) + + elif formula_type == "dataTable": + value = DataTableFormula(**formula.attrib) + + return value + + + def parse_column_dimensions(self, col): + attrs = dict(col.attrib) + column = get_column_letter(int(attrs['min'])) + attrs['index'] = column + self.column_dimensions[column] = attrs + + + def parse_row(self, row): + attrs = dict(row.attrib) + + if "r" in attrs: + try: + self.row_counter = int(attrs['r']) + except ValueError: + val = float(attrs['r']) + if val.is_integer(): + self.row_counter = int(val) + else: + raise ValueError(f"{attrs['r']} is not a valid row number") + else: + self.row_counter += 1 + self.col_counter = 0 + + keys = {k for k in attrs if not k.startswith('{')} + if keys - {'r', 'spans'}: + # don't create dimension objects unless they have relevant information + self.row_dimensions[str(self.row_counter)] = attrs + + cells = [self.parse_cell(el) for el in row] + return self.row_counter, cells + + + def parse_formatting(self, element): + try: + cf = ConditionalFormatting.from_tree(element) + self.formatting.append(cf) + except TypeError as e: + msg = f"Failed to load a conditional formatting rule. It will be discarded. Cause: {e}" + warn(msg) + + + def parse_sheet_protection(self, element): + protection = SheetProtection.from_tree(element) + password = element.get("password") + if password is not None: + protection.set_password(password, True) + self.protection = protection + + + def parse_extensions(self, element): + extLst = ExtensionList.from_tree(element) + for e in extLst.ext: + ext_type = EXT_TYPES.get(e.uri.upper(), "Unknown") + msg = "{0} extension is not supported and will be removed".format(ext_type) + warn(msg) + + + def parse_legacy(self, element): + obj = Related.from_tree(element) + self.legacy_drawing = obj.id + + + def parse_row_breaks(self, element): + brk = RowBreak.from_tree(element) + self.row_breaks = brk + + + def parse_col_breaks(self, element): + brk = ColBreak.from_tree(element) + self.col_breaks = brk + + + def parse_custom_views(self, element): + # clear page_breaks to avoid duplication which Excel doesn't like + # basically they're ignored in custom views + self.row_breaks = RowBreak() + self.col_breaks = ColBreak() + + +class WorksheetReader: + """ + Create a parser and apply it to a workbook + """ + + def __init__(self, ws, xml_source, shared_strings, data_only, rich_text): + self.ws = ws + self.parser = WorkSheetParser(xml_source, shared_strings, + data_only, ws.parent.epoch, ws.parent._date_formats, + ws.parent._timedelta_formats, rich_text) + self.tables = [] + + + def bind_cells(self): + for idx, row in self.parser.parse(): + for cell in row: + style = self.ws.parent._cell_styles[cell['style_id']] + c = Cell(self.ws, row=cell['row'], column=cell['column'], style_array=style) + c._value = cell['value'] + c.data_type = cell['data_type'] + self.ws._cells[(cell['row'], cell['column'])] = c + + if self.ws._cells: + self.ws._current_row = self.ws.max_row # use cells not row dimensions + + + def bind_formatting(self): + for cf in self.parser.formatting: + for rule in cf.rules: + if rule.dxfId is not None: + rule.dxf = self.ws.parent._differential_styles[rule.dxfId] + self.ws.conditional_formatting[cf] = rule + + + def bind_tables(self): + for t in self.parser.tables.tablePart: + rel = self.ws._rels.get(t.id) + self.tables.append(rel.Target) + + + def bind_merged_cells(self): + from openpyxl.worksheet.cell_range import MultiCellRange + from openpyxl.worksheet.merge import MergedCellRange + if not self.parser.merged_cells: + return + + ranges = [] + for cr in self.parser.merged_cells.mergeCell: + mcr = MergedCellRange(self.ws, cr.ref) + self.ws._clean_merge_range(mcr) + ranges.append(mcr) + self.ws.merged_cells = MultiCellRange(ranges) + + + def bind_hyperlinks(self): + for link in self.parser.hyperlinks.hyperlink: + if link.id: + rel = self.ws._rels.get(link.id) + link.target = rel.Target + if ":" in link.ref: + # range of cells + for row in self.ws[link.ref]: + for cell in row: + try: + cell.hyperlink = copy(link) + except AttributeError: + pass + else: + cell = self.ws[link.ref] + if isinstance(cell, MergedCell): + cell = self.normalize_merged_cell_link(cell.coordinate) + cell.hyperlink = link + + def normalize_merged_cell_link(self, coord): + """ + Returns the appropriate cell to which a hyperlink, which references a merged cell at the specified coordinates, + should be bound. + """ + for rng in self.ws.merged_cells: + if coord in rng: + return self.ws.cell(*rng.top[0]) + + def bind_col_dimensions(self): + for col, cd in self.parser.column_dimensions.items(): + if 'style' in cd: + key = int(cd['style']) + cd['style'] = self.ws.parent._cell_styles[key] + self.ws.column_dimensions[col] = ColumnDimension(self.ws, **cd) + + + def bind_row_dimensions(self): + for row, rd in self.parser.row_dimensions.items(): + if 's' in rd: + key = int(rd['s']) + rd['s'] = self.ws.parent._cell_styles[key] + self.ws.row_dimensions[int(row)] = RowDimension(self.ws, **rd) + + + def bind_properties(self): + for k in ('print_options', 'page_margins', 'page_setup', + 'HeaderFooter', 'auto_filter', 'data_validations', + 'sheet_properties', 'views', 'sheet_format', + 'row_breaks', 'col_breaks', 'scenarios', 'legacy_drawing', + 'protection', + ): + v = getattr(self.parser, k, None) + if v is not None: + setattr(self.ws, k, v) + + + def bind_all(self): + self.bind_cells() + self.bind_merged_cells() + self.bind_hyperlinks() + self.bind_formatting() + self.bind_col_dimensions() + self.bind_row_dimensions() + self.bind_tables() + self.bind_properties() diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_write_only.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_write_only.py new file mode 100644 index 00000000..0b1d027d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_write_only.py @@ -0,0 +1,160 @@ +# Copyright (c) 2010-2024 openpyxl + + +"""Write worksheets to xml representations in an optimized way""" + +from inspect import isgenerator + +from openpyxl.cell import Cell, WriteOnlyCell +from openpyxl.workbook.child import _WorkbookChild +from .worksheet import Worksheet +from openpyxl.utils.exceptions import WorkbookAlreadySaved + +from ._writer import WorksheetWriter + + +class WriteOnlyWorksheet(_WorkbookChild): + """ + Streaming worksheet. Optimised to reduce memory by writing rows just in + time. + Cells can be styled and have comments Styles for rows and columns + must be applied before writing cells + """ + + __saved = False + _writer = None + _rows = None + _rel_type = Worksheet._rel_type + _path = Worksheet._path + mime_type = Worksheet.mime_type + + # copy methods from Standard worksheet + _add_row = Worksheet._add_row + _add_column = Worksheet._add_column + add_chart = Worksheet.add_chart + add_image = Worksheet.add_image + add_table = Worksheet.add_table + tables = Worksheet.tables + print_titles = Worksheet.print_titles + print_title_cols = Worksheet.print_title_cols + print_title_rows = Worksheet.print_title_rows + freeze_panes = Worksheet.freeze_panes + print_area = Worksheet.print_area + sheet_view = Worksheet.sheet_view + _setup = Worksheet._setup + + def __init__(self, parent, title): + super().__init__(parent, title) + self._max_col = 0 + self._max_row = 0 + self._setup() + + @property + def closed(self): + return self.__saved + + + def _write_rows(self): + """ + Send rows to the writer's stream + """ + try: + xf = self._writer.xf.send(True) + except StopIteration: + self._already_saved() + + with xf.element("sheetData"): + row_idx = 1 + try: + while True: + row = (yield) + row = self._values_to_row(row, row_idx) + self._writer.write_row(xf, row, row_idx) + row_idx += 1 + except GeneratorExit: + pass + + self._writer.xf.send(None) + + + def _get_writer(self): + if self._writer is None: + self._writer = WorksheetWriter(self) + self._writer.write_top() + + + def close(self): + if self.__saved: + self._already_saved() + + self._get_writer() + + if self._rows is None: + self._writer.write_rows() + else: + self._rows.close() + + self._writer.write_tail() + + self._writer.close() + self.__saved = True + + + def append(self, row): + """ + :param row: iterable containing values to append + :type row: iterable + """ + + if (not isgenerator(row) and + not isinstance(row, (list, tuple, range)) + ): + self._invalid_row(row) + + self._get_writer() + + if self._rows is None: + self._rows = self._write_rows() + next(self._rows) + + self._rows.send(row) + + + def _values_to_row(self, values, row_idx): + """ + Convert whatever has been appended into a form suitable for work_rows + """ + cell = WriteOnlyCell(self) + + for col_idx, value in enumerate(values, 1): + if value is None: + continue + try: + cell.value = value + except ValueError: + if isinstance(value, Cell): + cell = value + else: + raise ValueError + + cell.column = col_idx + cell.row = row_idx + + if cell.hyperlink is not None: + cell.hyperlink.ref = cell.coordinate + + yield cell + + # reset cell if style applied + if cell.has_style or cell.hyperlink: + cell = WriteOnlyCell(self) + + + def _already_saved(self): + raise WorkbookAlreadySaved('Workbook has already been saved and cannot be modified or saved anymore.') + + + def _invalid_row(self, iterable): + raise TypeError('Value must be a list, tuple, range or a generator Supplied value is {0}'.format( + type(iterable)) + ) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py new file mode 100644 index 00000000..df381d2b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py @@ -0,0 +1,390 @@ +# Copyright (c) 2010-2024 openpyxl + +import atexit +from collections import defaultdict +from io import BytesIO +import os +from tempfile import NamedTemporaryFile +from warnings import warn + +from openpyxl.xml.functions import xmlfile +from openpyxl.xml.constants import SHEET_MAIN_NS + +from openpyxl.comments.comment_sheet import CommentRecord +from openpyxl.packaging.relationship import Relationship, RelationshipList +from openpyxl.styles.differential import DifferentialStyle + +from .dimensions import SheetDimension +from .hyperlink import HyperlinkList +from .merge import MergeCell, MergeCells +from .related import Related +from .table import TablePartList + +from openpyxl.cell._writer import write_cell + + +ALL_TEMP_FILES = [] + +@atexit.register +def _openpyxl_shutdown(): + for path in ALL_TEMP_FILES: + if os.path.exists(path): + os.remove(path) + + +def create_temporary_file(suffix=''): + fobj = NamedTemporaryFile(mode='w+', suffix=suffix, + prefix='openpyxl.', delete=False) + filename = fobj.name + fobj.close() + ALL_TEMP_FILES.append(filename) + return filename + + +class WorksheetWriter: + + + def __init__(self, ws, out=None): + self.ws = ws + self.ws._hyperlinks = [] + self.ws._comments = [] + if out is None: + out = create_temporary_file() + self.out = out + self._rels = RelationshipList() + self.xf = self.get_stream() + next(self.xf) # start generator + + + def write_properties(self): + props = self.ws.sheet_properties + self.xf.send(props.to_tree()) + + + def write_dimensions(self): + """ + Write worksheet size if known + """ + ref = getattr(self.ws, 'calculate_dimension', None) + if ref: + dim = SheetDimension(ref()) + self.xf.send(dim.to_tree()) + + + def write_format(self): + self.ws.sheet_format.outlineLevelCol = self.ws.column_dimensions.max_outline + fmt = self.ws.sheet_format + self.xf.send(fmt.to_tree()) + + + def write_views(self): + views = self.ws.views + self.xf.send(views.to_tree()) + + + def write_cols(self): + cols = self.ws.column_dimensions + self.xf.send(cols.to_tree()) + + + def write_top(self): + """ + Write all elements up to rows: + properties + dimensions + views + format + cols + """ + self.write_properties() + self.write_dimensions() + self.write_views() + self.write_format() + self.write_cols() + + + def rows(self): + """Return all rows, and any cells that they contain""" + # order cells by row + rows = defaultdict(list) + for (row, col), cell in sorted(self.ws._cells.items()): + rows[row].append(cell) + + # add empty rows if styling has been applied + for row in self.ws.row_dimensions.keys() - rows.keys(): + rows[row] = [] + + return sorted(rows.items()) + + + def write_rows(self): + xf = self.xf.send(True) + + with xf.element("sheetData"): + for row_idx, row in self.rows(): + self.write_row(xf, row, row_idx) + + self.xf.send(None) # return control to generator + + + def write_row(self, xf, row, row_idx): + attrs = {'r': f"{row_idx}"} + dims = self.ws.row_dimensions + attrs.update(dims.get(row_idx, {})) + + with xf.element("row", attrs): + + for cell in row: + if cell._comment is not None: + comment = CommentRecord.from_cell(cell) + self.ws._comments.append(comment) + if ( + cell._value is None + and not cell.has_style + and not cell._comment + ): + continue + write_cell(xf, self.ws, cell, cell.has_style) + + + def write_protection(self): + prot = self.ws.protection + if prot: + self.xf.send(prot.to_tree()) + + + def write_scenarios(self): + scenarios = self.ws.scenarios + if scenarios: + self.xf.send(scenarios.to_tree()) + + + def write_filter(self): + flt = self.ws.auto_filter + if flt: + self.xf.send(flt.to_tree()) + + + def write_sort(self): + """ + As per discusion with the OOXML Working Group global sort state is not required. + openpyxl never reads it from existing files + """ + pass + + + def write_merged_cells(self): + merged = self.ws.merged_cells + if merged: + cells = [MergeCell(str(ref)) for ref in self.ws.merged_cells] + self.xf.send(MergeCells(mergeCell=cells).to_tree()) + + + def write_formatting(self): + df = DifferentialStyle() + wb = self.ws.parent + for cf in self.ws.conditional_formatting: + for rule in cf.rules: + if rule.dxf and rule.dxf != df: + rule.dxfId = wb._differential_styles.add(rule.dxf) + self.xf.send(cf.to_tree()) + + + def write_validations(self): + dv = self.ws.data_validations + if dv: + self.xf.send(dv.to_tree()) + + + def write_hyperlinks(self): + + links = self.ws._hyperlinks + + for link in links: + if link.target: + rel = Relationship(type="hyperlink", TargetMode="External", Target=link.target) + self._rels.append(rel) + link.id = rel.id + + if links: + self.xf.send(HyperlinkList(links).to_tree()) + + + def write_print(self): + print_options = self.ws.print_options + if print_options: + self.xf.send(print_options.to_tree()) + + + def write_margins(self): + margins = self.ws.page_margins + if margins: + self.xf.send(margins.to_tree()) + + + def write_page(self): + setup = self.ws.page_setup + if setup: + self.xf.send(setup.to_tree()) + + + def write_header(self): + hf = self.ws.HeaderFooter + if hf: + self.xf.send(hf.to_tree()) + + + def write_breaks(self): + brks = (self.ws.row_breaks, self.ws.col_breaks) + for brk in brks: + if brk: + self.xf.send(brk.to_tree()) + + + def write_drawings(self): + if self.ws._charts or self.ws._images: + rel = Relationship(type="drawing", Target="") + self._rels.append(rel) + drawing = Related() + drawing.id = rel.id + self.xf.send(drawing.to_tree("drawing")) + + + def write_legacy(self): + """ + Comments & VBA controls use VML and require an additional element + that is no longer in the specification. + """ + if (self.ws.legacy_drawing is not None or self.ws._comments): + legacy = Related(id="anysvml") + self.xf.send(legacy.to_tree("legacyDrawing")) + + + def write_tables(self): + tables = TablePartList() + + for table in self.ws.tables.values(): + if not table.tableColumns: + table._initialise_columns() + if table.headerRowCount: + try: + row = self.ws[table.ref][0] + for cell, col in zip(row, table.tableColumns): + if cell.data_type != "s": + warn("File may not be readable: column headings must be strings.") + col.name = str(cell.value) + except TypeError: + warn("Column headings are missing, file may not be readable") + rel = Relationship(Type=table._rel_type, Target="") + self._rels.append(rel) + table._rel_id = rel.Id + tables.append(Related(id=rel.Id)) + + if tables: + self.xf.send(tables.to_tree()) + + + def get_stream(self): + with xmlfile(self.out) as xf: + with xf.element("worksheet", xmlns=SHEET_MAIN_NS): + try: + while True: + el = (yield) + if el is True: + yield xf + elif el is None: # et_xmlfile chokes + continue + else: + xf.write(el) + except GeneratorExit: + pass + + + def write_tail(self): + """ + Write all elements after the rows + calc properties + protection + protected ranges # + scenarios + filters + sorts # always ignored + data consolidation # + custom views # + merged cells + phonetic properties # + conditional formatting + data validation + hyperlinks + print options + page margins + page setup + header + row breaks + col breaks + custom properties # + cell watches # + ignored errors # + smart tags # + drawing + drawingHF # + background # + OLE objects # + controls # + web publishing # + tables + """ + self.write_protection() + self.write_scenarios() + self.write_filter() + self.write_merged_cells() + self.write_formatting() + self.write_validations() + self.write_hyperlinks() + self.write_print() + self.write_margins() + self.write_page() + self.write_header() + self.write_breaks() + self.write_drawings() + self.write_legacy() + self.write_tables() + + + def write(self): + """ + High level + """ + self.write_top() + self.write_rows() + self.write_tail() + self.close() + + + def close(self): + """ + Close the context manager + """ + if self.xf: + self.xf.close() + + + def read(self): + """ + Close the context manager and return serialised XML + """ + self.close() + if isinstance(self.out, BytesIO): + return self.out.getvalue() + with open(self.out, "rb") as src: + out = src.read() + + return out + + + def cleanup(self): + """ + Remove tempfile + """ + os.remove(self.out) + ALL_TEMP_FILES.remove(self.out) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py new file mode 100644 index 00000000..2fbf5e22 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py @@ -0,0 +1,512 @@ +# Copyright (c) 2010-2024 openpyxl + +from copy import copy +from operator import attrgetter + +from openpyxl.descriptors import Strict +from openpyxl.descriptors import MinMax +from openpyxl.descriptors.sequence import UniqueSequence +from openpyxl.descriptors.serialisable import Serialisable + +from openpyxl.utils import ( + range_boundaries, + range_to_tuple, + get_column_letter, + quote_sheetname, +) + +class CellRange(Serialisable): + """ + Represents a range in a sheet: title and coordinates. + + This object is used to perform operations on ranges, like: + + - shift, expand or shrink + - union/intersection with another sheet range, + + We can check whether a range is: + + - equal or not equal to another, + - disjoint of another, + - contained in another. + + We can get: + + - the size of a range. + - the range bounds (vertices) + - the coordinates, + - the string representation, + + """ + + min_col = MinMax(min=1, max=18278, expected_type=int) + min_row = MinMax(min=1, max=1048576, expected_type=int) + max_col = MinMax(min=1, max=18278, expected_type=int) + max_row = MinMax(min=1, max=1048576, expected_type=int) + + + def __init__(self, range_string=None, min_col=None, min_row=None, + max_col=None, max_row=None, title=None): + if range_string is not None: + if "!" in range_string: + title, (min_col, min_row, max_col, max_row) = range_to_tuple(range_string) + else: + min_col, min_row, max_col, max_row = range_boundaries(range_string) + + self.min_col = min_col + self.min_row = min_row + self.max_col = max_col + self.max_row = max_row + self.title = title + + if min_col > max_col: + fmt = "{max_col} must be greater than {min_col}" + raise ValueError(fmt.format(min_col=min_col, max_col=max_col)) + if min_row > max_row: + fmt = "{max_row} must be greater than {min_row}" + raise ValueError(fmt.format(min_row=min_row, max_row=max_row)) + + + @property + def bounds(self): + """ + Vertices of the range as a tuple + """ + return self.min_col, self.min_row, self.max_col, self.max_row + + + @property + def coord(self): + """ + Excel-style representation of the range + """ + fmt = "{min_col}{min_row}:{max_col}{max_row}" + if (self.min_col == self.max_col + and self.min_row == self.max_row): + fmt = "{min_col}{min_row}" + + return fmt.format( + min_col=get_column_letter(self.min_col), + min_row=self.min_row, + max_col=get_column_letter(self.max_col), + max_row=self.max_row + ) + + @property + def rows(self): + """ + Return cell coordinates as rows + """ + for row in range(self.min_row, self.max_row+1): + yield [(row, col) for col in range(self.min_col, self.max_col+1)] + + + @property + def cols(self): + """ + Return cell coordinates as columns + """ + for col in range(self.min_col, self.max_col+1): + yield [(row, col) for row in range(self.min_row, self.max_row+1)] + + + @property + def cells(self): + from itertools import product + return product(range(self.min_row, self.max_row+1), range(self.min_col, self.max_col+1)) + + + def _check_title(self, other): + """ + Check whether comparisons between ranges are possible. + Cannot compare ranges from different worksheets + Skip if the range passed in has no title. + """ + if not isinstance(other, CellRange): + raise TypeError(repr(type(other))) + + if other.title and self.title != other.title: + raise ValueError("Cannot work with ranges from different worksheets") + + + def __repr__(self): + fmt = u"<{cls} {coord}>" + if self.title: + fmt = u"<{cls} {title!r}!{coord}>" + return fmt.format(cls=self.__class__.__name__, title=self.title, coord=self.coord) + + + def __hash__(self): + return hash((self.min_row, self.min_col, self.max_row, self.max_col)) + + + def __str__(self): + fmt = "{coord}" + title = self.title + if title: + fmt = u"{title}!{coord}" + title = quote_sheetname(title) + return fmt.format(title=title, coord=self.coord) + + + def __copy__(self): + return self.__class__(min_col=self.min_col, min_row=self.min_row, + max_col=self.max_col, max_row=self.max_row, + title=self.title) + + + def shift(self, col_shift=0, row_shift=0): + """ + Shift the focus of the range according to the shift values (*col_shift*, *row_shift*). + + :type col_shift: int + :param col_shift: number of columns to be moved by, can be negative + :type row_shift: int + :param row_shift: number of rows to be moved by, can be negative + :raise: :class:`ValueError` if any row or column index < 1 + """ + + if (self.min_col + col_shift <= 0 + or self.min_row + row_shift <= 0): + raise ValueError("Invalid shift value: col_shift={0}, row_shift={1}".format(col_shift, row_shift)) + self.min_col += col_shift + self.min_row += row_shift + self.max_col += col_shift + self.max_row += row_shift + + + def __ne__(self, other): + """ + Test whether the ranges are not equal. + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range + :return: ``True`` if *range* != *other*. + """ + try: + self._check_title(other) + except ValueError: + return True + + return ( + other.min_row != self.min_row + or self.max_row != other.max_row + or other.min_col != self.min_col + or self.max_col != other.max_col + ) + + + def __eq__(self, other): + """ + Test whether the ranges are equal. + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range + :return: ``True`` if *range* == *other*. + """ + return not self.__ne__(other) + + + def issubset(self, other): + """ + Test whether every cell in this range is also in *other*. + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range + :return: ``True`` if *range* <= *other*. + """ + self._check_title(other) + + return other.__superset(self) + + __le__ = issubset + + + def __lt__(self, other): + """ + Test whether *other* contains every cell of this range, and more. + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range + :return: ``True`` if *range* < *other*. + """ + return self.__le__(other) and self.__ne__(other) + + + def __superset(self, other): + return ( + (self.min_row <= other.min_row <= other.max_row <= self.max_row) + and + (self.min_col <= other.min_col <= other.max_col <= self.max_col) + ) + + + def issuperset(self, other): + """ + Test whether every cell in *other* is in this range. + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range + :return: ``True`` if *range* >= *other* (or *other* in *range*). + """ + self._check_title(other) + + return self.__superset(other) + + __ge__ = issuperset + + + def __contains__(self, coord): + """ + Check whether the range contains a particular cell coordinate + """ + cr = self.__class__(coord) + return self.__superset(cr) + + + def __gt__(self, other): + """ + Test whether this range contains every cell in *other*, and more. + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range + :return: ``True`` if *range* > *other*. + """ + return self.__ge__(other) and self.__ne__(other) + + + def isdisjoint(self, other): + """ + Return ``True`` if this range has no cell in common with *other*. + Ranges are disjoint if and only if their intersection is the empty range. + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range. + :return: ``True`` if the range has no cells in common with other. + """ + self._check_title(other) + + # Sort by top-left vertex + if self.bounds > other.bounds: + self, other = other, self + + return (self.max_col < other.min_col + or self.max_row < other.min_row + or other.max_row < self.min_row) + + + def intersection(self, other): + """ + Return a new range with cells common to this range and *other* + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range. + :return: the intersecting sheet range. + :raise: :class:`ValueError` if the *other* range doesn't intersect + with this range. + """ + if self.isdisjoint(other): + raise ValueError("Range {0} doesn't intersect {0}".format(self, other)) + + min_row = max(self.min_row, other.min_row) + max_row = min(self.max_row, other.max_row) + min_col = max(self.min_col, other.min_col) + max_col = min(self.max_col, other.max_col) + + return CellRange(min_col=min_col, min_row=min_row, max_col=max_col, + max_row=max_row) + + __and__ = intersection + + + def union(self, other): + """ + Return the minimal superset of this range and *other*. This new range + will contain all cells from this range, *other*, and any additional + cells required to form a rectangular ``CellRange``. + + :type other: openpyxl.worksheet.cell_range.CellRange + :param other: Other sheet range. + :return: a ``CellRange`` that is a superset of this and *other*. + """ + self._check_title(other) + + min_row = min(self.min_row, other.min_row) + max_row = max(self.max_row, other.max_row) + min_col = min(self.min_col, other.min_col) + max_col = max(self.max_col, other.max_col) + return CellRange(min_col=min_col, min_row=min_row, max_col=max_col, + max_row=max_row, title=self.title) + + __or__ = union + + + def __iter__(self): + """ + For use as a dictionary elsewhere in the library. + """ + for x in self.__attrs__: + if x == "title": + continue + v = getattr(self, x) + yield x, v + + + def expand(self, right=0, down=0, left=0, up=0): + """ + Expand the range by the dimensions provided. + + :type right: int + :param right: expand range to the right by this number of cells + :type down: int + :param down: expand range down by this number of cells + :type left: int + :param left: expand range to the left by this number of cells + :type up: int + :param up: expand range up by this number of cells + """ + self.min_col -= left + self.min_row -= up + self.max_col += right + self.max_row += down + + + def shrink(self, right=0, bottom=0, left=0, top=0): + """ + Shrink the range by the dimensions provided. + + :type right: int + :param right: shrink range from the right by this number of cells + :type down: int + :param down: shrink range from the top by this number of cells + :type left: int + :param left: shrink range from the left by this number of cells + :type up: int + :param up: shrink range from the bottom by this number of cells + """ + self.min_col += left + self.min_row += top + self.max_col -= right + self.max_row -= bottom + + + @property + def size(self): + """ Return the size of the range as a dictionary of rows and columns. """ + cols = self.max_col + 1 - self.min_col + rows = self.max_row + 1 - self.min_row + return {'columns':cols, 'rows':rows} + + + @property + def top(self): + """A list of cell coordinates that comprise the top of the range""" + return [(self.min_row, col) for col in range(self.min_col, self.max_col+1)] + + + @property + def bottom(self): + """A list of cell coordinates that comprise the bottom of the range""" + return [(self.max_row, col) for col in range(self.min_col, self.max_col+1)] + + + @property + def left(self): + """A list of cell coordinates that comprise the left-side of the range""" + return [(row, self.min_col) for row in range(self.min_row, self.max_row+1)] + + + @property + def right(self): + """A list of cell coordinates that comprise the right-side of the range""" + return [(row, self.max_col) for row in range(self.min_row, self.max_row+1)] + + +class MultiCellRange(Strict): + + + ranges = UniqueSequence(expected_type=CellRange) + + + def __init__(self, ranges=set()): + if isinstance(ranges, str): + ranges = [CellRange(r) for r in ranges.split()] + self.ranges = set(ranges) + + + def __contains__(self, coord): + if isinstance(coord, str): + coord = CellRange(coord) + for r in self.ranges: + if coord <= r: + return True + return False + + + def __repr__(self): + ranges = " ".join([str(r) for r in self.sorted()]) + return f"<{self.__class__.__name__} [{ranges}]>" + + + def __str__(self): + ranges = u" ".join([str(r) for r in self.sorted()]) + return ranges + + + def __hash__(self): + return hash(str(self)) + + + def sorted(self): + """ + Return a sorted list of items + """ + return sorted(self.ranges, key=attrgetter('min_col', 'min_row', 'max_col', 'max_row')) + + + def add(self, coord): + """ + Add a cell coordinate or CellRange + """ + cr = coord + if isinstance(coord, str): + cr = CellRange(coord) + elif not isinstance(coord, CellRange): + raise ValueError("You can only add CellRanges") + if cr not in self: + self.ranges.add(cr) + + + def __iadd__(self, coord): + self.add(coord) + return self + + + def __eq__(self, other): + if isinstance(other, str): + other = self.__class__(other) + return self.ranges == other.ranges + + + def __ne__(self, other): + return not self == other + + + def __bool__(self): + return bool(self.ranges) + + + def remove(self, coord): + if not isinstance(coord, CellRange): + coord = CellRange(coord) + self.ranges.remove(coord) + + + def __iter__(self): + for cr in self.ranges: + yield cr + + + def __copy__(self): + ranges = {copy(r) for r in self.ranges} + return MultiCellRange(ranges) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_watch.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_watch.py new file mode 100644 index 00000000..dea89caf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_watch.py @@ -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 + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/controls.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/controls.py new file mode 100644 index 00000000..f1fd1c9e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/controls.py @@ -0,0 +1,107 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Integer, + String, + Sequence, +) + +from openpyxl.descriptors.excel import Relation +from .ole import ObjectAnchor + + +class ControlProperty(Serialisable): + + tagname = "controlPr" + + anchor = Typed(expected_type=ObjectAnchor, ) + locked = Bool(allow_none=True) + defaultSize = Bool(allow_none=True) + _print = Bool(allow_none=True) + disabled = Bool(allow_none=True) + recalcAlways = Bool(allow_none=True) + uiObject = Bool(allow_none=True) + autoFill = Bool(allow_none=True) + autoLine = Bool(allow_none=True) + autoPict = Bool(allow_none=True) + macro = String(allow_none=True) + altText = String(allow_none=True) + linkedCell = String(allow_none=True) + listFillRange = String(allow_none=True) + cf = String(allow_none=True) + id = Relation(allow_none=True) + + __elements__ = ('anchor',) + + def __init__(self, + anchor=None, + locked=True, + defaultSize=True, + _print=True, + disabled=False, + recalcAlways=False, + uiObject=False, + autoFill=True, + autoLine=True, + autoPict=True, + macro=None, + altText=None, + linkedCell=None, + listFillRange=None, + cf='pict', + id=None, + ): + self.anchor = anchor + self.locked = locked + self.defaultSize = defaultSize + self._print = _print + self.disabled = disabled + self.recalcAlways = recalcAlways + self.uiObject = uiObject + self.autoFill = autoFill + self.autoLine = autoLine + self.autoPict = autoPict + self.macro = macro + self.altText = altText + self.linkedCell = linkedCell + self.listFillRange = listFillRange + self.cf = cf + self.id = id + + +class Control(Serialisable): + + tagname = "control" + + controlPr = Typed(expected_type=ControlProperty, allow_none=True) + shapeId = Integer() + name = String(allow_none=True) + + __elements__ = ('controlPr',) + + def __init__(self, + controlPr=None, + shapeId=None, + name=None, + ): + self.controlPr = controlPr + self.shapeId = shapeId + self.name = name + + +class Controls(Serialisable): + + tagname = "controls" + + control = Sequence(expected_type=Control) + + __elements__ = ('control',) + + def __init__(self, + control=(), + ): + self.control = control + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/copier.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/copier.py new file mode 100644 index 00000000..f6601540 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/copier.py @@ -0,0 +1,70 @@ +# Copyright (c) 2010-2024 openpyxl + +#standard lib imports +from copy import copy + +from .worksheet import Worksheet + + +class WorksheetCopy: + """ + Copy the values, styles, dimensions, merged cells, margins, and + print/page setup from one worksheet to another within the same + workbook. + """ + + def __init__(self, source_worksheet, target_worksheet): + self.source = source_worksheet + self.target = target_worksheet + self._verify_resources() + + + def _verify_resources(self): + + if (not isinstance(self.source, Worksheet) + and not isinstance(self.target, Worksheet)): + raise TypeError("Can only copy worksheets") + + if self.source is self.target: + raise ValueError("Cannot copy a worksheet to itself") + + if self.source.parent != self.target.parent: + raise ValueError('Cannot copy between worksheets from different workbooks') + + + def copy_worksheet(self): + self._copy_cells() + self._copy_dimensions() + + self.target.sheet_format = copy(self.source.sheet_format) + self.target.sheet_properties = copy(self.source.sheet_properties) + self.target.merged_cells = copy(self.source.merged_cells) + self.target.page_margins = copy(self.source.page_margins) + self.target.page_setup = copy(self.source.page_setup) + self.target.print_options = copy(self.source.print_options) + + + def _copy_cells(self): + for (row, col), source_cell in self.source._cells.items(): + target_cell = self.target.cell(column=col, row=row) + + target_cell._value = source_cell._value + target_cell.data_type = source_cell.data_type + + if source_cell.has_style: + target_cell._style = copy(source_cell._style) + + if source_cell.hyperlink: + target_cell._hyperlink = copy(source_cell.hyperlink) + + if source_cell.comment: + target_cell.comment = copy(source_cell.comment) + + + def _copy_dimensions(self): + for attr in ('row_dimensions', 'column_dimensions'): + src = getattr(self.source, attr) + target = getattr(self.target, attr) + for key, dim in src.items(): + target[key] = copy(dim) + target[key].worksheet = self.target diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/custom.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/custom.py new file mode 100644 index 00000000..b3af5c91 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/custom.py @@ -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 + 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 diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/dimensions.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/dimensions.py new file mode 100644 index 00000000..482717a1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/dimensions.py @@ -0,0 +1,306 @@ +# Copyright (c) 2010-2024 openpyxl + +from copy import copy + +from openpyxl.compat import safe_string +from openpyxl.utils import ( + get_column_letter, + get_column_interval, + column_index_from_string, + range_boundaries, +) +from openpyxl.utils.units import DEFAULT_COLUMN_WIDTH +from openpyxl.descriptors import ( + Integer, + Float, + Bool, + Strict, + String, + Alias, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.styles.styleable import StyleableObject +from openpyxl.utils.bound_dictionary import BoundDictionary +from openpyxl.xml.functions import Element + + +class Dimension(Strict, StyleableObject): + """Information about the display properties of a row or column.""" + __fields__ = ('hidden', + 'outlineLevel', + 'collapsed',) + + index = Integer() + hidden = Bool() + outlineLevel = Integer(allow_none=True) + outline_level = Alias('outlineLevel') + collapsed = Bool() + style = Alias('style_id') + + + def __init__(self, index, hidden, outlineLevel, + collapsed, worksheet, visible=True, style=None): + super().__init__(sheet=worksheet, style_array=style) + self.index = index + self.hidden = hidden + self.outlineLevel = outlineLevel + self.collapsed = collapsed + + + def __iter__(self): + for key in self.__fields__: + value = getattr(self, key, None) + if value: + yield key, safe_string(value) + + + def __copy__(self): + cp = self.__new__(self.__class__) + attrib = self.__dict__ + attrib['worksheet'] = self.parent + cp.__init__(**attrib) + cp._style = copy(self._style) + return cp + + + def __repr__(self): + return f"<{self.__class__.__name__} Instance, Attributes={dict(self)}>" + + +class RowDimension(Dimension): + """Information about the display properties of a row.""" + + __fields__ = Dimension.__fields__ + ('ht', 'customFormat', 'customHeight', 's', + 'thickBot', 'thickTop') + r = Alias('index') + s = Alias('style_id') + ht = Float(allow_none=True) + height = Alias('ht') + thickBot = Bool() + thickTop = Bool() + + def __init__(self, + worksheet, + index=0, + ht=None, + customHeight=None, # do not write + s=None, + customFormat=None, # do not write + hidden=False, + outlineLevel=0, + outline_level=None, + collapsed=False, + visible=None, + height=None, + r=None, + spans=None, + thickBot=None, + thickTop=None, + **kw + ): + if r is not None: + index = r + if height is not None: + ht = height + self.ht = ht + if visible is not None: + hidden = not visible + if outline_level is not None: + outlineLevel = outline_level + self.thickBot = thickBot + self.thickTop = thickTop + super().__init__(index, hidden, outlineLevel, + collapsed, worksheet, style=s) + + @property + def customFormat(self): + """Always true if there is a style for the row""" + return self.has_style + + @property + def customHeight(self): + """Always true if there is a height for the row""" + return self.ht is not None + + +class ColumnDimension(Dimension): + """Information about the display properties of a column.""" + + width = Float() + bestFit = Bool() + auto_size = Alias('bestFit') + index = String() + min = Integer(allow_none=True) + max = Integer(allow_none=True) + collapsed = Bool() + + __fields__ = Dimension.__fields__ + ('width', 'bestFit', 'customWidth', 'style', + 'min', 'max') + + def __init__(self, + worksheet, + index='A', + width=DEFAULT_COLUMN_WIDTH, + bestFit=False, + hidden=False, + outlineLevel=0, + outline_level=None, + collapsed=False, + style=None, + min=None, + max=None, + customWidth=False, # do not write + visible=None, + auto_size=None,): + self.width = width + self.min = min + self.max = max + if visible is not None: + hidden = not visible + if auto_size is not None: + bestFit = auto_size + self.bestFit = bestFit + if outline_level is not None: + outlineLevel = outline_level + self.collapsed = collapsed + super().__init__(index, hidden, outlineLevel, + collapsed, worksheet, style=style) + + + @property + def customWidth(self): + """Always true if there is a width for the column""" + return bool(self.width) + + + def reindex(self): + """ + Set boundaries for column definition + """ + if not all([self.min, self.max]): + self.min = self.max = column_index_from_string(self.index) + + @property + def range(self): + """Return the range of cells actually covered""" + return f"{get_column_letter(self.min)}:{get_column_letter(self.max)}" + + + def to_tree(self): + attrs = dict(self) + if attrs.keys() != {'min', 'max'}: + return Element("col", **attrs) + + +class DimensionHolder(BoundDictionary): + """ + Allow columns to be grouped + """ + + def __init__(self, worksheet, reference="index", default_factory=None): + self.worksheet = worksheet + self.max_outline = None + self.default_factory = default_factory + super().__init__(reference, default_factory) + + + def group(self, start, end=None, outline_level=1, hidden=False): + """allow grouping a range of consecutive rows or columns together + + :param start: first row or column to be grouped (mandatory) + :param end: last row or column to be grouped (optional, default to start) + :param outline_level: outline level + :param hidden: should the group be hidden on workbook open or not + """ + if end is None: + end = start + + if isinstance(self.default_factory(), ColumnDimension): + new_dim = self[start] + new_dim.outline_level = outline_level + new_dim.hidden = hidden + work_sequence = get_column_interval(start, end)[1:] + for column_letter in work_sequence: + if column_letter in self: + del self[column_letter] + new_dim.min, new_dim.max = map(column_index_from_string, (start, end)) + elif isinstance(self.default_factory(), RowDimension): + for el in range(start, end + 1): + new_dim = self.worksheet.row_dimensions[el] + new_dim.outline_level = outline_level + new_dim.hidden = hidden + + + def to_tree(self): + + def sorter(value): + value.reindex() + return value.min + + el = Element('cols') + outlines = set() + + for col in sorted(self.values(), key=sorter): + obj = col.to_tree() + if obj is not None: + outlines.add(col.outlineLevel) + el.append(obj) + + if outlines: + self.max_outline = max(outlines) + + if len(el): + return el # must have at least one child + + +class SheetFormatProperties(Serialisable): + + tagname = "sheetFormatPr" + + baseColWidth = Integer(allow_none=True) + defaultColWidth = Float(allow_none=True) + defaultRowHeight = Float() + customHeight = Bool(allow_none=True) + zeroHeight = Bool(allow_none=True) + thickTop = Bool(allow_none=True) + thickBottom = Bool(allow_none=True) + outlineLevelRow = Integer(allow_none=True) + outlineLevelCol = Integer(allow_none=True) + + def __init__(self, + baseColWidth=8, #according to spec + defaultColWidth=None, + defaultRowHeight=15, + customHeight=None, + zeroHeight=None, + thickTop=None, + thickBottom=None, + outlineLevelRow=None, + outlineLevelCol=None, + ): + self.baseColWidth = baseColWidth + self.defaultColWidth = defaultColWidth + self.defaultRowHeight = defaultRowHeight + self.customHeight = customHeight + self.zeroHeight = zeroHeight + self.thickTop = thickTop + self.thickBottom = thickBottom + self.outlineLevelRow = outlineLevelRow + self.outlineLevelCol = outlineLevelCol + + +class SheetDimension(Serialisable): + + tagname = "dimension" + + ref = String() + + def __init__(self, + ref=None, + ): + self.ref = ref + + + @property + def boundaries(self): + return range_boundaries(self.ref) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/drawing.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/drawing.py new file mode 100644 index 00000000..45bf4d35 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/drawing.py @@ -0,0 +1,14 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.excel import Relation + + +class Drawing(Serialisable): + + tagname = "drawing" + + id = Relation() + + def __init__(self, id=None): + self.id = id diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/errors.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/errors.py new file mode 100644 index 00000000..1bed3f78 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/errors.py @@ -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 + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/filters.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/filters.py new file mode 100644 index 00000000..a2cfd8eb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/filters.py @@ -0,0 +1,486 @@ +# Copyright (c) 2010-2024 openpyxl + +import re + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + Set, + Float, + DateTime, + NoneSet, + Bool, + Integer, + String, + Sequence, + MinMax, +) +from openpyxl.descriptors.excel import ExtensionList, CellRange +from openpyxl.descriptors.sequence import ValueSequence +from openpyxl.utils import absolute_coordinate + + +class SortCondition(Serialisable): + + tagname = "sortCondition" + + descending = Bool(allow_none=True) + sortBy = NoneSet(values=(['value', 'cellColor', 'fontColor', 'icon'])) + ref = CellRange() + customList = String(allow_none=True) + dxfId = Integer(allow_none=True) + iconSet = NoneSet(values=(['3Arrows', '3ArrowsGray', '3Flags', + '3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2', + '4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights', + '5Arrows', '5ArrowsGray', '5Rating', '5Quarters'])) + iconId = Integer(allow_none=True) + + def __init__(self, + ref=None, + descending=None, + sortBy=None, + customList=None, + dxfId=None, + iconSet=None, + iconId=None, + ): + self.descending = descending + self.sortBy = sortBy + self.ref = ref + self.customList = customList + self.dxfId = dxfId + self.iconSet = iconSet + self.iconId = iconId + + +class SortState(Serialisable): + + tagname = "sortState" + + columnSort = Bool(allow_none=True) + caseSensitive = Bool(allow_none=True) + sortMethod = NoneSet(values=(['stroke', 'pinYin'])) + ref = CellRange() + sortCondition = Sequence(expected_type=SortCondition, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('sortCondition',) + + def __init__(self, + columnSort=None, + caseSensitive=None, + sortMethod=None, + ref=None, + sortCondition=(), + extLst=None, + ): + self.columnSort = columnSort + self.caseSensitive = caseSensitive + self.sortMethod = sortMethod + self.ref = ref + self.sortCondition = sortCondition + + + def __bool__(self): + return self.ref is not None + + + +class IconFilter(Serialisable): + + tagname = "iconFilter" + + iconSet = Set(values=(['3Arrows', '3ArrowsGray', '3Flags', + '3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2', + '4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights', + '5Arrows', '5ArrowsGray', '5Rating', '5Quarters'])) + iconId = Integer(allow_none=True) + + def __init__(self, + iconSet=None, + iconId=None, + ): + self.iconSet = iconSet + self.iconId = iconId + + +class ColorFilter(Serialisable): + + tagname = "colorFilter" + + dxfId = Integer(allow_none=True) + cellColor = Bool(allow_none=True) + + def __init__(self, + dxfId=None, + cellColor=None, + ): + self.dxfId = dxfId + self.cellColor = cellColor + + +class DynamicFilter(Serialisable): + + tagname = "dynamicFilter" + + type = Set(values=(['null', 'aboveAverage', 'belowAverage', 'tomorrow', + 'today', 'yesterday', 'nextWeek', 'thisWeek', 'lastWeek', 'nextMonth', + 'thisMonth', 'lastMonth', 'nextQuarter', 'thisQuarter', 'lastQuarter', + 'nextYear', 'thisYear', 'lastYear', 'yearToDate', 'Q1', 'Q2', 'Q3', 'Q4', + 'M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11', + 'M12'])) + val = Float(allow_none=True) + valIso = DateTime(allow_none=True) + maxVal = Float(allow_none=True) + maxValIso = DateTime(allow_none=True) + + def __init__(self, + type=None, + val=None, + valIso=None, + maxVal=None, + maxValIso=None, + ): + self.type = type + self.val = val + self.valIso = valIso + self.maxVal = maxVal + self.maxValIso = maxValIso + + +class CustomFilter(Serialisable): + + tagname = "customFilter" + + val = String() + operator = Set(values=['equal', 'lessThan', 'lessThanOrEqual', + 'notEqual', 'greaterThanOrEqual', 'greaterThan']) + + def __init__(self, operator="equal", val=None): + self.operator = operator + self.val = val + + + def _get_subtype(self): + if self.val == " ": + subtype = BlankFilter + else: + try: + float(self.val) + subtype = NumberFilter + except ValueError: + subtype = StringFilter + return subtype + + + def convert(self): + """Convert to more specific filter""" + typ = self._get_subtype() + if typ in (BlankFilter, NumberFilter): + return typ(**dict(self)) + + operator, term = StringFilter._guess_operator(self.val) + flt = StringFilter(operator, term) + if self.operator == "notEqual": + flt.exclude = True + return flt + + +class BlankFilter(CustomFilter): + """ + Exclude blanks + """ + + __attrs__ = ("operator", "val") + + def __init__(self, **kw): + pass + + + @property + def operator(self): + return "notEqual" + + + @property + def val(self): + return " " + + +class NumberFilter(CustomFilter): + + + operator = Set(values= + ['equal', 'lessThan', 'lessThanOrEqual', + 'notEqual', 'greaterThanOrEqual', 'greaterThan']) + val = Float() + + def __init__(self, operator="equal", val=None): + self.operator = operator + self.val = val + + +string_format_mapping = { + "contains": "*{}*", + "startswith": "{}*", + "endswith": "*{}", + "wildcard": "{}", +} + + +class StringFilter(CustomFilter): + + operator = Set(values=['contains', 'startswith', 'endswith', 'wildcard'] + ) + val = String() + exclude = Bool() + + + def __init__(self, operator="contains", val=None, exclude=False): + self.operator = operator + self.val = val + self.exclude = exclude + + + def _escape(self): + """Escape wildcards ~, * ? when serialising""" + if self.operator == "wildcard": + return self.val + return re.sub(r"~|\*|\?", r"~\g<0>", self.val) + + + @staticmethod + def _unescape(value): + """ + Unescape value + """ + return re.sub(r"~(?P<op>[~*?])", r"\g<op>", value) + + + @staticmethod + def _guess_operator(value): + value = StringFilter._unescape(value) + endswith = r"^(?P<endswith>\*)(?P<term>[^\*\?]*$)" + startswith = r"^(?P<term>[^\*\?]*)(?P<startswith>\*)$" + contains = r"^(?P<contains>\*)(?P<term>[^\*\?]*)\*$" + d = {"wildcard": True, "term": value} + for pat in [contains, startswith, endswith]: + m = re.match(pat, value) + if m: + d = m.groupdict() + + term = d.pop("term") + op = list(d)[0] + return op, term + + + def to_tree(self, tagname=None, idx=None, namespace=None): + fmt = string_format_mapping[self.operator] + op = self.exclude and "notEqual" or "equal" + value = fmt.format(self._escape()) + flt = CustomFilter(op, value) + return flt.to_tree(tagname, idx, namespace) + + +class CustomFilters(Serialisable): + + tagname = "customFilters" + + _and = Bool(allow_none=True) + customFilter = Sequence(expected_type=CustomFilter) # min 1, max 2 + + __elements__ = ('customFilter',) + + def __init__(self, + _and=None, + customFilter=(), + ): + self._and = _and + self.customFilter = customFilter + + +class Top10(Serialisable): + + tagname = "top10" + + top = Bool(allow_none=True) + percent = Bool(allow_none=True) + val = Float() + filterVal = Float(allow_none=True) + + def __init__(self, + top=None, + percent=None, + val=None, + filterVal=None, + ): + self.top = top + self.percent = percent + self.val = val + self.filterVal = filterVal + + +class DateGroupItem(Serialisable): + + tagname = "dateGroupItem" + + year = Integer() + month = MinMax(min=1, max=12, allow_none=True) + day = MinMax(min=1, max=31, allow_none=True) + hour = MinMax(min=0, max=23, allow_none=True) + minute = MinMax(min=0, max=59, allow_none=True) + second = Integer(min=0, max=59, allow_none=True) + dateTimeGrouping = Set(values=(['year', 'month', 'day', 'hour', 'minute', + 'second'])) + + def __init__(self, + year=None, + month=None, + day=None, + hour=None, + minute=None, + second=None, + dateTimeGrouping=None, + ): + self.year = year + self.month = month + self.day = day + self.hour = hour + self.minute = minute + self.second = second + self.dateTimeGrouping = dateTimeGrouping + + +class Filters(Serialisable): + + tagname = "filters" + + blank = Bool(allow_none=True) + calendarType = NoneSet(values=["gregorian","gregorianUs", + "gregorianMeFrench","gregorianArabic", "hijri","hebrew", + "taiwan","japan", "thai","korea", + "saka","gregorianXlitEnglish","gregorianXlitFrench"]) + filter = ValueSequence(expected_type=str) + dateGroupItem = Sequence(expected_type=DateGroupItem, allow_none=True) + + __elements__ = ('filter', 'dateGroupItem') + + def __init__(self, + blank=None, + calendarType=None, + filter=(), + dateGroupItem=(), + ): + self.blank = blank + self.calendarType = calendarType + self.filter = filter + self.dateGroupItem = dateGroupItem + + +class FilterColumn(Serialisable): + + tagname = "filterColumn" + + colId = Integer() + col_id = Alias('colId') + hiddenButton = Bool(allow_none=True) + showButton = Bool(allow_none=True) + # some elements are choice + filters = Typed(expected_type=Filters, allow_none=True) + top10 = Typed(expected_type=Top10, allow_none=True) + customFilters = Typed(expected_type=CustomFilters, allow_none=True) + dynamicFilter = Typed(expected_type=DynamicFilter, allow_none=True) + colorFilter = Typed(expected_type=ColorFilter, allow_none=True) + iconFilter = Typed(expected_type=IconFilter, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('filters', 'top10', 'customFilters', 'dynamicFilter', + 'colorFilter', 'iconFilter') + + def __init__(self, + colId=None, + hiddenButton=False, + showButton=True, + filters=None, + top10=None, + customFilters=None, + dynamicFilter=None, + colorFilter=None, + iconFilter=None, + extLst=None, + blank=None, + vals=None, + ): + self.colId = colId + self.hiddenButton = hiddenButton + self.showButton = showButton + self.filters = filters + self.top10 = top10 + self.customFilters = customFilters + self.dynamicFilter = dynamicFilter + self.colorFilter = colorFilter + self.iconFilter = iconFilter + if blank is not None and self.filters: + self.filters.blank = blank + if vals is not None and self.filters: + self.filters.filter = vals + + +class AutoFilter(Serialisable): + + tagname = "autoFilter" + + ref = CellRange() + filterColumn = Sequence(expected_type=FilterColumn, allow_none=True) + sortState = Typed(expected_type=SortState, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('filterColumn', 'sortState') + + def __init__(self, + ref=None, + filterColumn=(), + sortState=None, + extLst=None, + ): + self.ref = ref + self.filterColumn = filterColumn + self.sortState = sortState + + + def __bool__(self): + return self.ref is not None + + + def __str__(self): + return absolute_coordinate(self.ref) + + + def add_filter_column(self, col_id, vals, blank=False): + """ + Add row filter for specified column. + + :param col_id: Zero-origin column id. 0 means first column. + :type col_id: int + :param vals: Value list to show. + :type vals: str[] + :param blank: Show rows that have blank cell if True (default=``False``) + :type blank: bool + """ + self.filterColumn.append(FilterColumn(colId=col_id, filters=Filters(blank=blank, filter=vals))) + + + def add_sort_condition(self, ref, descending=False): + """ + Add sort condition for cpecified range of cells. + + :param ref: range of the cells (e.g. 'A2:A150') + :type ref: string, is the same as that of the filter + :param descending: Descending sort order (default=``False``) + :type descending: bool + """ + cond = SortCondition(ref, descending) + if self.sortState is None: + self.sortState = SortState(ref=self.ref) + self.sortState.sortCondition.append(cond) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/formula.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/formula.py new file mode 100644 index 00000000..7eb920e9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/formula.py @@ -0,0 +1,51 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string + +class DataTableFormula: + + + t = "dataTable" + + def __init__(self, + ref, + ca=False, + dt2D=False, + dtr=False, + r1=None, + r2=None, + del1=False, + del2=False, + **kw): + self.ref = ref + self.ca = ca + self.dt2D = dt2D + self.dtr = dtr + self.r1 = r1 + self.r2 = r2 + self.del1 = del1 + self.del2 = del2 + + + def __iter__(self): + for k in ["t", "ref", "dt2D", "dtr", "r1", "r2", "del1", "del2", "ca"]: + v = getattr(self, k) + if v: + yield k, safe_string(v) + + +class ArrayFormula: + + t = "array" + + + def __init__(self, ref, text=None): + self.ref = ref + self.text = text + + + def __iter__(self): + for k in ["t", "ref"]: + v = getattr(self, k) + if v: + yield k, safe_string(v) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py new file mode 100644 index 00000000..598aa23d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py @@ -0,0 +1,270 @@ +# Copyright (c) 2010-2024 openpyxl + +# Simplified implementation of headers and footers: let worksheets have separate items + +import re +from warnings import warn + +from openpyxl.descriptors import ( + Alias, + Bool, + Strict, + String, + Integer, + MatchPattern, + Typed, +) +from openpyxl.descriptors.serialisable import Serialisable + + +from openpyxl.xml.functions import Element +from openpyxl.utils.escape import escape, unescape + + +FONT_PATTERN = '&"(?P<font>.+)"' +COLOR_PATTERN = "&K(?P<color>[A-F0-9]{6})" +SIZE_REGEX = r"&(?P<size>\d+\s?)" +FORMAT_REGEX = re.compile("{0}|{1}|{2}".format(FONT_PATTERN, COLOR_PATTERN, + SIZE_REGEX) + ) + +def _split_string(text): + """ + Split the combined (decoded) string into left, center and right parts + + # See http://stackoverflow.com/questions/27711175/regex-with-multiple-optional-groups for discussion + """ + + ITEM_REGEX = re.compile(""" + (&L(?P<left>.+?))? + (&C(?P<center>.+?))? + (&R(?P<right>.+?))? + $""", re.VERBOSE | re.DOTALL) + + m = ITEM_REGEX.match(text) + try: + parts = m.groupdict() + except AttributeError: + warn("""Cannot parse header or footer so it will be ignored""") + parts = {'left':'', 'right':'', 'center':''} + return parts + + +class _HeaderFooterPart(Strict): + + """ + Individual left/center/right header/footer part + + Do not use directly. + + Header & Footer ampersand codes: + + * &A Inserts the worksheet name + * &B Toggles bold + * &D or &[Date] Inserts the current date + * &E Toggles double-underline + * &F or &[File] Inserts the workbook name + * &I Toggles italic + * &N or &[Pages] Inserts the total page count + * &S Toggles strikethrough + * &T Inserts the current time + * &[Tab] Inserts the worksheet name + * &U Toggles underline + * &X Toggles superscript + * &Y Toggles subscript + * &P or &[Page] Inserts the current page number + * &P+n Inserts the page number incremented by n + * &P-n Inserts the page number decremented by n + * &[Path] Inserts the workbook path + * && Escapes the ampersand character + * &"fontname" Selects the named font + * &nn Selects the specified 2-digit font point size + + Colours are in RGB Hex + """ + + text = String(allow_none=True) + font = String(allow_none=True) + size = Integer(allow_none=True) + RGB = ("^[A-Fa-f0-9]{6}$") + color = MatchPattern(allow_none=True, pattern=RGB) + + + def __init__(self, text=None, font=None, size=None, color=None): + self.text = text + self.font = font + self.size = size + self.color = color + + + def __str__(self): + """ + Convert to Excel HeaderFooter miniformat minus position + """ + fmt = [] + if self.font: + fmt.append(u'&"{0}"'.format(self.font)) + if self.size: + fmt.append("&{0} ".format(self.size)) + if self.color: + fmt.append("&K{0}".format(self.color)) + return u"".join(fmt + [self.text]) + + def __bool__(self): + return bool(self.text) + + + + @classmethod + def from_str(cls, text): + """ + Convert from miniformat to object + """ + keys = ('font', 'color', 'size') + kw = dict((k, v) for match in FORMAT_REGEX.findall(text) + for k, v in zip(keys, match) if v) + + kw['text'] = FORMAT_REGEX.sub('', text) + + return cls(**kw) + + +class HeaderFooterItem(Strict): + """ + Header or footer item + + """ + + left = Typed(expected_type=_HeaderFooterPart) + center = Typed(expected_type=_HeaderFooterPart) + centre = Alias("center") + right = Typed(expected_type=_HeaderFooterPart) + + __keys = ('L', 'C', 'R') + + + def __init__(self, left=None, right=None, center=None): + if left is None: + left = _HeaderFooterPart() + self.left = left + if center is None: + center = _HeaderFooterPart() + self.center = center + if right is None: + right = _HeaderFooterPart() + self.right = right + + + def __str__(self): + """ + Pack parts into a single string + """ + TRANSFORM = {'&[Tab]': '&A', '&[Pages]': '&N', '&[Date]': '&D', + '&[Path]': '&Z', '&[Page]': '&P', '&[Time]': '&T', '&[File]': '&F', + '&[Picture]': '&G'} + + # escape keys and create regex + SUBS_REGEX = re.compile("|".join(["({0})".format(re.escape(k)) + for k in TRANSFORM])) + + def replace(match): + """ + Callback for re.sub + Replace expanded control with mini-format equivalent + """ + sub = match.group(0) + return TRANSFORM[sub] + + txt = [] + for key, part in zip( + self.__keys, [self.left, self.center, self.right]): + if part.text is not None: + txt.append(u"&{0}{1}".format(key, str(part))) + txt = "".join(txt) + txt = SUBS_REGEX.sub(replace, txt) + return escape(txt) + + + def __bool__(self): + return any([self.left, self.center, self.right]) + + + + def to_tree(self, tagname): + """ + Return as XML node + """ + el = Element(tagname) + el.text = str(self) + return el + + + @classmethod + def from_tree(cls, node): + if node.text: + text = unescape(node.text) + parts = _split_string(text) + for k, v in parts.items(): + if v is not None: + parts[k] = _HeaderFooterPart.from_str(v) + self = cls(**parts) + return self + + +class HeaderFooter(Serialisable): + + tagname = "headerFooter" + + differentOddEven = Bool(allow_none=True) + differentFirst = Bool(allow_none=True) + scaleWithDoc = Bool(allow_none=True) + alignWithMargins = Bool(allow_none=True) + oddHeader = Typed(expected_type=HeaderFooterItem, allow_none=True) + oddFooter = Typed(expected_type=HeaderFooterItem, allow_none=True) + evenHeader = Typed(expected_type=HeaderFooterItem, allow_none=True) + evenFooter = Typed(expected_type=HeaderFooterItem, allow_none=True) + firstHeader = Typed(expected_type=HeaderFooterItem, allow_none=True) + firstFooter = Typed(expected_type=HeaderFooterItem, allow_none=True) + + __elements__ = ("oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter") + + def __init__(self, + differentOddEven=None, + differentFirst=None, + scaleWithDoc=None, + alignWithMargins=None, + oddHeader=None, + oddFooter=None, + evenHeader=None, + evenFooter=None, + firstHeader=None, + firstFooter=None, + ): + self.differentOddEven = differentOddEven + self.differentFirst = differentFirst + self.scaleWithDoc = scaleWithDoc + self.alignWithMargins = alignWithMargins + if oddHeader is None: + oddHeader = HeaderFooterItem() + self.oddHeader = oddHeader + if oddFooter is None: + oddFooter = HeaderFooterItem() + self.oddFooter = oddFooter + if evenHeader is None: + evenHeader = HeaderFooterItem() + self.evenHeader = evenHeader + if evenFooter is None: + evenFooter = HeaderFooterItem() + self.evenFooter = evenFooter + if firstHeader is None: + firstHeader = HeaderFooterItem() + self.firstHeader = firstHeader + if firstFooter is None: + firstFooter = HeaderFooterItem() + self.firstFooter = firstFooter + + + def __bool__(self): + parts = [getattr(self, attr) for attr in self.__attrs__ + self.__elements__] + return any(parts) + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/hyperlink.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/hyperlink.py new file mode 100644 index 00000000..332b4154 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/hyperlink.py @@ -0,0 +1,46 @@ +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + String, + Sequence, +) +from openpyxl.descriptors.excel import Relation + + +class Hyperlink(Serialisable): + + tagname = "hyperlink" + + ref = String() + location = String(allow_none=True) + tooltip = String(allow_none=True) + display = String(allow_none=True) + id = Relation() + target = String(allow_none=True) + + __attrs__ = ("ref", "location", "tooltip", "display", "id") + + def __init__(self, + ref=None, + location=None, + tooltip=None, + display=None, + id=None, + target=None, + ): + self.ref = ref + self.location = location + self.tooltip = tooltip + self.display = display + self.id = id + self.target = target + + +class HyperlinkList(Serialisable): + + tagname = "hyperlinks" + + __expected_type = Hyperlink + hyperlink = Sequence(expected_type=__expected_type) + + def __init__(self, hyperlink=()): + self.hyperlink = hyperlink diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/merge.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/merge.py new file mode 100644 index 00000000..a3a6bebd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/merge.py @@ -0,0 +1,141 @@ +# Copyright (c) 2010-2024 openpyxl + +import copy + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Integer, + Sequence, +) + +from openpyxl.cell.cell import MergedCell +from openpyxl.styles.borders import Border + +from .cell_range import CellRange + + +class MergeCell(CellRange): + + tagname = "mergeCell" + ref = CellRange.coord + + __attrs__ = ("ref",) + + + def __init__(self, + ref=None, + ): + super().__init__(ref) + + + def __copy__(self): + return self.__class__(self.ref) + + +class MergeCells(Serialisable): + + tagname = "mergeCells" + + count = Integer(allow_none=True) + mergeCell = Sequence(expected_type=MergeCell, ) + + __elements__ = ('mergeCell',) + __attrs__ = ('count',) + + def __init__(self, + count=None, + mergeCell=(), + ): + self.mergeCell = mergeCell + + + @property + def count(self): + return len(self.mergeCell) + + +class MergedCellRange(CellRange): + + """ + MergedCellRange stores the border information of a merged cell in the top + left cell of the merged cell. + The remaining cells in the merged cell are stored as MergedCell objects and + get their border information from the upper left cell. + """ + + def __init__(self, worksheet, coord): + self.ws = worksheet + super().__init__(range_string=coord) + self.start_cell = None + self._get_borders() + + + def _get_borders(self): + """ + If the upper left cell of the merged cell does not yet exist, it is + created. + The upper left cell gets the border information of the bottom and right + border from the bottom right cell of the merged cell, if available. + """ + + # Top-left cell. + self.start_cell = self.ws._cells.get((self.min_row, self.min_col)) + if self.start_cell is None: + self.start_cell = self.ws.cell(row=self.min_row, column=self.min_col) + + # Bottom-right cell + end_cell = self.ws._cells.get((self.max_row, self.max_col)) + if end_cell is not None: + self.start_cell.border += Border(right=end_cell.border.right, + bottom=end_cell.border.bottom) + + + def format(self): + """ + Each cell of the merged cell is created as MergedCell if it does not + already exist. + + The MergedCells at the edge of the merged cell gets its borders from + the upper left cell. + + - The top MergedCells get the top border from the top left cell. + - The bottom MergedCells get the bottom border from the top left cell. + - The left MergedCells get the left border from the top left cell. + - The right MergedCells get the right border from the top left cell. + """ + + names = ['top', 'left', 'right', 'bottom'] + + for name in names: + side = getattr(self.start_cell.border, name) + if side and side.style is None: + continue # don't need to do anything if there is no border style + border = Border(**{name:side}) + for coord in getattr(self, name): + cell = self.ws._cells.get(coord) + if cell is None: + row, col = coord + cell = MergedCell(self.ws, row=row, column=col) + self.ws._cells[(cell.row, cell.column)] = cell + cell.border += border + + protected = self.start_cell.protection is not None + if protected: + protection = copy.copy(self.start_cell.protection) + for coord in self.cells: + cell = self.ws._cells.get(coord) + if cell is None: + row, col = coord + cell = MergedCell(self.ws, row=row, column=col) + self.ws._cells[(cell.row, cell.column)] = cell + + if protected: + cell.protection = protection + + + def __contains__(self, coord): + return coord in CellRange(self.coord) + + + def __copy__(self): + return self.__class__(self.ws, self.coord) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/ole.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/ole.py new file mode 100644 index 00000000..61dc0048 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/ole.py @@ -0,0 +1,133 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + String, + Set, + Bool, + Sequence, +) + +from openpyxl.drawing.spreadsheet_drawing import AnchorMarker +from openpyxl.xml.constants import SHEET_DRAWING_NS + + +class ObjectAnchor(Serialisable): + + tagname = "anchor" + + _from = Typed(expected_type=AnchorMarker, namespace=SHEET_DRAWING_NS) + to = Typed(expected_type=AnchorMarker, namespace=SHEET_DRAWING_NS) + moveWithCells = Bool(allow_none=True) + sizeWithCells = Bool(allow_none=True) + z_order = Integer(allow_none=True, hyphenated=True) + + + def __init__(self, + _from=None, + to=None, + moveWithCells=False, + sizeWithCells=False, + z_order=None, + ): + self._from = _from + self.to = to + self.moveWithCells = moveWithCells + self.sizeWithCells = sizeWithCells + self.z_order = z_order + + +class ObjectPr(Serialisable): + + tagname = "objectPr" + + anchor = Typed(expected_type=ObjectAnchor, ) + locked = Bool(allow_none=True) + defaultSize = Bool(allow_none=True) + _print = Bool(allow_none=True) + disabled = Bool(allow_none=True) + uiObject = Bool(allow_none=True) + autoFill = Bool(allow_none=True) + autoLine = Bool(allow_none=True) + autoPict = Bool(allow_none=True) + macro = String() + altText = String(allow_none=True) + dde = Bool(allow_none=True) + + __elements__ = ('anchor',) + + def __init__(self, + anchor=None, + locked=True, + defaultSize=True, + _print=True, + disabled=False, + uiObject=False, + autoFill=True, + autoLine=True, + autoPict=True, + macro=None, + altText=None, + dde=False, + ): + self.anchor = anchor + self.locked = locked + self.defaultSize = defaultSize + self._print = _print + self.disabled = disabled + self.uiObject = uiObject + self.autoFill = autoFill + self.autoLine = autoLine + self.autoPict = autoPict + self.macro = macro + self.altText = altText + self.dde = dde + + +class OleObject(Serialisable): + + tagname = "oleObject" + + objectPr = Typed(expected_type=ObjectPr, allow_none=True) + progId = String(allow_none=True) + dvAspect = Set(values=(['DVASPECT_CONTENT', 'DVASPECT_ICON'])) + link = String(allow_none=True) + oleUpdate = Set(values=(['OLEUPDATE_ALWAYS', 'OLEUPDATE_ONCALL'])) + autoLoad = Bool(allow_none=True) + shapeId = Integer() + + __elements__ = ('objectPr',) + + def __init__(self, + objectPr=None, + progId=None, + dvAspect='DVASPECT_CONTENT', + link=None, + oleUpdate=None, + autoLoad=False, + shapeId=None, + ): + self.objectPr = objectPr + self.progId = progId + self.dvAspect = dvAspect + self.link = link + self.oleUpdate = oleUpdate + self.autoLoad = autoLoad + self.shapeId = shapeId + + +class OleObjects(Serialisable): + + tagname = "oleObjects" + + oleObject = Sequence(expected_type=OleObject) + + __elements__ = ('oleObject',) + + def __init__(self, + oleObject=(), + ): + self.oleObject = oleObject + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/page.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/page.py new file mode 100644 index 00000000..7d630c2c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/page.py @@ -0,0 +1,174 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Float, + Bool, + Integer, + NoneSet, + ) +from openpyxl.descriptors.excel import UniversalMeasure, Relation + + +class PrintPageSetup(Serialisable): + """ Worksheet print page setup """ + + tagname = "pageSetup" + + orientation = NoneSet(values=("default", "portrait", "landscape")) + paperSize = Integer(allow_none=True) + scale = Integer(allow_none=True) + fitToHeight = Integer(allow_none=True) + fitToWidth = Integer(allow_none=True) + firstPageNumber = Integer(allow_none=True) + useFirstPageNumber = Bool(allow_none=True) + paperHeight = UniversalMeasure(allow_none=True) + paperWidth = UniversalMeasure(allow_none=True) + pageOrder = NoneSet(values=("downThenOver", "overThenDown")) + usePrinterDefaults = Bool(allow_none=True) + blackAndWhite = Bool(allow_none=True) + draft = Bool(allow_none=True) + cellComments = NoneSet(values=("asDisplayed", "atEnd")) + errors = NoneSet(values=("displayed", "blank", "dash", "NA")) + horizontalDpi = Integer(allow_none=True) + verticalDpi = Integer(allow_none=True) + copies = Integer(allow_none=True) + id = Relation() + + + def __init__(self, + worksheet=None, + orientation=None, + paperSize=None, + scale=None, + fitToHeight=None, + fitToWidth=None, + firstPageNumber=None, + useFirstPageNumber=None, + paperHeight=None, + paperWidth=None, + pageOrder=None, + usePrinterDefaults=None, + blackAndWhite=None, + draft=None, + cellComments=None, + errors=None, + horizontalDpi=None, + verticalDpi=None, + copies=None, + id=None): + self._parent = worksheet + self.orientation = orientation + self.paperSize = paperSize + self.scale = scale + self.fitToHeight = fitToHeight + self.fitToWidth = fitToWidth + self.firstPageNumber = firstPageNumber + self.useFirstPageNumber = useFirstPageNumber + self.paperHeight = paperHeight + self.paperWidth = paperWidth + self.pageOrder = pageOrder + self.usePrinterDefaults = usePrinterDefaults + self.blackAndWhite = blackAndWhite + self.draft = draft + self.cellComments = cellComments + self.errors = errors + self.horizontalDpi = horizontalDpi + self.verticalDpi = verticalDpi + self.copies = copies + self.id = id + + + def __bool__(self): + return bool(dict(self)) + + + + + @property + def sheet_properties(self): + """ + Proxy property + """ + return self._parent.sheet_properties.pageSetUpPr + + + @property + def fitToPage(self): + return self.sheet_properties.fitToPage + + + @fitToPage.setter + def fitToPage(self, value): + self.sheet_properties.fitToPage = value + + + @property + def autoPageBreaks(self): + return self.sheet_properties.autoPageBreaks + + + @autoPageBreaks.setter + def autoPageBreaks(self, value): + self.sheet_properties.autoPageBreaks = value + + + @classmethod + def from_tree(cls, node): + self = super().from_tree(node) + self.id = None # strip link to binary settings + return self + + +class PrintOptions(Serialisable): + """ Worksheet print options """ + + tagname = "printOptions" + horizontalCentered = Bool(allow_none=True) + verticalCentered = Bool(allow_none=True) + headings = Bool(allow_none=True) + gridLines = Bool(allow_none=True) + gridLinesSet = Bool(allow_none=True) + + def __init__(self, horizontalCentered=None, + verticalCentered=None, + headings=None, + gridLines=None, + gridLinesSet=None, + ): + self.horizontalCentered = horizontalCentered + self.verticalCentered = verticalCentered + self.headings = headings + self.gridLines = gridLines + self.gridLinesSet = gridLinesSet + + + def __bool__(self): + return bool(dict(self)) + + +class PageMargins(Serialisable): + """ + Information about page margins for view/print layouts. + Standard values (in inches) + left, right = 0.75 + top, bottom = 1 + header, footer = 0.5 + """ + tagname = "pageMargins" + + left = Float() + right = Float() + top = Float() + bottom = Float() + header = Float() + footer = Float() + + def __init__(self, left=0.75, right=0.75, top=1, bottom=1, header=0.5, + footer=0.5): + self.left = left + self.right = right + self.top = top + self.bottom = bottom + self.header = header + self.footer = footer diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/pagebreak.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/pagebreak.py new file mode 100644 index 00000000..ad50a321 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/pagebreak.py @@ -0,0 +1,94 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Integer, + Bool, + Sequence, +) + + +class Break(Serialisable): + + tagname = "brk" + + id = Integer(allow_none=True) + min = Integer(allow_none=True) + max = Integer(allow_none=True) + man = Bool(allow_none=True) + pt = Bool(allow_none=True) + + def __init__(self, + id=0, + min=0, + max=16383, + man=True, + pt=None, + ): + self.id = id + self.min = min + self.max = max + self.man = man + self.pt = pt + + +class RowBreak(Serialisable): + + tagname = "rowBreaks" + + count = Integer(allow_none=True) + manualBreakCount = Integer(allow_none=True) + brk = Sequence(expected_type=Break, allow_none=True) + + __elements__ = ('brk',) + __attrs__ = ("count", "manualBreakCount",) + + def __init__(self, + count=None, + manualBreakCount=None, + brk=(), + ): + self.brk = brk + + + def __bool__(self): + return len(self.brk) > 0 + + + def __len__(self): + return len(self.brk) + + + @property + def count(self): + return len(self) + + + @property + def manualBreakCount(self): + return len(self) + + + def append(self, brk=None): + """ + Add a page break + """ + vals = list(self.brk) + if not isinstance(brk, Break): + brk = Break(id=self.count+1) + vals.append(brk) + self.brk = vals + + +PageBreak = RowBreak + + +class ColBreak(RowBreak): + + tagname = "colBreaks" + + count = RowBreak.count + manualBreakCount = RowBreak.manualBreakCount + brk = RowBreak.brk + + __attrs__ = RowBreak.__attrs__ diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/picture.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/picture.py new file mode 100644 index 00000000..8fff338a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/picture.py @@ -0,0 +1,8 @@ +#Autogenerated schema +from openpyxl.descriptors.serialisable import Serialisable + +# same as related + +class SheetBackgroundPicture(Serialisable): + + tagname = "sheetBackgroundPicture" diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/print_settings.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/print_settings.py new file mode 100644 index 00000000..b4629df1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/print_settings.py @@ -0,0 +1,184 @@ +# Copyright (c) 2010-2024 openpyxl + +import re +from openpyxl.descriptors import ( + Strict, + Integer, + String, + Typed, +) +from openpyxl.utils import quote_sheetname, absolute_coordinate +from openpyxl.utils.cell import SHEET_TITLE, SHEETRANGE_RE, RANGE_EXPR + +from .cell_range import MultiCellRange + +COL_RANGE = r"""(?P<cols>[$]?(?P<min_col>[a-zA-Z]{1,3}):[$]?(?P<max_col>[a-zA-Z]{1,3}))""" +COL_RANGE_RE = re.compile(COL_RANGE) +ROW_RANGE = r"""(?P<rows>[$]?(?P<min_row>\d+):[$]?(?P<max_row>\d+))""" +ROW_RANGE_RE = re.compile(ROW_RANGE) +TITLES_REGEX = re.compile("""{0}{1}?,?{2}?,?""".format(SHEET_TITLE, ROW_RANGE, COL_RANGE), + re.VERBOSE) +PRINT_AREA_RE = re.compile(f"({SHEET_TITLE})?(?P<cells>{RANGE_EXPR})", re.VERBOSE) + +class ColRange(Strict): + """ + Represent a range of at least one column + """ + + min_col = String() + max_col = String() + + + def __init__(self, range_string=None, min_col=None, max_col=None): + if range_string is not None: + match = COL_RANGE_RE.match(range_string) + if not match: + raise ValueError(f"{range_string} is not a valid column range") + min_col, max_col = match.groups()[1:] + self.min_col = min_col + self.max_col = max_col + + + def __eq__(self, other): + if isinstance(other, self.__class__): + return (self.min_col == other.min_col + and + self.max_col == other.max_col) + elif isinstance(other, str): + return (str(self) == other + or + f"{self.min_col}:{self.max_col}") + return False + + + def __repr__(self): + return f"Range of columns from '{self.min_col}' to '{self.max_col}'" + + + def __str__(self): + return f"${self.min_col}:${self.max_col}" + + +class RowRange(Strict): + """ + Represent a range of at least one row + """ + + min_row = Integer() + max_row = Integer() + + def __init__(self, range_string=None, min_row=None, max_row=None): + if range_string is not None: + match = ROW_RANGE_RE.match(range_string) + if not match: + raise ValueError(f"{range_string} is not a valid row range") + min_row, max_row = match.groups()[1:] + self.min_row = min_row + self.max_row = max_row + + + def __eq__(self, other): + if isinstance(other, self.__class__): + return (self.min_row == other.min_row + and + self.max_row == other.max_row) + elif isinstance(other, str): + return (str(self) == other + or + f"{self.min_row}:{self.max_row}") + return False + + def __repr__(self): + return f"Range of rows from '{self.min_row}' to '{self.max_row}'" + + + def __str__(self): + return f"${self.min_row}:${self.max_row}" + + +class PrintTitles(Strict): + """ + Contains at least either a range of rows or columns + """ + + cols = Typed(expected_type=ColRange, allow_none=True) + rows = Typed(expected_type=RowRange, allow_none=True) + title = String() + + + def __init__(self, cols=None, rows=None, title=""): + self.cols = cols + self.rows = rows + self.title = title + + + @classmethod + def from_string(cls, value): + kw = dict((k, v) for match in TITLES_REGEX.finditer(value) + for k, v in match.groupdict().items() if v) + + if not kw: + raise ValueError(f"{value} is not a valid print titles definition") + + cols = rows = None + + if "cols" in kw: + cols = ColRange(kw["cols"]) + if "rows" in kw: + rows = RowRange(kw["rows"]) + + title = kw.get("quoted") or kw.get("notquoted") + + return cls(cols=cols, rows=rows, title=title) + + + def __eq__(self, other): + if isinstance(other, self.__class__): + return (self.cols == other.cols + and + self.rows == other.rows + and + self.title == other.title) + elif isinstance(other, str): + return str(self) == other + return False + + def __repr__(self): + return f"Print titles for sheet {self.title} cols {self.rows}, rows {self.cols}" + + + def __str__(self): + title = quote_sheetname(self.title) + titles = ",".join([f"{title}!{value}" for value in (self.rows, self.cols) if value]) + return titles or "" + + +class PrintArea(MultiCellRange): + + + @classmethod + def from_string(cls, value): + new = [] + for m in PRINT_AREA_RE.finditer(value): # can be multiple + coord = m.group("cells") + if coord: + new.append(coord) + return cls(new) + + + def __init__(self, ranges=(), title=""): + self.title = "" + super().__init__(ranges) + + + def __str__(self): + if self.ranges: + return ",".join([f"{quote_sheetname(self.title)}!{absolute_coordinate(str(range))}" + for range in self.sorted()]) + return "" + + + def __eq__(self, other): + super().__eq__(other) + if isinstance(other, str): + return str(self) == other diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/properties.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/properties.py new file mode 100644 index 00000000..e16d15be --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/properties.py @@ -0,0 +1,97 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Worksheet Properties""" + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import String, Bool, Typed +from openpyxl.styles.colors import ColorDescriptor + + +class Outline(Serialisable): + + tagname = "outlinePr" + + applyStyles = Bool(allow_none=True) + summaryBelow = Bool(allow_none=True) + summaryRight = Bool(allow_none=True) + showOutlineSymbols = Bool(allow_none=True) + + + def __init__(self, + applyStyles=None, + summaryBelow=None, + summaryRight=None, + showOutlineSymbols=None + ): + self.applyStyles = applyStyles + self.summaryBelow = summaryBelow + self.summaryRight = summaryRight + self.showOutlineSymbols = showOutlineSymbols + + +class PageSetupProperties(Serialisable): + + tagname = "pageSetUpPr" + + autoPageBreaks = Bool(allow_none=True) + fitToPage = Bool(allow_none=True) + + def __init__(self, autoPageBreaks=None, fitToPage=None): + self.autoPageBreaks = autoPageBreaks + self.fitToPage = fitToPage + + +class WorksheetProperties(Serialisable): + + tagname = "sheetPr" + + codeName = String(allow_none=True) + enableFormatConditionsCalculation = Bool(allow_none=True) + filterMode = Bool(allow_none=True) + published = Bool(allow_none=True) + syncHorizontal = Bool(allow_none=True) + syncRef = String(allow_none=True) + syncVertical = Bool(allow_none=True) + transitionEvaluation = Bool(allow_none=True) + transitionEntry = Bool(allow_none=True) + tabColor = ColorDescriptor(allow_none=True) + outlinePr = Typed(expected_type=Outline, allow_none=True) + pageSetUpPr = Typed(expected_type=PageSetupProperties, allow_none=True) + + __elements__ = ('tabColor', 'outlinePr', 'pageSetUpPr') + + + def __init__(self, + codeName=None, + enableFormatConditionsCalculation=None, + filterMode=None, + published=None, + syncHorizontal=None, + syncRef=None, + syncVertical=None, + transitionEvaluation=None, + transitionEntry=None, + tabColor=None, + outlinePr=None, + pageSetUpPr=None + ): + """ Attributes """ + self.codeName = codeName + self.enableFormatConditionsCalculation = enableFormatConditionsCalculation + self.filterMode = filterMode + self.published = published + self.syncHorizontal = syncHorizontal + self.syncRef = syncRef + self.syncVertical = syncVertical + self.transitionEvaluation = transitionEvaluation + self.transitionEntry = transitionEntry + """ Elements """ + self.tabColor = tabColor + if outlinePr is None: + self.outlinePr = Outline(summaryBelow=True, summaryRight=True) + else: + self.outlinePr = outlinePr + + if pageSetUpPr is None: + pageSetUpPr = PageSetupProperties() + self.pageSetUpPr = pageSetUpPr diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/protection.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/protection.py new file mode 100644 index 00000000..7f931840 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/protection.py @@ -0,0 +1,120 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Bool, + String, + Alias, + Integer, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.excel import ( + Base64Binary, +) +from openpyxl.utils.protection import hash_password + + +class _Protected: + _password = None + + def set_password(self, value='', already_hashed=False): + """Set a password on this sheet.""" + if not already_hashed: + value = hash_password(value) + self._password = value + + @property + def password(self): + """Return the password value, regardless of hash.""" + return self._password + + @password.setter + def password(self, value): + """Set a password directly, forcing a hash step.""" + self.set_password(value) + + +class SheetProtection(Serialisable, _Protected): + """ + Information about protection of various aspects of a sheet. True values + mean that protection for the object or action is active This is the + **default** when protection is active, ie. users cannot do something + """ + + tagname = "sheetProtection" + + sheet = Bool() + enabled = Alias('sheet') + objects = Bool() + scenarios = Bool() + formatCells = Bool() + formatColumns = Bool() + formatRows = Bool() + insertColumns = Bool() + insertRows = Bool() + insertHyperlinks = Bool() + deleteColumns = Bool() + deleteRows = Bool() + selectLockedCells = Bool() + selectUnlockedCells = Bool() + sort = Bool() + autoFilter = Bool() + pivotTables = Bool() + saltValue = Base64Binary(allow_none=True) + spinCount = Integer(allow_none=True) + algorithmName = String(allow_none=True) + hashValue = Base64Binary(allow_none=True) + + + __attrs__ = ('selectLockedCells', 'selectUnlockedCells', 'algorithmName', + 'sheet', 'objects', 'insertRows', 'insertHyperlinks', 'autoFilter', + 'scenarios', 'formatColumns', 'deleteColumns', 'insertColumns', + 'pivotTables', 'deleteRows', 'formatCells', 'saltValue', 'formatRows', + 'sort', 'spinCount', 'password', 'hashValue') + + + def __init__(self, sheet=False, objects=False, scenarios=False, + formatCells=True, formatRows=True, formatColumns=True, + insertColumns=True, insertRows=True, insertHyperlinks=True, + deleteColumns=True, deleteRows=True, selectLockedCells=False, + selectUnlockedCells=False, sort=True, autoFilter=True, pivotTables=True, + password=None, algorithmName=None, saltValue=None, spinCount=None, hashValue=None): + self.sheet = sheet + self.objects = objects + self.scenarios = scenarios + self.formatCells = formatCells + self.formatColumns = formatColumns + self.formatRows = formatRows + self.insertColumns = insertColumns + self.insertRows = insertRows + self.insertHyperlinks = insertHyperlinks + self.deleteColumns = deleteColumns + self.deleteRows = deleteRows + self.selectLockedCells = selectLockedCells + self.selectUnlockedCells = selectUnlockedCells + self.sort = sort + self.autoFilter = autoFilter + self.pivotTables = pivotTables + if password is not None: + self.password = password + self.algorithmName = algorithmName + self.saltValue = saltValue + self.spinCount = spinCount + self.hashValue = hashValue + + + def set_password(self, value='', already_hashed=False): + super().set_password(value, already_hashed) + self.enable() + + + def enable(self): + self.sheet = True + + + def disable(self): + self.sheet = False + + + def __bool__(self): + return self.sheet + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/related.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/related.py new file mode 100644 index 00000000..2bf05019 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/related.py @@ -0,0 +1,17 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.excel import Relation + + +class Related(Serialisable): + + id = Relation() + + + def __init__(self, id=None): + self.id = id + + + def to_tree(self, tagname, idx=None): + return super().to_tree(tagname) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/scenario.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/scenario.py new file mode 100644 index 00000000..3c86f607 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/scenario.py @@ -0,0 +1,105 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + String, + Integer, + Bool, + Sequence, + Convertible, +) +from .cell_range import MultiCellRange + + +class InputCells(Serialisable): + + tagname = "inputCells" + + r = String() + deleted = Bool(allow_none=True) + undone = Bool(allow_none=True) + val = String() + numFmtId = Integer(allow_none=True) + + def __init__(self, + r=None, + deleted=False, + undone=False, + val=None, + numFmtId=None, + ): + self.r = r + self.deleted = deleted + self.undone = undone + self.val = val + self.numFmtId = numFmtId + + +class Scenario(Serialisable): + + tagname = "scenario" + + inputCells = Sequence(expected_type=InputCells) + name = String() + locked = Bool(allow_none=True) + hidden = Bool(allow_none=True) + user = String(allow_none=True) + comment = String(allow_none=True) + + __elements__ = ('inputCells',) + __attrs__ = ('name', 'locked', 'hidden', 'user', 'comment', 'count') + + def __init__(self, + inputCells=(), + name=None, + locked=False, + hidden=False, + count=None, + user=None, + comment=None, + ): + self.inputCells = inputCells + self.name = name + self.locked = locked + self.hidden = hidden + self.user = user + self.comment = comment + + + @property + def count(self): + return len(self.inputCells) + + +class ScenarioList(Serialisable): + + tagname = "scenarios" + + scenario = Sequence(expected_type=Scenario) + current = Integer(allow_none=True) + show = Integer(allow_none=True) + sqref = Convertible(expected_type=MultiCellRange, allow_none=True) + + __elements__ = ('scenario',) + + def __init__(self, + scenario=(), + current=None, + show=None, + sqref=None, + ): + self.scenario = scenario + self.current = current + self.show = show + self.sqref = sqref + + + def append(self, scenario): + s = self.scenario + s.append(scenario) + self.scenario = s + + + def __bool__(self): + return bool(self.scenario) + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/smart_tag.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/smart_tag.py new file mode 100644 index 00000000..29fe1926 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/smart_tag.py @@ -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 + diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/table.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/table.py new file mode 100644 index 00000000..756345f9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/table.py @@ -0,0 +1,385 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Descriptor, + Alias, + Typed, + Bool, + Integer, + NoneSet, + String, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList, CellRange +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.xml.constants import SHEET_MAIN_NS, REL_NS +from openpyxl.xml.functions import tostring +from openpyxl.utils import range_boundaries +from openpyxl.utils.escape import escape, unescape + +from .related import Related + +from .filters import ( + AutoFilter, + SortState, +) + +TABLESTYLES = tuple( + ["TableStyleMedium{0}".format(i) for i in range(1, 29)] + + ["TableStyleLight{0}".format(i) for i in range(1, 22)] + + ["TableStyleDark{0}".format(i) for i in range(1, 12)] +) + +PIVOTSTYLES = tuple( + ["PivotStyleMedium{0}".format(i) for i in range(1, 29)] + + ["PivotStyleLight{0}".format(i) for i in range(1, 29)] + + ["PivotStyleDark{0}".format(i) for i in range(1, 29)] +) + + +class TableStyleInfo(Serialisable): + + tagname = "tableStyleInfo" + + name = String(allow_none=True) + showFirstColumn = Bool(allow_none=True) + showLastColumn = Bool(allow_none=True) + showRowStripes = Bool(allow_none=True) + showColumnStripes = Bool(allow_none=True) + + def __init__(self, + name=None, + showFirstColumn=None, + showLastColumn=None, + showRowStripes=None, + showColumnStripes=None, + ): + self.name = name + self.showFirstColumn = showFirstColumn + self.showLastColumn = showLastColumn + self.showRowStripes = showRowStripes + self.showColumnStripes = showColumnStripes + + +class XMLColumnProps(Serialisable): + + tagname = "xmlColumnPr" + + mapId = Integer() + xpath = String() + denormalized = Bool(allow_none=True) + xmlDataType = String() + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + mapId=None, + xpath=None, + denormalized=None, + xmlDataType=None, + extLst=None, + ): + self.mapId = mapId + self.xpath = xpath + self.denormalized = denormalized + self.xmlDataType = xmlDataType + + +class TableFormula(Serialisable): + + tagname = "tableFormula" + + ## Note formula is stored as the text value + + array = Bool(allow_none=True) + attr_text = Descriptor() + text = Alias('attr_text') + + + def __init__(self, + array=None, + attr_text=None, + ): + self.array = array + self.attr_text = attr_text + + +class TableColumn(Serialisable): + + tagname = "tableColumn" + + id = Integer() + uniqueName = String(allow_none=True) + name = String() + totalsRowFunction = NoneSet(values=(['sum', 'min', 'max', 'average', + 'count', 'countNums', 'stdDev', 'var', 'custom'])) + totalsRowLabel = String(allow_none=True) + queryTableFieldId = Integer(allow_none=True) + headerRowDxfId = Integer(allow_none=True) + dataDxfId = Integer(allow_none=True) + totalsRowDxfId = Integer(allow_none=True) + headerRowCellStyle = String(allow_none=True) + dataCellStyle = String(allow_none=True) + totalsRowCellStyle = String(allow_none=True) + calculatedColumnFormula = Typed(expected_type=TableFormula, allow_none=True) + totalsRowFormula = Typed(expected_type=TableFormula, allow_none=True) + xmlColumnPr = Typed(expected_type=XMLColumnProps, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('calculatedColumnFormula', 'totalsRowFormula', + 'xmlColumnPr', 'extLst') + + def __init__(self, + id=None, + uniqueName=None, + name=None, + totalsRowFunction=None, + totalsRowLabel=None, + queryTableFieldId=None, + headerRowDxfId=None, + dataDxfId=None, + totalsRowDxfId=None, + headerRowCellStyle=None, + dataCellStyle=None, + totalsRowCellStyle=None, + calculatedColumnFormula=None, + totalsRowFormula=None, + xmlColumnPr=None, + extLst=None, + ): + self.id = id + self.uniqueName = uniqueName + self.name = name + self.totalsRowFunction = totalsRowFunction + self.totalsRowLabel = totalsRowLabel + self.queryTableFieldId = queryTableFieldId + self.headerRowDxfId = headerRowDxfId + self.dataDxfId = dataDxfId + self.totalsRowDxfId = totalsRowDxfId + self.headerRowCellStyle = headerRowCellStyle + self.dataCellStyle = dataCellStyle + self.totalsRowCellStyle = totalsRowCellStyle + self.calculatedColumnFormula = calculatedColumnFormula + self.totalsRowFormula = totalsRowFormula + self.xmlColumnPr = xmlColumnPr + self.extLst = extLst + + + def __iter__(self): + for k, v in super().__iter__(): + if k == 'name': + v = escape(v) + yield k, v + + + @classmethod + def from_tree(cls, node): + self = super().from_tree(node) + self.name = unescape(self.name) + return self + + +class TableNameDescriptor(String): + + """ + Table names cannot have spaces in them + """ + + def __set__(self, instance, value): + if value is not None and " " in value: + raise ValueError("Table names cannot have spaces") + super().__set__(instance, value) + + +class Table(Serialisable): + + _path = "/tables/table{0}.xml" + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml" + _rel_type = REL_NS + "/table" + _rel_id = None + + tagname = "table" + + id = Integer() + name = String(allow_none=True) + displayName = TableNameDescriptor() + comment = String(allow_none=True) + ref = CellRange() + tableType = NoneSet(values=(['worksheet', 'xml', 'queryTable'])) + headerRowCount = Integer(allow_none=True) + insertRow = Bool(allow_none=True) + insertRowShift = Bool(allow_none=True) + totalsRowCount = Integer(allow_none=True) + totalsRowShown = Bool(allow_none=True) + published = Bool(allow_none=True) + headerRowDxfId = Integer(allow_none=True) + dataDxfId = Integer(allow_none=True) + totalsRowDxfId = Integer(allow_none=True) + headerRowBorderDxfId = Integer(allow_none=True) + tableBorderDxfId = Integer(allow_none=True) + totalsRowBorderDxfId = Integer(allow_none=True) + headerRowCellStyle = String(allow_none=True) + dataCellStyle = String(allow_none=True) + totalsRowCellStyle = String(allow_none=True) + connectionId = Integer(allow_none=True) + autoFilter = Typed(expected_type=AutoFilter, allow_none=True) + sortState = Typed(expected_type=SortState, allow_none=True) + tableColumns = NestedSequence(expected_type=TableColumn, count=True) + tableStyleInfo = Typed(expected_type=TableStyleInfo, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('autoFilter', 'sortState', 'tableColumns', + 'tableStyleInfo') + + def __init__(self, + id=1, + displayName=None, + ref=None, + name=None, + comment=None, + tableType=None, + headerRowCount=1, + insertRow=None, + insertRowShift=None, + totalsRowCount=None, + totalsRowShown=None, + published=None, + headerRowDxfId=None, + dataDxfId=None, + totalsRowDxfId=None, + headerRowBorderDxfId=None, + tableBorderDxfId=None, + totalsRowBorderDxfId=None, + headerRowCellStyle=None, + dataCellStyle=None, + totalsRowCellStyle=None, + connectionId=None, + autoFilter=None, + sortState=None, + tableColumns=(), + tableStyleInfo=None, + extLst=None, + ): + self.id = id + self.displayName = displayName + if name is None: + name = displayName + self.name = name + self.comment = comment + self.ref = ref + self.tableType = tableType + self.headerRowCount = headerRowCount + self.insertRow = insertRow + self.insertRowShift = insertRowShift + self.totalsRowCount = totalsRowCount + self.totalsRowShown = totalsRowShown + self.published = published + self.headerRowDxfId = headerRowDxfId + self.dataDxfId = dataDxfId + self.totalsRowDxfId = totalsRowDxfId + self.headerRowBorderDxfId = headerRowBorderDxfId + self.tableBorderDxfId = tableBorderDxfId + self.totalsRowBorderDxfId = totalsRowBorderDxfId + self.headerRowCellStyle = headerRowCellStyle + self.dataCellStyle = dataCellStyle + self.totalsRowCellStyle = totalsRowCellStyle + self.connectionId = connectionId + self.autoFilter = autoFilter + self.sortState = sortState + self.tableColumns = tableColumns + self.tableStyleInfo = tableStyleInfo + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def path(self): + """ + Return path within the archive + """ + return "/xl" + self._path.format(self.id) + + + def _write(self, archive): + """ + Serialise to XML and write to archive + """ + xml = self.to_tree() + archive.writestr(self.path[1:], tostring(xml)) + + + def _initialise_columns(self): + """ + Create a list of table columns from a cell range + Always set a ref if we have headers (the default) + Column headings must be strings and must match cells in the worksheet. + """ + + min_col, min_row, max_col, max_row = range_boundaries(self.ref) + for idx in range(min_col, max_col+1): + col = TableColumn(id=idx, name="Column{0}".format(idx)) + self.tableColumns.append(col) + if self.headerRowCount and not self.autoFilter: + self.autoFilter = AutoFilter(ref=self.ref) + + + @property + def column_names(self): + return [column.name for column in self.tableColumns] + + +class TablePartList(Serialisable): + + tagname = "tableParts" + + count = Integer(allow_none=True) + tablePart = Sequence(expected_type=Related) + + __elements__ = ('tablePart',) + __attrs__ = ('count',) + + def __init__(self, + count=None, + tablePart=(), + ): + self.tablePart = tablePart + + + def append(self, part): + self.tablePart.append(part) + + + @property + def count(self): + return len(self.tablePart) + + + def __bool__(self): + return bool(self.tablePart) + + +class TableList(dict): + + + def add(self, table): + if not isinstance(table, Table): + raise TypeError("You can only add tables") + self[table.name] = table + + + def get(self, name=None, table_range=None): + if name is not None: + return super().get(name) + for table in self.values(): + if table_range == table.ref: + return table + + + def items(self): + return [(name, table.ref) for name, table in super().items()] diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/views.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/views.py new file mode 100644 index 00000000..27046b0d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/views.py @@ -0,0 +1,155 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Bool, + Integer, + String, + Set, + Float, + Typed, + NoneSet, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.serialisable import Serialisable + + +class Pane(Serialisable): + xSplit = Float(allow_none=True) + ySplit = Float(allow_none=True) + topLeftCell = String(allow_none=True) + activePane = Set(values=("bottomRight", "topRight", "bottomLeft", "topLeft")) + state = Set(values=("split", "frozen", "frozenSplit")) + + def __init__(self, + xSplit=None, + ySplit=None, + topLeftCell=None, + activePane="topLeft", + state="split"): + self.xSplit = xSplit + self.ySplit = ySplit + self.topLeftCell = topLeftCell + self.activePane = activePane + self.state = state + + +class Selection(Serialisable): + pane = NoneSet(values=("bottomRight", "topRight", "bottomLeft", "topLeft")) + activeCell = String(allow_none=True) + activeCellId = Integer(allow_none=True) + sqref = String(allow_none=True) + + def __init__(self, + pane=None, + activeCell="A1", + activeCellId=None, + sqref="A1"): + self.pane = pane + self.activeCell = activeCell + self.activeCellId = activeCellId + self.sqref = sqref + + +class SheetView(Serialisable): + + """Information about the visible portions of this sheet.""" + + tagname = "sheetView" + + windowProtection = Bool(allow_none=True) + showFormulas = Bool(allow_none=True) + showGridLines = Bool(allow_none=True) + showRowColHeaders = Bool(allow_none=True) + showZeros = Bool(allow_none=True) + rightToLeft = Bool(allow_none=True) + tabSelected = Bool(allow_none=True) + showRuler = Bool(allow_none=True) + showOutlineSymbols = Bool(allow_none=True) + defaultGridColor = Bool(allow_none=True) + showWhiteSpace = Bool(allow_none=True) + view = NoneSet(values=("normal", "pageBreakPreview", "pageLayout")) + topLeftCell = String(allow_none=True) + colorId = Integer(allow_none=True) + zoomScale = Integer(allow_none=True) + zoomScaleNormal = Integer(allow_none=True) + zoomScaleSheetLayoutView = Integer(allow_none=True) + zoomScalePageLayoutView = Integer(allow_none=True) + zoomToFit = Bool(allow_none=True) # Chart sheets only + workbookViewId = Integer() + selection = Sequence(expected_type=Selection) + pane = Typed(expected_type=Pane, allow_none=True) + + def __init__(self, + windowProtection=None, + showFormulas=None, + showGridLines=None, + showRowColHeaders=None, + showZeros=None, + rightToLeft=None, + tabSelected=None, + showRuler=None, + showOutlineSymbols=None, + defaultGridColor=None, + showWhiteSpace=None, + view=None, + topLeftCell=None, + colorId=None, + zoomScale=None, + zoomScaleNormal=None, + zoomScaleSheetLayoutView=None, + zoomScalePageLayoutView=None, + zoomToFit=None, + workbookViewId=0, + selection=None, + pane=None,): + self.windowProtection = windowProtection + self.showFormulas = showFormulas + self.showGridLines = showGridLines + self.showRowColHeaders = showRowColHeaders + self.showZeros = showZeros + self.rightToLeft = rightToLeft + self.tabSelected = tabSelected + self.showRuler = showRuler + self.showOutlineSymbols = showOutlineSymbols + self.defaultGridColor = defaultGridColor + self.showWhiteSpace = showWhiteSpace + self.view = view + self.topLeftCell = topLeftCell + self.colorId = colorId + self.zoomScale = zoomScale + self.zoomScaleNormal = zoomScaleNormal + self.zoomScaleSheetLayoutView = zoomScaleSheetLayoutView + self.zoomScalePageLayoutView = zoomScalePageLayoutView + self.zoomToFit = zoomToFit + self.workbookViewId = workbookViewId + self.pane = pane + if selection is None: + selection = (Selection(), ) + self.selection = selection + + +class SheetViewList(Serialisable): + + tagname = "sheetViews" + + sheetView = Sequence(expected_type=SheetView, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('sheetView',) + + def __init__(self, + sheetView=None, + extLst=None, + ): + if sheetView is None: + sheetView = [SheetView()] + self.sheetView = sheetView + + + @property + def active(self): + """ + Returns the first sheet view which is assumed to be active + """ + return self.sheetView[0] diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py new file mode 100644 index 00000000..b7ffbebc --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py @@ -0,0 +1,907 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Worksheet is the 2nd-level container in Excel.""" + + +# Python stdlib imports +from itertools import chain +from operator import itemgetter +from inspect import isgenerator +from warnings import warn + +# compatibility imports +from openpyxl.compat import ( + deprecated, +) + +# package imports +from openpyxl.utils import ( + column_index_from_string, + get_column_letter, + range_boundaries, + coordinate_to_tuple, +) +from openpyxl.cell import Cell, MergedCell +from openpyxl.formatting.formatting import ConditionalFormattingList +from openpyxl.packaging.relationship import RelationshipList +from openpyxl.workbook.child import _WorkbookChild +from openpyxl.workbook.defined_name import ( + DefinedNameDict, +) + +from openpyxl.formula.translate import Translator + +from .datavalidation import DataValidationList +from .page import ( + PrintPageSetup, + PageMargins, + PrintOptions, +) +from .dimensions import ( + ColumnDimension, + RowDimension, + DimensionHolder, + SheetFormatProperties, +) +from .protection import SheetProtection +from .filters import AutoFilter +from .views import ( + Pane, + Selection, + SheetViewList, +) +from .cell_range import MultiCellRange, CellRange +from .merge import MergedCellRange +from .properties import WorksheetProperties +from .pagebreak import RowBreak, ColBreak +from .scenario import ScenarioList +from .table import TableList +from .formula import ArrayFormula +from .print_settings import ( + PrintTitles, + ColRange, + RowRange, + PrintArea, +) + + +class Worksheet(_WorkbookChild): + """Represents a worksheet. + + Do not create worksheets yourself, + use :func:`openpyxl.workbook.Workbook.create_sheet` instead + + """ + + _rel_type = "worksheet" + _path = "/xl/worksheets/sheet{0}.xml" + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" + + BREAK_NONE = 0 + BREAK_ROW = 1 + BREAK_COLUMN = 2 + + SHEETSTATE_VISIBLE = 'visible' + SHEETSTATE_HIDDEN = 'hidden' + SHEETSTATE_VERYHIDDEN = 'veryHidden' + + # Paper size + PAPERSIZE_LETTER = '1' + PAPERSIZE_LETTER_SMALL = '2' + PAPERSIZE_TABLOID = '3' + PAPERSIZE_LEDGER = '4' + PAPERSIZE_LEGAL = '5' + PAPERSIZE_STATEMENT = '6' + PAPERSIZE_EXECUTIVE = '7' + PAPERSIZE_A3 = '8' + PAPERSIZE_A4 = '9' + PAPERSIZE_A4_SMALL = '10' + PAPERSIZE_A5 = '11' + + # Page orientation + ORIENTATION_PORTRAIT = 'portrait' + ORIENTATION_LANDSCAPE = 'landscape' + + def __init__(self, parent, title=None): + _WorkbookChild.__init__(self, parent, title) + self._setup() + + def _setup(self): + self.row_dimensions = DimensionHolder(worksheet=self, + default_factory=self._add_row) + self.column_dimensions = DimensionHolder(worksheet=self, + default_factory=self._add_column) + self.row_breaks = RowBreak() + self.col_breaks = ColBreak() + self._cells = {} + self._charts = [] + self._images = [] + self._rels = RelationshipList() + self._drawing = None + self._comments = [] + self.merged_cells = MultiCellRange() + self._tables = TableList() + self._pivots = [] + self.data_validations = DataValidationList() + self._hyperlinks = [] + self.sheet_state = 'visible' + self.page_setup = PrintPageSetup(worksheet=self) + self.print_options = PrintOptions() + self._print_rows = None + self._print_cols = None + self._print_area = PrintArea() + self.page_margins = PageMargins() + self.views = SheetViewList() + self.protection = SheetProtection() + self.defined_names = DefinedNameDict() + + self._current_row = 0 + self.auto_filter = AutoFilter() + self.conditional_formatting = ConditionalFormattingList() + self.legacy_drawing = None + self.sheet_properties = WorksheetProperties() + self.sheet_format = SheetFormatProperties() + self.scenarios = ScenarioList() + + + @property + def sheet_view(self): + return self.views.active + + + @property + def selected_cell(self): + return self.sheet_view.selection[0].sqref + + + @property + def active_cell(self): + return self.sheet_view.selection[0].activeCell + + + @property + def array_formulae(self): + """Returns a dictionary of cells with array formulae and the cells in array""" + result = {} + for c in self._cells.values(): + if c.data_type == "f": + if isinstance(c.value, ArrayFormula): + result[c.coordinate] = c.value.ref + return result + + + @property + def show_gridlines(self): + return self.sheet_view.showGridLines + + + @property + def freeze_panes(self): + if self.sheet_view.pane is not None: + return self.sheet_view.pane.topLeftCell + + + @freeze_panes.setter + def freeze_panes(self, topLeftCell=None): + if isinstance(topLeftCell, Cell): + topLeftCell = topLeftCell.coordinate + if topLeftCell == 'A1': + topLeftCell = None + + if not topLeftCell: + self.sheet_view.pane = None + return + + row, column = coordinate_to_tuple(topLeftCell) + + view = self.sheet_view + view.pane = Pane(topLeftCell=topLeftCell, + activePane="topRight", + state="frozen") + view.selection[0].pane = "topRight" + + if column > 1: + view.pane.xSplit = column - 1 + if row > 1: + view.pane.ySplit = row - 1 + view.pane.activePane = 'bottomLeft' + view.selection[0].pane = "bottomLeft" + if column > 1: + view.selection[0].pane = "bottomRight" + view.pane.activePane = 'bottomRight' + + if row > 1 and column > 1: + sel = list(view.selection) + sel.insert(0, Selection(pane="topRight", activeCell=None, sqref=None)) + sel.insert(1, Selection(pane="bottomLeft", activeCell=None, sqref=None)) + view.selection = sel + + + def cell(self, row, column, value=None): + """ + Returns a cell object based on the given coordinates. + + Usage: cell(row=15, column=1, value=5) + + Calling `cell` creates cells in memory when they + are first accessed. + + :param row: row index of the cell (e.g. 4) + :type row: int + + :param column: column index of the cell (e.g. 3) + :type column: int + + :param value: value of the cell (e.g. 5) + :type value: numeric or time or string or bool or none + + :rtype: openpyxl.cell.cell.Cell + """ + + if row < 1 or column < 1: + raise ValueError("Row or column values must be at least 1") + + cell = self._get_cell(row, column) + if value is not None: + cell.value = value + + return cell + + + def _get_cell(self, row, column): + """ + Internal method for getting a cell from a worksheet. + Will create a new cell if one doesn't already exist. + """ + if not 0 < row < 1048577: + raise ValueError(f"Row numbers must be between 1 and 1048576. Row number supplied was {row}") + coordinate = (row, column) + if not coordinate in self._cells: + cell = Cell(self, row=row, column=column) + self._add_cell(cell) + return self._cells[coordinate] + + + def _add_cell(self, cell): + """ + Internal method for adding cell objects. + """ + column = cell.col_idx + row = cell.row + self._current_row = max(row, self._current_row) + self._cells[(row, column)] = cell + + + def __getitem__(self, key): + """Convenience access by Excel style coordinates + + The key can be a single cell coordinate 'A1', a range of cells 'A1:D25', + individual rows or columns 'A', 4 or ranges of rows or columns 'A:D', + 4:10. + + Single cells will always be created if they do not exist. + + Returns either a single cell or a tuple of rows or columns. + """ + if isinstance(key, slice): + if not all([key.start, key.stop]): + raise IndexError("{0} is not a valid coordinate or range".format(key)) + key = "{0}:{1}".format(key.start, key.stop) + + if isinstance(key, int): + key = str(key + ) + min_col, min_row, max_col, max_row = range_boundaries(key) + + if not any([min_col, min_row, max_col, max_row]): + raise IndexError("{0} is not a valid coordinate or range".format(key)) + + if min_row is None: + cols = tuple(self.iter_cols(min_col, max_col)) + if min_col == max_col: + cols = cols[0] + return cols + if min_col is None: + rows = tuple(self.iter_rows(min_col=min_col, min_row=min_row, + max_col=self.max_column, max_row=max_row)) + if min_row == max_row: + rows = rows[0] + return rows + if ":" not in key: + return self._get_cell(min_row, min_col) + return tuple(self.iter_rows(min_row=min_row, min_col=min_col, + max_row=max_row, max_col=max_col)) + + + def __setitem__(self, key, value): + self[key].value = value + + + def __iter__(self): + return self.iter_rows() + + + def __delitem__(self, key): + row, column = coordinate_to_tuple(key) + if (row, column) in self._cells: + del self._cells[(row, column)] + + + @property + def min_row(self): + """The minimum row index containing data (1-based) + + :type: int + """ + min_row = 1 + if self._cells: + min_row = min(self._cells)[0] + return min_row + + + @property + def max_row(self): + """The maximum row index containing data (1-based) + + :type: int + """ + max_row = 1 + if self._cells: + max_row = max(self._cells)[0] + return max_row + + + @property + def min_column(self): + """The minimum column index containing data (1-based) + + :type: int + """ + min_col = 1 + if self._cells: + min_col = min(c[1] for c in self._cells) + return min_col + + + @property + def max_column(self): + """The maximum column index containing data (1-based) + + :type: int + """ + max_col = 1 + if self._cells: + max_col = max(c[1] for c in self._cells) + return max_col + + + def calculate_dimension(self): + """Return the minimum bounding range for all cells containing data (ex. 'A1:M24') + + :rtype: string + """ + if self._cells: + rows = set() + cols = set() + for row, col in self._cells: + rows.add(row) + cols.add(col) + max_row = max(rows) + max_col = max(cols) + min_col = min(cols) + min_row = min(rows) + else: + return "A1:A1" + + return f"{get_column_letter(min_col)}{min_row}:{get_column_letter(max_col)}{max_row}" + + + @property + def dimensions(self): + """Returns the result of :func:`calculate_dimension`""" + return self.calculate_dimension() + + + def iter_rows(self, min_row=None, max_row=None, min_col=None, max_col=None, values_only=False): + """ + Produces cells from the worksheet, by row. Specify the iteration range + using indices of rows and columns. + + If no indices are specified the range starts at A1. + + If no cells are in the worksheet an empty tuple will be returned. + + :param min_col: smallest column index (1-based index) + :type min_col: int + + :param min_row: smallest row index (1-based index) + :type min_row: int + + :param max_col: largest column index (1-based index) + :type max_col: int + + :param max_row: largest row index (1-based index) + :type max_row: int + + :param values_only: whether only cell values should be returned + :type values_only: bool + + :rtype: generator + """ + + if self._current_row == 0 and not any([min_col, min_row, max_col, max_row ]): + return iter(()) + + + min_col = min_col or 1 + min_row = min_row or 1 + max_col = max_col or self.max_column + max_row = max_row or self.max_row + + return self._cells_by_row(min_col, min_row, max_col, max_row, values_only) + + + def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False): + for row in range(min_row, max_row + 1): + cells = (self.cell(row=row, column=column) for column in range(min_col, max_col + 1)) + if values_only: + yield tuple(cell.value for cell in cells) + else: + yield tuple(cells) + + + @property + def rows(self): + """Produces all cells in the worksheet, by row (see :func:`iter_rows`) + + :type: generator + """ + return self.iter_rows() + + + @property + def values(self): + """Produces all cell values in the worksheet, by row + + :type: generator + """ + for row in self.iter_rows(values_only=True): + yield row + + + def iter_cols(self, min_col=None, max_col=None, min_row=None, max_row=None, values_only=False): + """ + Produces cells from the worksheet, by column. Specify the iteration range + using indices of rows and columns. + + If no indices are specified the range starts at A1. + + If no cells are in the worksheet an empty tuple will be returned. + + :param min_col: smallest column index (1-based index) + :type min_col: int + + :param min_row: smallest row index (1-based index) + :type min_row: int + + :param max_col: largest column index (1-based index) + :type max_col: int + + :param max_row: largest row index (1-based index) + :type max_row: int + + :param values_only: whether only cell values should be returned + :type values_only: bool + + :rtype: generator + """ + + if self._current_row == 0 and not any([min_col, min_row, max_col, max_row]): + return iter(()) + + min_col = min_col or 1 + min_row = min_row or 1 + max_col = max_col or self.max_column + max_row = max_row or self.max_row + + return self._cells_by_col(min_col, min_row, max_col, max_row, values_only) + + + def _cells_by_col(self, min_col, min_row, max_col, max_row, values_only=False): + """ + Get cells by column + """ + for column in range(min_col, max_col+1): + cells = (self.cell(row=row, column=column) + for row in range(min_row, max_row+1)) + if values_only: + yield tuple(cell.value for cell in cells) + else: + yield tuple(cells) + + + @property + def columns(self): + """Produces all cells in the worksheet, by column (see :func:`iter_cols`)""" + return self.iter_cols() + + + @property + def column_groups(self): + """ + Return a list of column ranges where more than one column + """ + return [cd.range for cd in self.column_dimensions.values() if cd.min and cd.max > cd.min] + + + def set_printer_settings(self, paper_size, orientation): + """Set printer settings """ + + self.page_setup.paperSize = paper_size + self.page_setup.orientation = orientation + + + def add_data_validation(self, data_validation): + """ Add a data-validation object to the sheet. The data-validation + object defines the type of data-validation to be applied and the + cell or range of cells it should apply to. + """ + self.data_validations.append(data_validation) + + + def add_chart(self, chart, anchor=None): + """ + Add a chart to the sheet + Optionally provide a cell for the top-left anchor + """ + if anchor is not None: + chart.anchor = anchor + self._charts.append(chart) + + + def add_image(self, img, anchor=None): + """ + Add an image to the sheet. + Optionally provide a cell for the top-left anchor + """ + if anchor is not None: + img.anchor = anchor + self._images.append(img) + + + def add_table(self, table): + """ + Check for duplicate name in definedNames and other worksheet tables + before adding table. + """ + + if self.parent._duplicate_name(table.name): + raise ValueError("Table with name {0} already exists".format(table.name)) + if not hasattr(self, "_get_cell"): + warn("In write-only mode you must add table columns manually") + self._tables.add(table) + + + @property + def tables(self): + return self._tables + + + def add_pivot(self, pivot): + self._pivots.append(pivot) + + + def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None): + """ Set merge on a cell range. Range is a cell range (e.g. A1:E1) """ + if range_string is None: + cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row, + max_col=end_column, max_row=end_row) + range_string = cr.coord + mcr = MergedCellRange(self, range_string) + self.merged_cells.add(mcr) + self._clean_merge_range(mcr) + + + def _clean_merge_range(self, mcr): + """ + Remove all but the top left-cell from a range of merged cells + and recreate the lost border information. + Borders are then applied + """ + cells = mcr.cells + next(cells) # skip first cell + for row, col in cells: + self._cells[row, col] = MergedCell(self, row, col) + mcr.format() + + + @property + @deprecated("Use ws.merged_cells.ranges") + def merged_cell_ranges(self): + """Return a copy of cell ranges""" + return self.merged_cells.ranges[:] + + + def unmerge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None): + """ Remove merge on a cell range. Range is a cell range (e.g. A1:E1) """ + cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row, + max_col=end_column, max_row=end_row) + + if cr.coord not in self.merged_cells: + raise ValueError("Cell range {0} is not merged".format(cr.coord)) + + self.merged_cells.remove(cr) + + cells = cr.cells + next(cells) # skip first cell + for row, col in cells: + del self._cells[(row, col)] + + + def append(self, iterable): + """Appends a group of values at the bottom of the current sheet. + + * If it's a list: all values are added in order, starting from the first column + * If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters) + + :param iterable: list, range or generator, or dict containing values to append + :type iterable: list|tuple|range|generator or dict + + Usage: + + * append(['This is A1', 'This is B1', 'This is C1']) + * **or** append({'A' : 'This is A1', 'C' : 'This is C1'}) + * **or** append({1 : 'This is A1', 3 : 'This is C1'}) + + :raise: TypeError when iterable is neither a list/tuple nor a dict + + """ + row_idx = self._current_row + 1 + + if (isinstance(iterable, (list, tuple, range)) + or isgenerator(iterable)): + for col_idx, content in enumerate(iterable, 1): + if isinstance(content, Cell): + # compatible with write-only mode + cell = content + if cell.parent and cell.parent != self: + raise ValueError("Cells cannot be copied from other worksheets") + cell.parent = self + cell.column = col_idx + cell.row = row_idx + else: + cell = Cell(self, row=row_idx, column=col_idx, value=content) + self._cells[(row_idx, col_idx)] = cell + + elif isinstance(iterable, dict): + for col_idx, content in iterable.items(): + if isinstance(col_idx, str): + col_idx = column_index_from_string(col_idx) + cell = Cell(self, row=row_idx, column=col_idx, value=content) + self._cells[(row_idx, col_idx)] = cell + + else: + self._invalid_row(iterable) + + self._current_row = row_idx + + + def _move_cells(self, min_row=None, min_col=None, offset=0, row_or_col="row"): + """ + Move either rows or columns around by the offset + """ + reverse = offset > 0 # start at the end if inserting + row_offset = 0 + col_offset = 0 + + # need to make affected ranges contiguous + if row_or_col == 'row': + cells = self.iter_rows(min_row=min_row) + row_offset = offset + key = 0 + else: + cells = self.iter_cols(min_col=min_col) + col_offset = offset + key = 1 + cells = list(cells) + + for row, column in sorted(self._cells, key=itemgetter(key), reverse=reverse): + if min_row and row < min_row: + continue + elif min_col and column < min_col: + continue + + self._move_cell(row, column, row_offset, col_offset) + + + def insert_rows(self, idx, amount=1): + """ + Insert row or rows before row==idx + """ + self._move_cells(min_row=idx, offset=amount, row_or_col="row") + self._current_row = self.max_row + + + def insert_cols(self, idx, amount=1): + """ + Insert column or columns before col==idx + """ + self._move_cells(min_col=idx, offset=amount, row_or_col="column") + + + def delete_rows(self, idx, amount=1): + """ + Delete row or rows from row==idx + """ + + remainder = _gutter(idx, amount, self.max_row) + + self._move_cells(min_row=idx+amount, offset=-amount, row_or_col="row") + + # calculating min and max col is an expensive operation, do it only once + min_col = self.min_column + max_col = self.max_column + 1 + for row in remainder: + for col in range(min_col, max_col): + if (row, col) in self._cells: + del self._cells[row, col] + self._current_row = self.max_row + if not self._cells: + self._current_row = 0 + + + def delete_cols(self, idx, amount=1): + """ + Delete column or columns from col==idx + """ + + remainder = _gutter(idx, amount, self.max_column) + + self._move_cells(min_col=idx+amount, offset=-amount, row_or_col="column") + + # calculating min and max row is an expensive operation, do it only once + min_row = self.min_row + max_row = self.max_row + 1 + for col in remainder: + for row in range(min_row, max_row): + if (row, col) in self._cells: + del self._cells[row, col] + + + def move_range(self, cell_range, rows=0, cols=0, translate=False): + """ + Move a cell range by the number of rows and/or columns: + down if rows > 0 and up if rows < 0 + right if cols > 0 and left if cols < 0 + Existing cells will be overwritten. + Formulae and references will not be updated. + """ + if isinstance(cell_range, str): + cell_range = CellRange(cell_range) + if not isinstance(cell_range, CellRange): + raise ValueError("Only CellRange objects can be moved") + if not rows and not cols: + return + + down = rows > 0 + right = cols > 0 + + if rows: + cells = sorted(cell_range.rows, reverse=down) + else: + cells = sorted(cell_range.cols, reverse=right) + + for row, col in chain.from_iterable(cells): + self._move_cell(row, col, rows, cols, translate) + + # rebase moved range + cell_range.shift(row_shift=rows, col_shift=cols) + + + def _move_cell(self, row, column, row_offset, col_offset, translate=False): + """ + Move a cell from one place to another. + Delete at old index + Rebase coordinate + """ + cell = self._get_cell(row, column) + new_row = cell.row + row_offset + new_col = cell.column + col_offset + self._cells[new_row, new_col] = cell + del self._cells[(cell.row, cell.column)] + cell.row = new_row + cell.column = new_col + if translate and cell.data_type == "f": + t = Translator(cell.value, cell.coordinate) + cell.value = t.translate_formula(row_delta=row_offset, col_delta=col_offset) + + + def _invalid_row(self, iterable): + raise TypeError('Value must be a list, tuple, range or generator, or a dict. Supplied value is {0}'.format( + type(iterable)) + ) + + + def _add_column(self): + """Dimension factory for column information""" + + return ColumnDimension(self) + + def _add_row(self): + """Dimension factory for row information""" + + return RowDimension(self) + + + @property + def print_title_rows(self): + """Rows to be printed at the top of every page (ex: '1:3')""" + if self._print_rows: + return str(self._print_rows) + + + @print_title_rows.setter + def print_title_rows(self, rows): + """ + Set rows to be printed on the top of every page + format `1:3` + """ + if rows is not None: + self._print_rows = RowRange(rows) + + + @property + def print_title_cols(self): + """Columns to be printed at the left side of every page (ex: 'A:C')""" + if self._print_cols: + return str(self._print_cols) + + + @print_title_cols.setter + def print_title_cols(self, cols): + """ + Set cols to be printed on the left of every page + format ``A:C` + """ + if cols is not None: + self._print_cols = ColRange(cols) + + + @property + def print_titles(self): + titles = PrintTitles(cols=self._print_cols, rows=self._print_rows, title=self.title) + return str(titles) + + + @property + def print_area(self): + """ + The print area for the worksheet, or None if not set. To set, supply a range + like 'A1:D4' or a list of ranges. + """ + self._print_area.title = self.title + return str(self._print_area) + + + @print_area.setter + def print_area(self, value): + """ + Range of cells in the form A1:D4 or list of ranges. Print area can be cleared + by passing `None` or an empty list + """ + if not value: + self._print_area = PrintArea() + elif isinstance(value, str): + self._print_area = PrintArea.from_string(value) + elif hasattr(value, "__iter__"): + self._print_area = PrintArea.from_string(",".join(value)) + + +def _gutter(idx, offset, max_val): + """ + When deleting rows and columns are deleted we rely on overwriting. + This may not be the case for a large offset on small set of cells: + range(cells_to_delete) > range(cell_to_be_moved) + """ + gutter = range(max(max_val+1-offset, idx), min(idx+offset, max_val)+1) + return gutter diff --git a/.venv/lib/python3.12/site-packages/openpyxl/writer/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/writer/__init__.py new file mode 100644 index 00000000..ab6cdead --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/writer/__init__.py @@ -0,0 +1 @@ +# Copyright (c) 2010-2024 openpyxl diff --git a/.venv/lib/python3.12/site-packages/openpyxl/writer/excel.py b/.venv/lib/python3.12/site-packages/openpyxl/writer/excel.py new file mode 100644 index 00000000..c1154fd2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/writer/excel.py @@ -0,0 +1,295 @@ +# Copyright (c) 2010-2024 openpyxl + + +# Python stdlib imports +import datetime +import re +from zipfile import ZipFile, ZIP_DEFLATED + +# package imports +from openpyxl.utils.exceptions import InvalidFileException +from openpyxl.xml.constants import ( + ARC_ROOT_RELS, + ARC_WORKBOOK_RELS, + ARC_APP, + ARC_CORE, + ARC_CUSTOM, + CPROPS_TYPE, + ARC_THEME, + ARC_STYLE, + ARC_WORKBOOK, + ) +from openpyxl.drawing.spreadsheet_drawing import SpreadsheetDrawing +from openpyxl.xml.functions import tostring, fromstring +from openpyxl.packaging.manifest import Manifest +from openpyxl.packaging.relationship import ( + get_rels_path, + RelationshipList, + Relationship, +) +from openpyxl.comments.comment_sheet import CommentSheet +from openpyxl.styles.stylesheet import write_stylesheet +from openpyxl.worksheet._writer import WorksheetWriter +from openpyxl.workbook._writer import WorkbookWriter +from .theme import theme_xml + + +class ExcelWriter: + """Write a workbook object to an Excel file.""" + + def __init__(self, workbook, archive): + self._archive = archive + self.workbook = workbook + self.manifest = Manifest() + self.vba_modified = set() + self._tables = [] + self._charts = [] + self._images = [] + self._drawings = [] + self._comments = [] + self._pivots = [] + + + def write_data(self): + from openpyxl.packaging.extended import ExtendedProperties + """Write the various xml files into the zip archive.""" + # cleanup all worksheets + archive = self._archive + + props = ExtendedProperties() + archive.writestr(ARC_APP, tostring(props.to_tree())) + + archive.writestr(ARC_CORE, tostring(self.workbook.properties.to_tree())) + if self.workbook.loaded_theme: + archive.writestr(ARC_THEME, self.workbook.loaded_theme) + else: + archive.writestr(ARC_THEME, theme_xml) + + if len(self.workbook.custom_doc_props) >= 1: + archive.writestr(ARC_CUSTOM, tostring(self.workbook.custom_doc_props.to_tree())) + class CustomOverride(): + path = "/" + ARC_CUSTOM #PartName + mime_type = CPROPS_TYPE #ContentType + + custom_override = CustomOverride() + self.manifest.append(custom_override) + + self._write_worksheets() + self._write_chartsheets() + self._write_images() + self._write_charts() + + self._write_external_links() + + stylesheet = write_stylesheet(self.workbook) + archive.writestr(ARC_STYLE, tostring(stylesheet)) + + writer = WorkbookWriter(self.workbook) + archive.writestr(ARC_ROOT_RELS, writer.write_root_rels()) + archive.writestr(ARC_WORKBOOK, writer.write()) + archive.writestr(ARC_WORKBOOK_RELS, writer.write_rels()) + + self._merge_vba() + + self.manifest._write(archive, self.workbook) + + def _merge_vba(self): + """ + If workbook contains macros then extract associated files from cache + of old file and add to archive + """ + ARC_VBA = re.compile("|".join( + ('xl/vba', r'xl/drawings/.*vmlDrawing\d\.vml', + 'xl/ctrlProps', 'customUI', 'xl/activeX', r'xl/media/.*\.emf') + ) + ) + + if self.workbook.vba_archive: + for name in set(self.workbook.vba_archive.namelist()) - self.vba_modified: + if ARC_VBA.match(name): + self._archive.writestr(name, self.workbook.vba_archive.read(name)) + + + def _write_images(self): + # delegate to object + for img in self._images: + self._archive.writestr(img.path[1:], img._data()) + + + def _write_charts(self): + # delegate to object + if len(self._charts) != len(set(self._charts)): + raise InvalidFileException("The same chart cannot be used in more than one worksheet") + for chart in self._charts: + self._archive.writestr(chart.path[1:], tostring(chart._write())) + self.manifest.append(chart) + + + def _write_drawing(self, drawing): + """ + Write a drawing + """ + self._drawings.append(drawing) + drawing._id = len(self._drawings) + for chart in drawing.charts: + self._charts.append(chart) + chart._id = len(self._charts) + for img in drawing.images: + self._images.append(img) + img._id = len(self._images) + rels_path = get_rels_path(drawing.path)[1:] + self._archive.writestr(drawing.path[1:], tostring(drawing._write())) + self._archive.writestr(rels_path, tostring(drawing._write_rels())) + self.manifest.append(drawing) + + + def _write_chartsheets(self): + for idx, sheet in enumerate(self.workbook.chartsheets, 1): + + sheet._id = idx + xml = tostring(sheet.to_tree()) + + self._archive.writestr(sheet.path[1:], xml) + self.manifest.append(sheet) + + if sheet._drawing: + self._write_drawing(sheet._drawing) + + rel = Relationship(type="drawing", Target=sheet._drawing.path) + rels = RelationshipList() + rels.append(rel) + tree = rels.to_tree() + + rels_path = get_rels_path(sheet.path[1:]) + self._archive.writestr(rels_path, tostring(tree)) + + + def _write_comment(self, ws): + + cs = CommentSheet.from_comments(ws._comments) + self._comments.append(cs) + cs._id = len(self._comments) + self._archive.writestr(cs.path[1:], tostring(cs.to_tree())) + self.manifest.append(cs) + + if ws.legacy_drawing is None or self.workbook.vba_archive is None: + ws.legacy_drawing = 'xl/drawings/commentsDrawing{0}.vml'.format(cs._id) + vml = None + else: + vml = fromstring(self.workbook.vba_archive.read(ws.legacy_drawing)) + + vml = cs.write_shapes(vml) + + self._archive.writestr(ws.legacy_drawing, vml) + self.vba_modified.add(ws.legacy_drawing) + + comment_rel = Relationship(Id="comments", type=cs._rel_type, Target=cs.path) + ws._rels.append(comment_rel) + + + def write_worksheet(self, ws): + ws._drawing = SpreadsheetDrawing() + ws._drawing.charts = ws._charts + ws._drawing.images = ws._images + if self.workbook.write_only: + if not ws.closed: + ws.close() + writer = ws._writer + else: + writer = WorksheetWriter(ws) + writer.write() + + ws._rels = writer._rels + self._archive.write(writer.out, ws.path[1:]) + self.manifest.append(ws) + writer.cleanup() + + + def _write_worksheets(self): + + pivot_caches = set() + + for idx, ws in enumerate(self.workbook.worksheets, 1): + + ws._id = idx + self.write_worksheet(ws) + + if ws._drawing: + self._write_drawing(ws._drawing) + + for r in ws._rels: + if "drawing" in r.Type: + r.Target = ws._drawing.path + + if ws._comments: + self._write_comment(ws) + + if ws.legacy_drawing is not None: + shape_rel = Relationship(type="vmlDrawing", Id="anysvml", + Target="/" + ws.legacy_drawing) + ws._rels.append(shape_rel) + + for t in ws._tables.values(): + self._tables.append(t) + t.id = len(self._tables) + t._write(self._archive) + self.manifest.append(t) + ws._rels.get(t._rel_id).Target = t.path + + for p in ws._pivots: + if p.cache not in pivot_caches: + pivot_caches.add(p.cache) + p.cache._id = len(pivot_caches) + + self._pivots.append(p) + p._id = len(self._pivots) + p._write(self._archive, self.manifest) + self.workbook._pivots.append(p) + r = Relationship(Type=p.rel_type, Target=p.path) + ws._rels.append(r) + + if ws._rels: + tree = ws._rels.to_tree() + rels_path = get_rels_path(ws.path)[1:] + self._archive.writestr(rels_path, tostring(tree)) + + + def _write_external_links(self): + # delegate to object + """Write links to external workbooks""" + wb = self.workbook + for idx, link in enumerate(wb._external_links, 1): + link._id = idx + rels_path = get_rels_path(link.path[1:]) + + xml = link.to_tree() + self._archive.writestr(link.path[1:], tostring(xml)) + rels = RelationshipList() + rels.append(link.file_link) + self._archive.writestr(rels_path, tostring(rels.to_tree())) + self.manifest.append(link) + + + def save(self): + """Write data into the archive.""" + self.write_data() + self._archive.close() + + +def save_workbook(workbook, filename): + """Save the given workbook on the filesystem under the name filename. + + :param workbook: the workbook to save + :type workbook: :class:`openpyxl.workbook.Workbook` + + :param filename: the path to which save the workbook + :type filename: string + + :rtype: bool + + """ + archive = ZipFile(filename, 'w', ZIP_DEFLATED, allowZip64=True) + workbook.properties.modified = datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None) + writer = ExcelWriter(workbook, archive) + writer.save() + return True diff --git a/.venv/lib/python3.12/site-packages/openpyxl/writer/theme.py b/.venv/lib/python3.12/site-packages/openpyxl/writer/theme.py new file mode 100644 index 00000000..20c1d607 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/writer/theme.py @@ -0,0 +1,291 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Write the theme xml based on a fixed string.""" + + +theme_xml = """<?xml version="1.0"?> +<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"> + <a:themeElements> + <a:clrScheme name="Office"> + <a:dk1> + <a:sysClr val="windowText" lastClr="000000"/> + </a:dk1> + <a:lt1> + <a:sysClr val="window" lastClr="FFFFFF"/> + </a:lt1> + <a:dk2> + <a:srgbClr val="1F497D"/> + </a:dk2> + <a:lt2> + <a:srgbClr val="EEECE1"/> + </a:lt2> + <a:accent1> + <a:srgbClr val="4F81BD"/> + </a:accent1> + <a:accent2> + <a:srgbClr val="C0504D"/> + </a:accent2> + <a:accent3> + <a:srgbClr val="9BBB59"/> + </a:accent3> + <a:accent4> + <a:srgbClr val="8064A2"/> + </a:accent4> + <a:accent5> + <a:srgbClr val="4BACC6"/> + </a:accent5> + <a:accent6> + <a:srgbClr val="F79646"/> + </a:accent6> + <a:hlink> + <a:srgbClr val="0000FF"/> + </a:hlink> + <a:folHlink> + <a:srgbClr val="800080"/> + </a:folHlink> + </a:clrScheme> + <a:fontScheme name="Office"> + <a:majorFont> + <a:latin typeface="Cambria"/> + <a:ea typeface=""/> + <a:cs typeface=""/> + <a:font script="Jpan" typeface="MS Pゴシック"/> + <a:font script="Hang" typeface="맑은 고딕"/> + <a:font script="Hans" typeface="宋体"/> + <a:font script="Hant" typeface="新細明體"/> + <a:font script="Arab" typeface="Times New Roman"/> + <a:font script="Hebr" typeface="Times New Roman"/> + <a:font script="Thai" typeface="Tahoma"/> + <a:font script="Ethi" typeface="Nyala"/> + <a:font script="Beng" typeface="Vrinda"/> + <a:font script="Gujr" typeface="Shruti"/> + <a:font script="Khmr" typeface="MoolBoran"/> + <a:font script="Knda" typeface="Tunga"/> + <a:font script="Guru" typeface="Raavi"/> + <a:font script="Cans" typeface="Euphemia"/> + <a:font script="Cher" typeface="Plantagenet Cherokee"/> + <a:font script="Yiii" typeface="Microsoft Yi Baiti"/> + <a:font script="Tibt" typeface="Microsoft Himalaya"/> + <a:font script="Thaa" typeface="MV Boli"/> + <a:font script="Deva" typeface="Mangal"/> + <a:font script="Telu" typeface="Gautami"/> + <a:font script="Taml" typeface="Latha"/> + <a:font script="Syrc" typeface="Estrangelo Edessa"/> + <a:font script="Orya" typeface="Kalinga"/> + <a:font script="Mlym" typeface="Kartika"/> + <a:font script="Laoo" typeface="DokChampa"/> + <a:font script="Sinh" typeface="Iskoola Pota"/> + <a:font script="Mong" typeface="Mongolian Baiti"/> + <a:font script="Viet" typeface="Times New Roman"/> + <a:font script="Uigh" typeface="Microsoft Uighur"/> + </a:majorFont> + <a:minorFont> + <a:latin typeface="Calibri"/> + <a:ea typeface=""/> + <a:cs typeface=""/> + <a:font script="Jpan" typeface="MS Pゴシック"/> + <a:font script="Hang" typeface="맑은 고딕"/> + <a:font script="Hans" typeface="宋体"/> + <a:font script="Hant" typeface="新細明體"/> + <a:font script="Arab" typeface="Arial"/> + <a:font script="Hebr" typeface="Arial"/> + <a:font script="Thai" typeface="Tahoma"/> + <a:font script="Ethi" typeface="Nyala"/> + <a:font script="Beng" typeface="Vrinda"/> + <a:font script="Gujr" typeface="Shruti"/> + <a:font script="Khmr" typeface="DaunPenh"/> + <a:font script="Knda" typeface="Tunga"/> + <a:font script="Guru" typeface="Raavi"/> + <a:font script="Cans" typeface="Euphemia"/> + <a:font script="Cher" typeface="Plantagenet Cherokee"/> + <a:font script="Yiii" typeface="Microsoft Yi Baiti"/> + <a:font script="Tibt" typeface="Microsoft Himalaya"/> + <a:font script="Thaa" typeface="MV Boli"/> + <a:font script="Deva" typeface="Mangal"/> + <a:font script="Telu" typeface="Gautami"/> + <a:font script="Taml" typeface="Latha"/> + <a:font script="Syrc" typeface="Estrangelo Edessa"/> + <a:font script="Orya" typeface="Kalinga"/> + <a:font script="Mlym" typeface="Kartika"/> + <a:font script="Laoo" typeface="DokChampa"/> + <a:font script="Sinh" typeface="Iskoola Pota"/> + <a:font script="Mong" typeface="Mongolian Baiti"/> + <a:font script="Viet" typeface="Arial"/> + <a:font script="Uigh" typeface="Microsoft Uighur"/> + </a:minorFont> + </a:fontScheme> + <a:fmtScheme name="Office"> + <a:fillStyleLst> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:gradFill rotWithShape="1"> + <a:gsLst> + <a:gs pos="0"> + <a:schemeClr val="phClr"> + <a:tint val="50000"/> + <a:satMod val="300000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="35000"> + <a:schemeClr val="phClr"> + <a:tint val="37000"/> + <a:satMod val="300000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="100000"> + <a:schemeClr val="phClr"> + <a:tint val="15000"/> + <a:satMod val="350000"/> + </a:schemeClr> + </a:gs> + </a:gsLst> + <a:lin ang="16200000" scaled="1"/> + </a:gradFill> + <a:gradFill rotWithShape="1"> + <a:gsLst> + <a:gs pos="0"> + <a:schemeClr val="phClr"> + <a:shade val="51000"/> + <a:satMod val="130000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="80000"> + <a:schemeClr val="phClr"> + <a:shade val="93000"/> + <a:satMod val="130000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="100000"> + <a:schemeClr val="phClr"> + <a:shade val="94000"/> + <a:satMod val="135000"/> + </a:schemeClr> + </a:gs> + </a:gsLst> + <a:lin ang="16200000" scaled="0"/> + </a:gradFill> + </a:fillStyleLst> + <a:lnStyleLst> + <a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"> + <a:solidFill> + <a:schemeClr val="phClr"> + <a:shade val="95000"/> + <a:satMod val="105000"/> + </a:schemeClr> + </a:solidFill> + <a:prstDash val="solid"/> + </a:ln> + <a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:prstDash val="solid"/> + </a:ln> + <a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:prstDash val="solid"/> + </a:ln> + </a:lnStyleLst> + <a:effectStyleLst> + <a:effectStyle> + <a:effectLst> + <a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0"> + <a:srgbClr val="000000"> + <a:alpha val="38000"/> + </a:srgbClr> + </a:outerShdw> + </a:effectLst> + </a:effectStyle> + <a:effectStyle> + <a:effectLst> + <a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"> + <a:srgbClr val="000000"> + <a:alpha val="35000"/> + </a:srgbClr> + </a:outerShdw> + </a:effectLst> + </a:effectStyle> + <a:effectStyle> + <a:effectLst> + <a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"> + <a:srgbClr val="000000"> + <a:alpha val="35000"/> + </a:srgbClr> + </a:outerShdw> + </a:effectLst> + <a:scene3d> + <a:camera prst="orthographicFront"> + <a:rot lat="0" lon="0" rev="0"/> + </a:camera> + <a:lightRig rig="threePt" dir="t"> + <a:rot lat="0" lon="0" rev="1200000"/> + </a:lightRig> + </a:scene3d> + <a:sp3d> + <a:bevelT w="63500" h="25400"/> + </a:sp3d> + </a:effectStyle> + </a:effectStyleLst> + <a:bgFillStyleLst> + <a:solidFill> + <a:schemeClr val="phClr"/> + </a:solidFill> + <a:gradFill rotWithShape="1"> + <a:gsLst> + <a:gs pos="0"> + <a:schemeClr val="phClr"> + <a:tint val="40000"/> + <a:satMod val="350000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="40000"> + <a:schemeClr val="phClr"> + <a:tint val="45000"/> + <a:shade val="99000"/> + <a:satMod val="350000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="100000"> + <a:schemeClr val="phClr"> + <a:shade val="20000"/> + <a:satMod val="255000"/> + </a:schemeClr> + </a:gs> + </a:gsLst> + <a:path path="circle"> + <a:fillToRect l="50000" t="-80000" r="50000" b="180000"/> + </a:path> + </a:gradFill> + <a:gradFill rotWithShape="1"> + <a:gsLst> + <a:gs pos="0"> + <a:schemeClr val="phClr"> + <a:tint val="80000"/> + <a:satMod val="300000"/> + </a:schemeClr> + </a:gs> + <a:gs pos="100000"> + <a:schemeClr val="phClr"> + <a:shade val="30000"/> + <a:satMod val="200000"/> + </a:schemeClr> + </a:gs> + </a:gsLst> + <a:path path="circle"> + <a:fillToRect l="50000" t="50000" r="50000" b="50000"/> + </a:path> + </a:gradFill> + </a:bgFillStyleLst> + </a:fmtScheme> + </a:themeElements> + <a:objectDefaults/> + <a:extraClrSchemeLst/> +</a:theme> +""" + +def write_theme(): + """Write the theme xml.""" + return theme_xml diff --git a/.venv/lib/python3.12/site-packages/openpyxl/xml/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/xml/__init__.py new file mode 100644 index 00000000..db510aa1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/xml/__init__.py @@ -0,0 +1,42 @@ +# Copyright (c) 2010-2024 openpyxl + + +"""Collection of XML resources compatible across different Python versions""" +import os + + +def lxml_available(): + try: + from lxml.etree import LXML_VERSION + LXML = LXML_VERSION >= (3, 3, 1, 0) + if not LXML: + import warnings + warnings.warn("The installed version of lxml is too old to be used with openpyxl") + return False # we have it, but too old + else: + return True # we have it, and recent enough + except ImportError: + return False # we don't even have it + + +def lxml_env_set(): + return os.environ.get("OPENPYXL_LXML", "True") == "True" + + +LXML = lxml_available() and lxml_env_set() + + +def defusedxml_available(): + try: + import defusedxml # noqa + except ImportError: + return False + else: + return True + + +def defusedxml_env_set(): + return os.environ.get("OPENPYXL_DEFUSEDXML", "True") == "True" + + +DEFUSEDXML = defusedxml_available() and defusedxml_env_set() diff --git a/.venv/lib/python3.12/site-packages/openpyxl/xml/constants.py b/.venv/lib/python3.12/site-packages/openpyxl/xml/constants.py new file mode 100644 index 00000000..4e0fd433 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/xml/constants.py @@ -0,0 +1,129 @@ +# Copyright (c) 2010-2024 openpyxl + + +"""Constants for fixed paths in a file and xml namespace urls.""" + +MIN_ROW = 0 +MIN_COLUMN = 0 +MAX_COLUMN = 16384 +MAX_ROW = 1048576 + +# constants +PACKAGE_PROPS = 'docProps' +PACKAGE_XL = 'xl' +PACKAGE_RELS = '_rels' +PACKAGE_THEME = PACKAGE_XL + '/' + 'theme' +PACKAGE_WORKSHEETS = PACKAGE_XL + '/' + 'worksheets' +PACKAGE_CHARTSHEETS = PACKAGE_XL + '/' + 'chartsheets' +PACKAGE_DRAWINGS = PACKAGE_XL + '/' + 'drawings' +PACKAGE_CHARTS = PACKAGE_XL + '/' + 'charts' +PACKAGE_IMAGES = PACKAGE_XL + '/' + 'media' +PACKAGE_WORKSHEET_RELS = PACKAGE_WORKSHEETS + '/' + '_rels' +PACKAGE_CHARTSHEETS_RELS = PACKAGE_CHARTSHEETS + '/' + '_rels' +PACKAGE_PIVOT_TABLE = PACKAGE_XL + '/' + 'pivotTables' +PACKAGE_PIVOT_CACHE = PACKAGE_XL + '/' + 'pivotCache' + +ARC_CONTENT_TYPES = '[Content_Types].xml' +ARC_ROOT_RELS = PACKAGE_RELS + '/.rels' +ARC_WORKBOOK_RELS = PACKAGE_XL + '/' + PACKAGE_RELS + '/workbook.xml.rels' +ARC_CORE = PACKAGE_PROPS + '/core.xml' +ARC_APP = PACKAGE_PROPS + '/app.xml' +ARC_CUSTOM = PACKAGE_PROPS + '/custom.xml' +ARC_WORKBOOK = PACKAGE_XL + '/workbook.xml' +ARC_STYLE = PACKAGE_XL + '/styles.xml' +ARC_THEME = PACKAGE_THEME + '/theme1.xml' +ARC_SHARED_STRINGS = PACKAGE_XL + '/sharedStrings.xml' +ARC_CUSTOM_UI = 'customUI/customUI.xml' + +## namespaces +# XML +XML_NS = "http://www.w3.org/XML/1998/namespace" +# Dublin Core +DCORE_NS = 'http://purl.org/dc/elements/1.1/' +DCTERMS_NS = 'http://purl.org/dc/terms/' +DCTERMS_PREFIX = 'dcterms' + +# Document +DOC_NS = "http://schemas.openxmlformats.org/officeDocument/2006/" +REL_NS = DOC_NS + "relationships" +COMMENTS_NS = REL_NS + "/comments" +IMAGE_NS = REL_NS + "/image" +VML_NS = REL_NS + "/vmlDrawing" +VTYPES_NS = DOC_NS + 'docPropsVTypes' +XPROPS_NS = DOC_NS + 'extended-properties' +CUSTPROPS_NS = DOC_NS + 'custom-properties' +EXTERNAL_LINK_NS = REL_NS + "/externalLink" + +# CustomDocumentProperty FMTID: +CPROPS_FMTID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" + +# Package +PKG_NS = "http://schemas.openxmlformats.org/package/2006/" +PKG_REL_NS = PKG_NS + "relationships" +COREPROPS_NS = PKG_NS + 'metadata/core-properties' +CONTYPES_NS = PKG_NS + 'content-types' + +XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance' +XML_NS = 'http://www.w3.org/XML/1998/namespace' +SHEET_MAIN_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main' + +# Drawing +CHART_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart" +DRAWING_NS = "http://schemas.openxmlformats.org/drawingml/2006/main" +SHEET_DRAWING_NS = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" +CHART_DRAWING_NS = "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing" + +CUSTOMUI_NS = 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility' + + +NAMESPACES = { + 'cp': COREPROPS_NS, + 'dc': DCORE_NS, + DCTERMS_PREFIX: DCTERMS_NS, + 'dcmitype': 'http://purl.org/dc/dcmitype/', + 'xsi': XSI_NS, + 'vt': VTYPES_NS, + 'xml': XML_NS, + 'main': SHEET_MAIN_NS, + 'cust': CUSTPROPS_NS, +} + +## Mime types +WORKBOOK_MACRO = "application/vnd.ms-excel.%s.macroEnabled.main+xml" +WORKBOOK = "application/vnd.openxmlformats-officedocument.spreadsheetml.%s.main+xml" +SPREADSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.%s+xml" +SHARED_STRINGS = SPREADSHEET % "sharedStrings" +EXTERNAL_LINK = SPREADSHEET % "externalLink" +WORKSHEET_TYPE = SPREADSHEET % "worksheet" +COMMENTS_TYPE = SPREADSHEET % "comments" +STYLES_TYPE = SPREADSHEET % "styles" +CHARTSHEET_TYPE = SPREADSHEET % "chartsheet" +DRAWING_TYPE = "application/vnd.openxmlformats-officedocument.drawing+xml" +CHART_TYPE = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" +CHARTSHAPE_TYPE = "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml" +THEME_TYPE = "application/vnd.openxmlformats-officedocument.theme+xml" +CPROPS_TYPE = "application/vnd.openxmlformats-officedocument.custom-properties+xml" +XLTM = WORKBOOK_MACRO % 'template' +XLSM = WORKBOOK_MACRO % 'sheet' +XLTX = WORKBOOK % 'template' +XLSX = WORKBOOK % 'sheet' + + +# Extensions to the specification + +EXT_TYPES = { + '{78C0D931-6437-407D-A8EE-F0AAD7539E65}': 'Conditional Formatting', + '{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}': 'Data Validation', + '{05C60535-1F16-4FD2-B633-F4F36F0B64E0}': 'Sparkline Group', + '{A8765BA9-456A-4DAB-B4F3-ACF838C121DE}': 'Slicer List', + '{FC87AEE6-9EDD-4A0A-B7FB-166176984837}': 'Protected Range', + '{01252117-D84E-4E92-8308-4BE1C098FCBB}': 'Ignored Error', + '{F7C9EE02-42E1-4005-9D12-6889AFFD525C}': 'Web Extension', + '{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}': 'Slicer List', + '{7E03D99C-DC04-49d9-9315-930204A7B6E9}': 'Timeline Ref', +} + +# Objects related to macros that we preserve +CTRL = "application/vnd.ms-excel.controlproperties+xml" +ACTIVEX = "application/vnd.ms-office.activeX+xml" +VBA = "application/vnd.ms-office.vbaProject" diff --git a/.venv/lib/python3.12/site-packages/openpyxl/xml/functions.py b/.venv/lib/python3.12/site-packages/openpyxl/xml/functions.py new file mode 100644 index 00000000..385cca60 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/xml/functions.py @@ -0,0 +1,87 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +XML compatibility functions +""" + +# Python stdlib imports +import re +from functools import partial + +from openpyxl import DEFUSEDXML, LXML + +if LXML is True: + from lxml.etree import ( + Element, + SubElement, + register_namespace, + QName, + xmlfile, + XMLParser, + ) + from lxml.etree import fromstring, tostring + # do not resolve entities + safe_parser = XMLParser(resolve_entities=False) + fromstring = partial(fromstring, parser=safe_parser) + +else: + from xml.etree.ElementTree import ( + Element, + SubElement, + fromstring, + tostring, + QName, + register_namespace + ) + from et_xmlfile import xmlfile + if DEFUSEDXML is True: + from defusedxml.ElementTree import fromstring + +from xml.etree.ElementTree import iterparse +if DEFUSEDXML is True: + from defusedxml.ElementTree import iterparse + +from openpyxl.xml.constants import ( + CHART_NS, + DRAWING_NS, + SHEET_DRAWING_NS, + CHART_DRAWING_NS, + SHEET_MAIN_NS, + REL_NS, + VTYPES_NS, + COREPROPS_NS, + CUSTPROPS_NS, + DCTERMS_NS, + DCTERMS_PREFIX, + XML_NS +) + +register_namespace(DCTERMS_PREFIX, DCTERMS_NS) +register_namespace('dcmitype', 'http://purl.org/dc/dcmitype/') +register_namespace('cp', COREPROPS_NS) +register_namespace('c', CHART_NS) +register_namespace('a', DRAWING_NS) +register_namespace('s', SHEET_MAIN_NS) +register_namespace('r', REL_NS) +register_namespace('vt', VTYPES_NS) +register_namespace('xdr', SHEET_DRAWING_NS) +register_namespace('cdr', CHART_DRAWING_NS) +register_namespace('xml', XML_NS) +register_namespace('cust', CUSTPROPS_NS) + + +tostring = partial(tostring, encoding="utf-8") + +NS_REGEX = re.compile("({(?P<namespace>.*)})?(?P<localname>.*)") + +def localname(node): + if callable(node.tag): + return "comment" + m = NS_REGEX.match(node.tag) + return m.group('localname') + + +def whitespace(node): + stripped = node.text.strip() + if stripped and node.text != stripped: + node.set("{%s}space" % XML_NS, "preserve") |