openpyxl2 3.2.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openpyxl/__init__.py +19 -0
- openpyxl/_constants.py +12 -0
- openpyxl/cell/__init__.py +5 -0
- openpyxl/cell/_writer.py +121 -0
- openpyxl/cell/cell.py +417 -0
- openpyxl/cell/coordinate.py +32 -0
- openpyxl/cell/read_only.py +98 -0
- openpyxl/cell/rich_text.py +188 -0
- openpyxl/cell/text.py +168 -0
- openpyxl/chart/_3d.py +85 -0
- openpyxl/chart/__init__.py +20 -0
- openpyxl/chart/_chart.py +194 -0
- openpyxl/chart/area_chart.py +81 -0
- openpyxl/chart/axis.py +389 -0
- openpyxl/chart/bar_chart.py +144 -0
- openpyxl/chart/bubble_chart.py +61 -0
- openpyxl/chart/chartspace.py +194 -0
- openpyxl/chart/data_source.py +175 -0
- openpyxl/chart/descriptors.py +34 -0
- openpyxl/chart/error_bar.py +69 -0
- openpyxl/chart/label.py +134 -0
- openpyxl/chart/layout.py +68 -0
- openpyxl/chart/legend.py +58 -0
- openpyxl/chart/line_chart.py +129 -0
- openpyxl/chart/marker.py +89 -0
- openpyxl/chart/picture.py +38 -0
- openpyxl/chart/pie_chart.py +136 -0
- openpyxl/chart/pivot.py +52 -0
- openpyxl/chart/plotarea.py +151 -0
- openpyxl/chart/print_settings.py +46 -0
- openpyxl/chart/radar_chart.py +44 -0
- openpyxl/chart/reader.py +28 -0
- openpyxl/chart/reference.py +101 -0
- openpyxl/chart/scatter_chart.py +53 -0
- openpyxl/chart/series.py +239 -0
- openpyxl/chart/series_factory.py +47 -0
- openpyxl/chart/shapes.py +103 -0
- openpyxl/chart/stock_chart.py +46 -0
- openpyxl/chart/surface_chart.py +85 -0
- openpyxl/chart/text.py +57 -0
- openpyxl/chart/title.py +70 -0
- openpyxl/chart/trendline.py +110 -0
- openpyxl/chart/updown_bars.py +20 -0
- openpyxl/chartsheet/__init__.py +2 -0
- openpyxl/chartsheet/chartsheet.py +109 -0
- openpyxl/chartsheet/custom.py +50 -0
- openpyxl/chartsheet/properties.py +19 -0
- openpyxl/chartsheet/protection.py +44 -0
- openpyxl/chartsheet/publish.py +63 -0
- openpyxl/chartsheet/relation.py +95 -0
- openpyxl/chartsheet/views.py +42 -0
- openpyxl/comments/__init__.py +2 -0
- openpyxl/comments/author.py +13 -0
- openpyxl/comments/comment_sheet.py +188 -0
- openpyxl/comments/comments.py +56 -0
- openpyxl/comments/shape_writer.py +111 -0
- openpyxl/compat/__init__.py +49 -0
- openpyxl/compat/abc.py +9 -0
- openpyxl/compat/numbers.py +43 -0
- openpyxl/compat/product.py +16 -0
- openpyxl/compat/singleton.py +38 -0
- openpyxl/compat/strings.py +24 -0
- openpyxl/connection/__init__.py +1 -0
- openpyxl/connection/connections.py +449 -0
- openpyxl/descriptors/__init__.py +55 -0
- openpyxl/descriptors/base.py +249 -0
- openpyxl/descriptors/container.py +34 -0
- openpyxl/descriptors/excel.py +101 -0
- openpyxl/descriptors/namespace.py +11 -0
- openpyxl/descriptors/nested.py +116 -0
- openpyxl/descriptors/sequence.py +127 -0
- openpyxl/descriptors/serialisable.py +208 -0
- openpyxl/descriptors/slots.py +18 -0
- openpyxl/drawing/__init__.py +2 -0
- openpyxl/drawing/anchor.py +166 -0
- openpyxl/drawing/colors.py +681 -0
- openpyxl/drawing/connector.py +121 -0
- openpyxl/drawing/drawing.py +78 -0
- openpyxl/drawing/effect.py +364 -0
- openpyxl/drawing/fill.py +456 -0
- openpyxl/drawing/geometry.py +764 -0
- openpyxl/drawing/graphic.py +140 -0
- openpyxl/drawing/image.py +95 -0
- openpyxl/drawing/legacy.py +24 -0
- openpyxl/drawing/line.py +142 -0
- openpyxl/drawing/picture.py +126 -0
- openpyxl/drawing/properties.py +158 -0
- openpyxl/drawing/relation.py +13 -0
- openpyxl/drawing/spreadsheet_drawing.py +269 -0
- openpyxl/drawing/text.py +767 -0
- openpyxl/drawing/xdr.py +30 -0
- openpyxl/formatting/__init__.py +2 -0
- openpyxl/formatting/formatting.py +95 -0
- openpyxl/formatting/rule.py +383 -0
- openpyxl/formula/__init__.py +2 -0
- openpyxl/formula/structural.py +178 -0
- openpyxl/formula/tokenizer.py +444 -0
- openpyxl/formula/translate.py +179 -0
- openpyxl/packaging/__init__.py +3 -0
- openpyxl/packaging/core.py +120 -0
- openpyxl/packaging/custom.py +236 -0
- openpyxl/packaging/extended.py +131 -0
- openpyxl/packaging/interface.py +49 -0
- openpyxl/packaging/manifest.py +196 -0
- openpyxl/packaging/relationship.py +148 -0
- openpyxl/packaging/workbook.py +189 -0
- openpyxl/pivot/__init__.py +1 -0
- openpyxl/pivot/cache.py +920 -0
- openpyxl/pivot/fields.py +280 -0
- openpyxl/pivot/record.py +81 -0
- openpyxl/pivot/table.py +1297 -0
- openpyxl/reader/__init__.py +1 -0
- openpyxl/reader/drawings.py +73 -0
- openpyxl/reader/excel.py +486 -0
- openpyxl/reader/strings.py +38 -0
- openpyxl/reader/workbook.py +121 -0
- openpyxl/styles/__init__.py +15 -0
- openpyxl/styles/alignment.py +79 -0
- openpyxl/styles/borders.py +133 -0
- openpyxl/styles/builtins.py +1362 -0
- openpyxl/styles/cell_style.py +203 -0
- openpyxl/styles/colors.py +213 -0
- openpyxl/styles/differential.py +81 -0
- openpyxl/styles/fills.py +228 -0
- openpyxl/styles/fonts.py +146 -0
- openpyxl/styles/named_styles.py +255 -0
- openpyxl/styles/numbers.py +181 -0
- openpyxl/styles/protection.py +17 -0
- openpyxl/styles/proxy.py +56 -0
- openpyxl/styles/styleable.py +217 -0
- openpyxl/styles/stylesheet.py +263 -0
- openpyxl/styles/table.py +100 -0
- openpyxl/utils/__init__.py +12 -0
- openpyxl/utils/bound_dictionary.py +24 -0
- openpyxl/utils/cell.py +229 -0
- openpyxl/utils/dataframe.py +73 -0
- openpyxl/utils/datetime.py +147 -0
- openpyxl/utils/escape.py +37 -0
- openpyxl/utils/exceptions.py +56 -0
- openpyxl/utils/formulas.py +380 -0
- openpyxl/utils/indexed_list.py +49 -0
- openpyxl/utils/inference.py +66 -0
- openpyxl/utils/protection.py +22 -0
- openpyxl/utils/units.py +116 -0
- openpyxl/volatile/__init__.py +1 -0
- openpyxl/volatile/volatile.py +79 -0
- openpyxl/workbook/__init__.py +2 -0
- openpyxl/workbook/_writer.py +185 -0
- openpyxl/workbook/child.py +147 -0
- openpyxl/workbook/defined_name.py +185 -0
- openpyxl/workbook/external_link/__init__.py +2 -0
- openpyxl/workbook/external_link/external.py +135 -0
- openpyxl/workbook/external_reference.py +11 -0
- openpyxl/workbook/function_group.py +24 -0
- openpyxl/workbook/properties.py +143 -0
- openpyxl/workbook/protection.py +180 -0
- openpyxl/workbook/smart_tags.py +37 -0
- openpyxl/workbook/views.py +142 -0
- openpyxl/workbook/web.py +98 -0
- openpyxl/workbook/workbook.py +428 -0
- openpyxl/worksheet/__init__.py +1 -0
- openpyxl/worksheet/_read_only.py +173 -0
- openpyxl/worksheet/_reader.py +482 -0
- openpyxl/worksheet/_structural_edit.py +428 -0
- openpyxl/worksheet/_write_only.py +138 -0
- openpyxl/worksheet/_writer.py +384 -0
- openpyxl/worksheet/cell_range.py +497 -0
- openpyxl/worksheet/cell_watch.py +23 -0
- openpyxl/worksheet/controls.py +323 -0
- openpyxl/worksheet/copier.py +57 -0
- openpyxl/worksheet/custom.py +23 -0
- openpyxl/worksheet/datavalidation.py +211 -0
- openpyxl/worksheet/dimensions.py +315 -0
- openpyxl/worksheet/drawing.py +11 -0
- openpyxl/worksheet/errors.py +74 -0
- openpyxl/worksheet/filters.py +558 -0
- openpyxl/worksheet/formula.py +47 -0
- openpyxl/worksheet/header_footer.py +260 -0
- openpyxl/worksheet/hyperlink.py +40 -0
- openpyxl/worksheet/merge.py +150 -0
- openpyxl/worksheet/ole.py +126 -0
- openpyxl/worksheet/page.py +167 -0
- openpyxl/worksheet/pagebreak.py +75 -0
- openpyxl/worksheet/picture.py +8 -0
- openpyxl/worksheet/print_settings.py +165 -0
- openpyxl/worksheet/properties.py +91 -0
- openpyxl/worksheet/protection.py +148 -0
- openpyxl/worksheet/related.py +13 -0
- openpyxl/worksheet/scenario.py +80 -0
- openpyxl/worksheet/smart_tag.py +57 -0
- openpyxl/worksheet/table.py +360 -0
- openpyxl/worksheet/views.py +145 -0
- openpyxl/worksheet/worksheet.py +982 -0
- openpyxl/writer/__init__.py +1 -0
- openpyxl/writer/excel.py +351 -0
- openpyxl/writer/theme.py +310 -0
- openpyxl/xml/__init__.py +44 -0
- openpyxl/xml/constants.py +121 -0
- openpyxl/xml/functions.py +80 -0
- openpyxl2-3.2.0b2.dist-info/METADATA +125 -0
- openpyxl2-3.2.0b2.dist-info/RECORD +204 -0
- openpyxl2-3.2.0b2.dist-info/WHEEL +5 -0
- openpyxl2-3.2.0b2.dist-info/licenses/LICENCE.rst +23 -0
- openpyxl2-3.2.0b2.dist-info/top_level.txt +1 -0
openpyxl/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright (c) 2010-2025 openpyxl
|
|
2
|
+
|
|
3
|
+
DEBUG = False
|
|
4
|
+
|
|
5
|
+
from openpyxl.compat.numbers import NUMPY
|
|
6
|
+
from openpyxl.xml import DEFUSEDXML
|
|
7
|
+
from openpyxl.xml import LXML
|
|
8
|
+
from openpyxl.workbook.workbook import Workbook
|
|
9
|
+
from openpyxl.reader.excel import load_workbook
|
|
10
|
+
import openpyxl._constants as constants
|
|
11
|
+
|
|
12
|
+
# Expose constants especially the version number
|
|
13
|
+
|
|
14
|
+
__author__ = constants.__author__
|
|
15
|
+
__author_email__ = constants.__author_email__
|
|
16
|
+
__license__ = constants.__license__
|
|
17
|
+
__maintainer_email__ = constants.__maintainer_email__
|
|
18
|
+
__url__ = constants.__url__
|
|
19
|
+
__version__ = constants.__version__
|
openpyxl/_constants.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Copyright (c) 2010-2025 openpyxl
|
|
2
|
+
"""
|
|
3
|
+
Package metadata
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__author__ = "See AUTHORS"
|
|
7
|
+
__author_email__ = "charlie.clark@clark-consulting.eu"
|
|
8
|
+
__license__ = "MIT"
|
|
9
|
+
__maintainer_email__ = "openpyxl-users@googlegroups.com"
|
|
10
|
+
__url__ = "https://openpyxl.pages.heptapod.net/openpyxl/"
|
|
11
|
+
__version__ = "3.2.0b2"
|
|
12
|
+
__python__ = "3.8"
|
openpyxl/cell/_writer.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Copyright (c) 2010-2025 openpyxl
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
from openpyxl.cell.rich_text import CellRichText
|
|
5
|
+
from openpyxl.compat.strings import safe_string
|
|
6
|
+
from openpyxl.utils.datetime import to_excel
|
|
7
|
+
from openpyxl.utils.datetime import to_ISO8601
|
|
8
|
+
from openpyxl.worksheet.formula import ArrayFormula
|
|
9
|
+
from openpyxl.worksheet.formula import DataTableFormula
|
|
10
|
+
from openpyxl.xml import LXML
|
|
11
|
+
from openpyxl.xml.functions import XML_NS
|
|
12
|
+
from openpyxl.xml.functions import Element
|
|
13
|
+
from openpyxl.xml.functions import SubElement
|
|
14
|
+
from openpyxl.xml.functions import whitespace
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _set_attributes(cell, styled=None):
|
|
18
|
+
"""
|
|
19
|
+
Set coordinate and datatype
|
|
20
|
+
"""
|
|
21
|
+
coordinate = cell.coordinate
|
|
22
|
+
attrs = {"r": coordinate}
|
|
23
|
+
if styled:
|
|
24
|
+
attrs["s"] = f"{cell.style_id}"
|
|
25
|
+
if cell.data_type == "s":
|
|
26
|
+
attrs["t"] = "inlineStr"
|
|
27
|
+
elif cell.data_type != "f":
|
|
28
|
+
attrs["t"] = cell.data_type
|
|
29
|
+
value = cell._value
|
|
30
|
+
if cell.data_type == "d":
|
|
31
|
+
if hasattr(value, "tzinfo") and value.tzinfo is not None:
|
|
32
|
+
raise TypeError(
|
|
33
|
+
"Excel does not support timezones in datetimes. "
|
|
34
|
+
"The tzinfo in the datetime/time object must be set to None."
|
|
35
|
+
)
|
|
36
|
+
if cell.parent.parent.iso_dates and not isinstance(value, datetime.timedelta):
|
|
37
|
+
value = to_ISO8601(value)
|
|
38
|
+
else:
|
|
39
|
+
attrs["t"] = "n"
|
|
40
|
+
value = to_excel(value, cell.parent.parent.epoch)
|
|
41
|
+
if cell.hyperlink:
|
|
42
|
+
cell.parent._hyperlinks.append(cell.hyperlink)
|
|
43
|
+
return value, attrs
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def etree_write_cell(xf, worksheet, cell, styled=None):
|
|
47
|
+
value, attributes = _set_attributes(cell, styled)
|
|
48
|
+
el = Element("c", attributes)
|
|
49
|
+
if value is None or value == "":
|
|
50
|
+
xf.write(el)
|
|
51
|
+
return
|
|
52
|
+
if cell.data_type == "f":
|
|
53
|
+
attrib = {}
|
|
54
|
+
if isinstance(value, ArrayFormula):
|
|
55
|
+
attrib = dict(value)
|
|
56
|
+
value = value.text
|
|
57
|
+
elif isinstance(value, DataTableFormula):
|
|
58
|
+
attrib = dict(value)
|
|
59
|
+
value = None
|
|
60
|
+
formula = SubElement(el, "f", attrib)
|
|
61
|
+
if value is not None and not attrib.get("t") == "dataTable":
|
|
62
|
+
formula.text = value[1:]
|
|
63
|
+
value = None
|
|
64
|
+
if cell.data_type == "s":
|
|
65
|
+
if isinstance(value, CellRichText):
|
|
66
|
+
el.append(value.to_tree())
|
|
67
|
+
else:
|
|
68
|
+
inline_string = Element("is")
|
|
69
|
+
text = Element("t")
|
|
70
|
+
text.text = value
|
|
71
|
+
whitespace(text)
|
|
72
|
+
inline_string.append(text)
|
|
73
|
+
el.append(inline_string)
|
|
74
|
+
else:
|
|
75
|
+
cell_content = SubElement(el, "v")
|
|
76
|
+
if value is not None:
|
|
77
|
+
cell_content.text = safe_string(value)
|
|
78
|
+
xf.write(el)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def lxml_write_cell(xf, worksheet, cell, styled=False):
|
|
82
|
+
value, attributes = _set_attributes(cell, styled)
|
|
83
|
+
if value == "" or value is None:
|
|
84
|
+
with xf.element("c", attributes):
|
|
85
|
+
return
|
|
86
|
+
with xf.element("c", attributes):
|
|
87
|
+
if cell.data_type == "f":
|
|
88
|
+
attrib = {}
|
|
89
|
+
if isinstance(value, ArrayFormula):
|
|
90
|
+
attrib = dict(value)
|
|
91
|
+
value = value.text
|
|
92
|
+
elif isinstance(value, DataTableFormula):
|
|
93
|
+
attrib = dict(value)
|
|
94
|
+
value = None
|
|
95
|
+
with xf.element("f", attrib):
|
|
96
|
+
if value is not None and not attrib.get("t") == "dataTable":
|
|
97
|
+
xf.write(value[1:])
|
|
98
|
+
value = None
|
|
99
|
+
if cell.data_type == "s":
|
|
100
|
+
if isinstance(value, CellRichText):
|
|
101
|
+
el = value.to_tree()
|
|
102
|
+
xf.write(el)
|
|
103
|
+
else:
|
|
104
|
+
with xf.element("is"):
|
|
105
|
+
if isinstance(value, str):
|
|
106
|
+
attrs = {}
|
|
107
|
+
if value != value.strip():
|
|
108
|
+
attrs[f"{{{XML_NS}}}space"] = "preserve"
|
|
109
|
+
el = Element("t", attrs) # lxml can't handle xml-ns
|
|
110
|
+
el.text = value
|
|
111
|
+
xf.write(el)
|
|
112
|
+
else:
|
|
113
|
+
with xf.element("v"):
|
|
114
|
+
if value is not None:
|
|
115
|
+
xf.write(safe_string(value))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if LXML:
|
|
119
|
+
write_cell = lxml_write_cell
|
|
120
|
+
else:
|
|
121
|
+
write_cell = etree_write_cell
|
openpyxl/cell/cell.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# Copyright (c) 2010-2025 openpyxl
|
|
2
|
+
"""Manage individual cells in a spreadsheet.
|
|
3
|
+
|
|
4
|
+
The Cell class is required to know its value and type, display options,
|
|
5
|
+
and any other features of an Excel cell. Utilities for referencing
|
|
6
|
+
cells using Excel's 'A1' column/row nomenclature are also provided.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
import copy
|
|
10
|
+
import datetime
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
from openpyxl.cell.coordinate import Coordinate
|
|
14
|
+
from openpyxl.cell.rich_text import CellRichText
|
|
15
|
+
from openpyxl.compat.numbers import NUMERIC_TYPES
|
|
16
|
+
from openpyxl.styles.alignment import Alignment
|
|
17
|
+
from openpyxl.styles.borders import Border
|
|
18
|
+
from openpyxl.styles.fills import PatternFill
|
|
19
|
+
from openpyxl.styles.fonts import Font
|
|
20
|
+
from openpyxl.styles.numbers import FORMAT_DATE_DATETIME
|
|
21
|
+
from openpyxl.styles.numbers import FORMAT_DATE_TIME6
|
|
22
|
+
from openpyxl.styles.numbers import FORMAT_DATE_TIMEDELTA
|
|
23
|
+
from openpyxl.styles.numbers import FORMAT_DATE_YYYYMMDD2
|
|
24
|
+
from openpyxl.styles.numbers import FORMAT_GENERAL
|
|
25
|
+
from openpyxl.styles.numbers import is_date_format
|
|
26
|
+
from openpyxl.styles.protection import Protection
|
|
27
|
+
from openpyxl.styles.styleable import NamedStyleDescriptor
|
|
28
|
+
from openpyxl.styles.styleable import StyleableObject
|
|
29
|
+
from openpyxl.utils.exceptions import IllegalCharacterError
|
|
30
|
+
from openpyxl.worksheet.formula import ArrayFormula
|
|
31
|
+
from openpyxl.worksheet.formula import DataTableFormula
|
|
32
|
+
from openpyxl.worksheet.hyperlink import Hyperlink
|
|
33
|
+
|
|
34
|
+
TIME_TYPES = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
|
|
35
|
+
TIME_FORMATS = {
|
|
36
|
+
datetime.datetime: FORMAT_DATE_DATETIME,
|
|
37
|
+
datetime.date: FORMAT_DATE_YYYYMMDD2,
|
|
38
|
+
datetime.time: FORMAT_DATE_TIME6,
|
|
39
|
+
datetime.timedelta: FORMAT_DATE_TIMEDELTA,
|
|
40
|
+
}
|
|
41
|
+
STRING_TYPES = (str, bytes, CellRichText)
|
|
42
|
+
KNOWN_TYPES = NUMERIC_TYPES + TIME_TYPES + STRING_TYPES + (bool, type(None))
|
|
43
|
+
ILLEGAL_CHARACTERS_RE = re.compile(r"[\000-\010]|[\013-\014]|[\016-\037]")
|
|
44
|
+
ERROR_CODES = ("#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A")
|
|
45
|
+
TYPE_STRING = "s"
|
|
46
|
+
TYPE_FORMULA = "f"
|
|
47
|
+
TYPE_NUMERIC = "n"
|
|
48
|
+
TYPE_BOOL = "b"
|
|
49
|
+
TYPE_NULL = "n"
|
|
50
|
+
TYPE_INLINE = "inlineStr"
|
|
51
|
+
TYPE_ERROR = "e"
|
|
52
|
+
TYPE_FORMULA_CACHE_STRING = "str"
|
|
53
|
+
VALID_TYPES = (
|
|
54
|
+
TYPE_STRING,
|
|
55
|
+
TYPE_FORMULA,
|
|
56
|
+
TYPE_NUMERIC,
|
|
57
|
+
TYPE_BOOL,
|
|
58
|
+
TYPE_NULL,
|
|
59
|
+
TYPE_INLINE,
|
|
60
|
+
TYPE_ERROR,
|
|
61
|
+
TYPE_FORMULA_CACHE_STRING,
|
|
62
|
+
)
|
|
63
|
+
_TYPES = {int: "n", float: "n", str: "s", bool: "b"}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_type(t, value):
|
|
67
|
+
if isinstance(value, NUMERIC_TYPES):
|
|
68
|
+
dt = "n"
|
|
69
|
+
elif isinstance(value, STRING_TYPES):
|
|
70
|
+
dt = "s"
|
|
71
|
+
elif isinstance(value, TIME_TYPES):
|
|
72
|
+
dt = "d"
|
|
73
|
+
elif isinstance(value, (DataTableFormula, ArrayFormula)):
|
|
74
|
+
dt = "f"
|
|
75
|
+
else:
|
|
76
|
+
return None
|
|
77
|
+
_TYPES[t] = dt
|
|
78
|
+
return dt
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_time_format(t):
|
|
82
|
+
value = TIME_FORMATS.get(t)
|
|
83
|
+
if value:
|
|
84
|
+
return value
|
|
85
|
+
for base in t.mro()[1:]:
|
|
86
|
+
value = TIME_FORMATS.get(base)
|
|
87
|
+
if value:
|
|
88
|
+
TIME_FORMATS[t] = value
|
|
89
|
+
return value
|
|
90
|
+
raise ValueError(f"Could not get time format for {value!r}")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Cell(StyleableObject):
|
|
94
|
+
"""Describes cell associated properties.
|
|
95
|
+
|
|
96
|
+
Properties of interest include style, type, value, and address.
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
__slots__ = ("_coord", "_value", "data_type", "parent", "_hyperlink", "_comment")
|
|
101
|
+
|
|
102
|
+
def __init__(self, worksheet, row=None, column=None, value=None, style_array=None):
|
|
103
|
+
super().__init__(worksheet, style_array)
|
|
104
|
+
self._coord = Coordinate(row, column) # 1-based index
|
|
105
|
+
self._value = None
|
|
106
|
+
self._hyperlink = None
|
|
107
|
+
self.data_type = "n"
|
|
108
|
+
if value is not None:
|
|
109
|
+
self.value = value
|
|
110
|
+
self._comment = None
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def column(self):
|
|
114
|
+
return self._coord.column
|
|
115
|
+
|
|
116
|
+
col_idx = column
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def row(self):
|
|
120
|
+
return self._coord.row
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def coordinate(self):
|
|
124
|
+
"""This cell's coordinate (ex. 'A5')"""
|
|
125
|
+
return str(self._coord)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def column_letter(self):
|
|
129
|
+
return self._coord.column_letter
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def encoding(self):
|
|
133
|
+
return self.parent.encoding
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def base_date(self):
|
|
137
|
+
return self.parent.parent.epoch
|
|
138
|
+
|
|
139
|
+
def __repr__(self):
|
|
140
|
+
return f"<Cell {self.parent.title!r}.{self.coordinate}>"
|
|
141
|
+
|
|
142
|
+
def check_string(self, value):
|
|
143
|
+
"""Check string coding, length, and line break character"""
|
|
144
|
+
if value is None:
|
|
145
|
+
return None
|
|
146
|
+
# convert to str string
|
|
147
|
+
if not isinstance(value, str):
|
|
148
|
+
value = str(value, self.encoding)
|
|
149
|
+
value = str(value)
|
|
150
|
+
# string must never be longer than 32,767 characters
|
|
151
|
+
# truncate if necessary
|
|
152
|
+
value = value[:32767]
|
|
153
|
+
if next(ILLEGAL_CHARACTERS_RE.finditer(value), None):
|
|
154
|
+
raise IllegalCharacterError(f"{value} cannot be used in worksheets.")
|
|
155
|
+
return value
|
|
156
|
+
|
|
157
|
+
def check_error(self, value):
|
|
158
|
+
"""Tries to convert Error" else N/A"""
|
|
159
|
+
try:
|
|
160
|
+
return str(value)
|
|
161
|
+
except UnicodeDecodeError:
|
|
162
|
+
return "#N/A"
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def value(self):
|
|
166
|
+
"""Get or set the value held in the cell.
|
|
167
|
+
|
|
168
|
+
:type: depends on the value (string, float, int or
|
|
169
|
+
:class:`datetime.datetime`)
|
|
170
|
+
"""
|
|
171
|
+
return self._value
|
|
172
|
+
|
|
173
|
+
@value.setter
|
|
174
|
+
def value(self, value):
|
|
175
|
+
"""Set the value and infer type and display options."""
|
|
176
|
+
self.data_type = "n"
|
|
177
|
+
t = type(value)
|
|
178
|
+
try:
|
|
179
|
+
dt = _TYPES[t]
|
|
180
|
+
except KeyError:
|
|
181
|
+
dt = get_type(t, value)
|
|
182
|
+
|
|
183
|
+
if dt is None and value is not None:
|
|
184
|
+
raise ValueError(f"Cannot convert {value!r} to Excel")
|
|
185
|
+
if dt:
|
|
186
|
+
self.data_type = dt
|
|
187
|
+
if dt == "d":
|
|
188
|
+
if not is_date_format(self.number_format):
|
|
189
|
+
self.number_format = get_time_format(t)
|
|
190
|
+
elif dt == "s" and not isinstance(value, CellRichText):
|
|
191
|
+
value = self.check_string(value)
|
|
192
|
+
if len(value) > 1 and value.startswith("="):
|
|
193
|
+
self.data_type = "f"
|
|
194
|
+
elif value in ERROR_CODES:
|
|
195
|
+
self.data_type = "e"
|
|
196
|
+
self._value = value
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def hyperlink(self):
|
|
200
|
+
"""Return the hyperlink target or an empty string"""
|
|
201
|
+
return self._hyperlink
|
|
202
|
+
|
|
203
|
+
@hyperlink.setter
|
|
204
|
+
def hyperlink(self, val):
|
|
205
|
+
"""Set value and display for hyperlinks in a cell.
|
|
206
|
+
Automatically sets the `value` of the cell with link text,
|
|
207
|
+
but you can modify it afterward by setting the `value`
|
|
208
|
+
property, and the hyperlink will remain.
|
|
209
|
+
Hyperlink is removed if set to ``None``."""
|
|
210
|
+
if val is None:
|
|
211
|
+
self._hyperlink = None
|
|
212
|
+
else:
|
|
213
|
+
if not isinstance(val, Hyperlink):
|
|
214
|
+
val = Hyperlink(ref="", target=val)
|
|
215
|
+
val.ref = self.coordinate
|
|
216
|
+
self._hyperlink = val
|
|
217
|
+
if self._value is None:
|
|
218
|
+
self.value = val.target or val.location
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def is_date(self):
|
|
222
|
+
"""True if the value is formatted as a date
|
|
223
|
+
|
|
224
|
+
:type: bool
|
|
225
|
+
"""
|
|
226
|
+
con1 = self.data_type == "d"
|
|
227
|
+
con2 = self.data_type == "n" and is_date_format(self.number_format)
|
|
228
|
+
return con1 or con2
|
|
229
|
+
|
|
230
|
+
def offset(self, row=0, column=0):
|
|
231
|
+
"""Returns a cell location relative to this cell.
|
|
232
|
+
|
|
233
|
+
:param row: number of rows to offset
|
|
234
|
+
:type row: int
|
|
235
|
+
|
|
236
|
+
:param column: number of columns to offset
|
|
237
|
+
:type column: int
|
|
238
|
+
|
|
239
|
+
:rtype: :class:`openpyxl.cell.Cell`
|
|
240
|
+
"""
|
|
241
|
+
row = self._coord.row + row
|
|
242
|
+
column = self._coord.column + column
|
|
243
|
+
return self.parent.cell(row=row, column=column)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def comment(self):
|
|
247
|
+
"""Returns the comment associated with this cell
|
|
248
|
+
|
|
249
|
+
:type: :class:`openpyxl.comments.Comment`
|
|
250
|
+
"""
|
|
251
|
+
return self._comment
|
|
252
|
+
|
|
253
|
+
@comment.setter
|
|
254
|
+
def comment(self, value):
|
|
255
|
+
"""
|
|
256
|
+
Assign a comment to a cell
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
if value is not None:
|
|
260
|
+
if value.parent:
|
|
261
|
+
value = copy.copy(value)
|
|
262
|
+
value.bind(self)
|
|
263
|
+
elif value is None and self._comment:
|
|
264
|
+
self._comment.unbind()
|
|
265
|
+
self._comment = value
|
|
266
|
+
|
|
267
|
+
def set_style_to_merged_cells(self, key, value):
|
|
268
|
+
if not hasattr(self.parent, "merged_cells"):
|
|
269
|
+
return
|
|
270
|
+
for merged in self.parent.merged_cells:
|
|
271
|
+
if (self.row, self.column) != (merged.min_row, merged.min_col):
|
|
272
|
+
continue
|
|
273
|
+
if key == "border":
|
|
274
|
+
merged._apply_border(value)
|
|
275
|
+
return
|
|
276
|
+
for row, column in merged.cells:
|
|
277
|
+
cell = self.parent._cells[(row, column)]
|
|
278
|
+
if cell is self:
|
|
279
|
+
continue
|
|
280
|
+
if key == "style":
|
|
281
|
+
NamedStyleDescriptor().__set__(cell, value)
|
|
282
|
+
else:
|
|
283
|
+
setattr(cell, key, value)
|
|
284
|
+
if key == "style":
|
|
285
|
+
merged._apply_border(copy.copy(self.border))
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def alignment(self):
|
|
290
|
+
return self._alignment
|
|
291
|
+
|
|
292
|
+
@alignment.setter
|
|
293
|
+
def alignment(self, value):
|
|
294
|
+
self._alignment = value
|
|
295
|
+
self.set_style_to_merged_cells("alignment", value)
|
|
296
|
+
|
|
297
|
+
@alignment.deleter
|
|
298
|
+
def alignment(self):
|
|
299
|
+
self._alignment = Alignment()
|
|
300
|
+
self.set_style_to_merged_cells("alignment", Alignment())
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def border(self):
|
|
304
|
+
return self._border
|
|
305
|
+
|
|
306
|
+
@border.setter
|
|
307
|
+
def border(self, value):
|
|
308
|
+
self._border = value
|
|
309
|
+
self.set_style_to_merged_cells("border", value)
|
|
310
|
+
|
|
311
|
+
@border.deleter
|
|
312
|
+
def border(self):
|
|
313
|
+
self._border = Border()
|
|
314
|
+
self.set_style_to_merged_cells("border", Border())
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def fill(self):
|
|
318
|
+
return self._fill
|
|
319
|
+
|
|
320
|
+
@fill.setter
|
|
321
|
+
def fill(self, value):
|
|
322
|
+
self._fill = value
|
|
323
|
+
self.set_style_to_merged_cells("fill", value)
|
|
324
|
+
|
|
325
|
+
@fill.deleter
|
|
326
|
+
def fill(self):
|
|
327
|
+
self._fill = PatternFill()
|
|
328
|
+
self.set_style_to_merged_cells("fill", PatternFill())
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def font(self):
|
|
332
|
+
return self._font
|
|
333
|
+
|
|
334
|
+
@font.setter
|
|
335
|
+
def font(self, value):
|
|
336
|
+
self._font = value
|
|
337
|
+
self.set_style_to_merged_cells("font", value)
|
|
338
|
+
|
|
339
|
+
@font.deleter
|
|
340
|
+
def font(self):
|
|
341
|
+
self._font = Font()
|
|
342
|
+
self.set_style_to_merged_cells("font", Font())
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def number_format(self):
|
|
346
|
+
return self._number_format
|
|
347
|
+
|
|
348
|
+
@number_format.setter
|
|
349
|
+
def number_format(self, value):
|
|
350
|
+
self._number_format = value
|
|
351
|
+
self.set_style_to_merged_cells("number_format", value)
|
|
352
|
+
|
|
353
|
+
@number_format.deleter
|
|
354
|
+
def number_format(self):
|
|
355
|
+
self._number_format = FORMAT_GENERAL
|
|
356
|
+
self.set_style_to_merged_cells("number_format", FORMAT_GENERAL)
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def protection(self):
|
|
360
|
+
return self._protection
|
|
361
|
+
|
|
362
|
+
@protection.setter
|
|
363
|
+
def protection(self, value):
|
|
364
|
+
self._protection = value
|
|
365
|
+
self.set_style_to_merged_cells("protection", value)
|
|
366
|
+
|
|
367
|
+
@protection.deleter
|
|
368
|
+
def protection(self):
|
|
369
|
+
self._protection = Protection()
|
|
370
|
+
self.set_style_to_merged_cells("protection", Protection())
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def style(self):
|
|
374
|
+
return NamedStyleDescriptor().__get__(self, None)
|
|
375
|
+
|
|
376
|
+
@style.setter
|
|
377
|
+
def style(self, value):
|
|
378
|
+
NamedStyleDescriptor().__set__(self, value)
|
|
379
|
+
self.set_style_to_merged_cells("style", value)
|
|
380
|
+
|
|
381
|
+
@style.deleter
|
|
382
|
+
def style(self):
|
|
383
|
+
NamedStyleDescriptor().__set__(self, "Normal")
|
|
384
|
+
self.set_style_to_merged_cells("style", "Normal")
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class MergedCell(StyleableObject):
|
|
388
|
+
"""
|
|
389
|
+
Describes the properties of a cell in a merged cell and helps to
|
|
390
|
+
display the borders of the merged cell.
|
|
391
|
+
|
|
392
|
+
The value of a MergedCell is always None.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
__slots__ = ("_coord",)
|
|
396
|
+
|
|
397
|
+
_value = None
|
|
398
|
+
data_type = "n"
|
|
399
|
+
comment = None
|
|
400
|
+
hyperlink = None
|
|
401
|
+
|
|
402
|
+
def __init__(self, worksheet, row=None, column=None):
|
|
403
|
+
super().__init__(worksheet)
|
|
404
|
+
self._coord = Coordinate(row, column)
|
|
405
|
+
|
|
406
|
+
def __repr__(self):
|
|
407
|
+
return f"<MergedCell {self.parent.title!r}.{self.coordinate}>"
|
|
408
|
+
|
|
409
|
+
coordinate = Cell.coordinate
|
|
410
|
+
row = Cell.row
|
|
411
|
+
column = Cell.column
|
|
412
|
+
_comment = comment
|
|
413
|
+
value = _value
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def WriteOnlyCell(ws=None, value=None):
|
|
417
|
+
return Cell(worksheet=ws, column=1, row=1, value=value)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright (c) 2010-2025 openpyxl
|
|
2
|
+
"""
|
|
3
|
+
Represent a cell's coordinate in any collection
|
|
4
|
+
"""
|
|
5
|
+
from openpyxl.utils.cell import coordinate_to_tuple
|
|
6
|
+
from openpyxl.utils.cell import get_column_letter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Coordinate:
|
|
10
|
+
__slots__ = ("row", "column")
|
|
11
|
+
|
|
12
|
+
def __init__(self, row, column):
|
|
13
|
+
self.row = row
|
|
14
|
+
self.column = column
|
|
15
|
+
|
|
16
|
+
def __str__(self):
|
|
17
|
+
return f"{get_column_letter(self.column)}{self.row}"
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def column_letter(self):
|
|
21
|
+
return get_column_letter(self.column)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_string(cls, coord):
|
|
25
|
+
row, column = coordinate_to_tuple(coord)
|
|
26
|
+
return cls(row, column)
|
|
27
|
+
|
|
28
|
+
def __eq__(self, other):
|
|
29
|
+
return self.row == other.row and self.column == other.column
|
|
30
|
+
|
|
31
|
+
def __ne__(self, other):
|
|
32
|
+
return not self == other
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Copyright (c) 2010-2025 openpyxl
|
|
2
|
+
from openpyxl.cell.cell import Cell
|
|
3
|
+
from openpyxl.cell.coordinate import Coordinate
|
|
4
|
+
from openpyxl.styles.numbers import BUILTIN_FORMATS
|
|
5
|
+
from openpyxl.styles.numbers import BUILTIN_FORMATS_MAX_SIZE
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReadOnlyCell:
|
|
9
|
+
__slots__ = ("parent", "_coord", "_value", "data_type", "_style_id")
|
|
10
|
+
|
|
11
|
+
def __init__(self, sheet, row, column, value, data_type="n", style_id=0):
|
|
12
|
+
self.parent = sheet
|
|
13
|
+
self._value = None
|
|
14
|
+
self._coord = Coordinate(row, column)
|
|
15
|
+
self.data_type = data_type
|
|
16
|
+
self._value = value
|
|
17
|
+
self._style_id = style_id
|
|
18
|
+
|
|
19
|
+
def __eq__(self, other):
|
|
20
|
+
for a in self.__slots__:
|
|
21
|
+
if getattr(self, a) != getattr(other, a):
|
|
22
|
+
return False
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
def __ne__(self, other):
|
|
26
|
+
return not self.__eq__(other)
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
return f"<ReadOnlyCell {self.parent.title!r}.{self.coordinate}>"
|
|
30
|
+
|
|
31
|
+
row = Cell.row
|
|
32
|
+
column = Cell.column
|
|
33
|
+
column_letter = Cell.column_letter
|
|
34
|
+
coordinate = Cell.coordinate
|
|
35
|
+
is_date = Cell.is_date
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def style_array(self):
|
|
39
|
+
return self.parent.parent._cell_styles[self._style_id]
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def has_style(self):
|
|
43
|
+
return self._style_id != 0
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def number_format(self):
|
|
47
|
+
_id = self.style_array.numFmtId
|
|
48
|
+
if _id < BUILTIN_FORMATS_MAX_SIZE:
|
|
49
|
+
return BUILTIN_FORMATS.get(_id, "General")
|
|
50
|
+
else:
|
|
51
|
+
return self.parent.parent._number_formats[_id - BUILTIN_FORMATS_MAX_SIZE]
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def font(self):
|
|
55
|
+
_id = self.style_array.fontId
|
|
56
|
+
return self.parent.parent._fonts[_id]
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def fill(self):
|
|
60
|
+
_id = self.style_array.fillId
|
|
61
|
+
return self.parent.parent._fills[_id]
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def border(self):
|
|
65
|
+
_id = self.style_array.borderId
|
|
66
|
+
return self.parent.parent._borders[_id]
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def alignment(self):
|
|
70
|
+
_id = self.style_array.alignmentId
|
|
71
|
+
return self.parent.parent._alignments[_id]
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def protection(self):
|
|
75
|
+
_id = self.style_array.protectionId
|
|
76
|
+
return self.parent.parent._protections[_id]
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def value(self):
|
|
80
|
+
return self._value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class EmptyCell:
|
|
84
|
+
__slots__ = ()
|
|
85
|
+
value = None
|
|
86
|
+
is_date = False
|
|
87
|
+
font = None
|
|
88
|
+
border = None
|
|
89
|
+
fill = None
|
|
90
|
+
number_format = None
|
|
91
|
+
alignment = None
|
|
92
|
+
data_type = "n"
|
|
93
|
+
|
|
94
|
+
def __repr__(self):
|
|
95
|
+
return "<EmptyCell>"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
EMPTY_CELL = EmptyCell()
|