aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py
blob: 373e263eec818f7a2c32c5182b00fa361f9f1213 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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