aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/openpyxl/cell
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/cell')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/cell/__init__.py4
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/cell/_writer.py136
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/cell/cell.py332
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/cell/read_only.py136
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py202
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/cell/text.py184
6 files changed, 994 insertions, 0 deletions
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)