about summary refs log tree commit diff
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)