hyb
2025-12-23 c980682a1fe205d8c21d349e9fc6b9e4951aea34
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
# Copyright (c) 2010-2024 openpyxl
 
""" Read worksheets on-demand
"""
 
from .worksheet import Worksheet
from openpyxl.cell.read_only import ReadOnlyCell, EMPTY_CELL
from openpyxl.utils import get_column_letter
 
from ._reader import WorkSheetParser
from openpyxl.workbook.defined_name import DefinedNameDict
 
 
def read_dimension(source):
    parser = WorkSheetParser(source, [])
    return parser.parse_dimensions()
 
 
class ReadOnlyWorksheet:
 
    _min_column = 1
    _min_row = 1
    _max_column = _max_row = None
 
    # from Standard Worksheet
    # Methods from Worksheet
    cell = Worksheet.cell
    iter_rows = Worksheet.iter_rows
    values = Worksheet.values
    rows = Worksheet.rows
    __getitem__ = Worksheet.__getitem__
    __iter__ = Worksheet.__iter__
 
 
    def __init__(self, parent_workbook, title, worksheet_path, shared_strings):
        self.parent = parent_workbook
        self.title = title
        self.sheet_state = 'visible'
        self._current_row = None
        self._worksheet_path = worksheet_path
        self._shared_strings = shared_strings
        self._get_size()
        self.defined_names = DefinedNameDict()
 
 
    def _get_size(self):
        src = self._get_source()
        parser = WorkSheetParser(src, [])
        dimensions = parser.parse_dimensions()
        src.close()
        if dimensions is not None:
            self._min_column, self._min_row, self._max_column, self._max_row = dimensions
 
 
    def _get_source(self):
        """Parse xml source on demand, must close after use"""
        return self.parent._archive.open(self._worksheet_path)
 
 
    def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False):
        """
        The source worksheet file may have columns or rows missing.
        Missing cells will be created.
        """
        filler = EMPTY_CELL
        if values_only:
            filler = None
 
        max_col = max_col or self.max_column
        max_row = max_row or self.max_row
        empty_row = []
        if max_col is not None:
            empty_row = (filler,) * (max_col + 1 - min_col)
 
        counter = min_row
        idx = 1
        with self._get_source() as src:
            parser = WorkSheetParser(src,
                                     self._shared_strings,
                                     data_only=self.parent.data_only,
                                     epoch=self.parent.epoch,
                                     date_formats=self.parent._date_formats,
                                     timedelta_formats=self.parent._timedelta_formats)
 
            for idx, row in parser.parse():
                if max_row is not None and idx > max_row:
                    break
 
                # some rows are missing
                for _ in range(counter, idx):
                    counter += 1
                    yield empty_row
 
                # return cells from a row
                if counter <= idx:
                    row = self._get_row(row, min_col, max_col, values_only)
                    counter += 1
                    yield row
 
        if max_row is not None and max_row < idx:
            for _ in range(counter, max_row+1):
                yield empty_row
 
 
    def _get_row(self, row, min_col=1, max_col=None, values_only=False):
        """
        Make sure a row contains always the same number of cells or values
        """
        if not row and not max_col: # in case someone wants to force rows where there aren't any
            return ()
 
        max_col = max_col or  row[-1]['column']
        row_width = max_col + 1 - min_col
 
        new_row = [EMPTY_CELL] * row_width
        if values_only:
            new_row = [None] * row_width
 
        for cell in row:
            counter = cell['column']
            if min_col <= counter <= max_col:
                idx = counter - min_col # position in list of cells returned
                new_row[idx] = cell['value']
                if not values_only:
                    new_row[idx] = ReadOnlyCell(self, **cell)
 
        return tuple(new_row)
 
 
    def _get_cell(self, row, column):
        """Cells are returned by a generator which can be empty"""
        for row in self._cells_by_row(column, row, column, row):
            if row:
                return row[0]
        return EMPTY_CELL
 
 
    def calculate_dimension(self, force=False):
        if not all([self.max_column, self.max_row]):
            if force:
                self._calculate_dimension()
            else:
                raise ValueError("Worksheet is unsized, use calculate_dimension(force=True)")
        return f"{get_column_letter(self.min_column)}{self.min_row}:{get_column_letter(self.max_column)}{self.max_row}"
 
 
    def _calculate_dimension(self):
        """
        Loop through all the cells to get the size of a worksheet.
        Do this only if it is explicitly requested.
        """
 
        max_col = 0
        for r in self.rows:
            if not r:
                continue
            cell = r[-1]
            max_col = max(max_col, cell.column)
 
        self._max_row = cell.row
        self._max_column = max_col
 
 
    def reset_dimensions(self):
        """
        Remove worksheet dimensions if these are incorrect in the worksheet source.
        NB. This probably indicates a bug in the library or application that created
        the workbook.
        """
        self._max_row = self._max_column = None
 
 
    @property
    def min_row(self):
        return self._min_row
 
 
    @property
    def max_row(self):
        return self._max_row
 
 
    @property
    def min_column(self):
        return self._min_column
 
 
    @property
    def max_column(self):
        return self._max_column