Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/openpyxl/utils/cell.py: 25%
121 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1# Copyright (c) 2010-2022 openpyxl
3"""
4Collection of utilities used within the package and also available for client code
5"""
6import re
7from string import digits
9from .exceptions import CellCoordinatesException
11# constants
12COORD_RE = re.compile(r'^[$]?([A-Za-z]{1,3})[$]?(\d+)$')
13COL_RANGE = """[A-Z]{1,3}:[A-Z]{1,3}:"""
14ROW_RANGE = r"""\d+:\d+:"""
15RANGE_EXPR = r"""
16[$]?(?P<min_col>[A-Za-z]{1,3})?
17[$]?(?P<min_row>\d+)?
18(:[$]?(?P<max_col>[A-Za-z]{1,3})?
19[$]?(?P<max_row>\d+)?)?
20"""
21ABSOLUTE_RE = re.compile('^' + RANGE_EXPR +'$', re.VERBOSE)
22SHEET_TITLE = r"""
23(('(?P<quoted>([^']|'')*)')|(?P<notquoted>[^'^ ^!]*))!"""
24SHEETRANGE_RE = re.compile("""{0}(?P<cells>{1})(?=,?)""".format(
25 SHEET_TITLE, RANGE_EXPR), re.VERBOSE)
28def get_column_interval(start, end):
29 """
30 Given the start and end columns, return all the columns in the series.
32 The start and end columns can be either column letters or 1-based
33 indexes.
34 """
35 if isinstance(start, str):
36 start = column_index_from_string(start)
37 if isinstance(end, str):
38 end = column_index_from_string(end)
39 return [get_column_letter(x) for x in range(start, end + 1)]
42def coordinate_from_string(coord_string):
43 """Convert a coordinate string like 'B12' to a tuple ('B', 12)"""
44 match = COORD_RE.match(coord_string)
45 if not match:
46 msg = f"Invalid cell coordinates ({coord_string})"
47 raise CellCoordinatesException(msg)
48 column, row = match.groups()
49 row = int(row)
50 if not row:
51 msg = f"There is no row 0 ({coord_string})"
52 raise CellCoordinatesException(msg)
53 return column, row
56def absolute_coordinate(coord_string):
57 """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)"""
58 m = ABSOLUTE_RE.match(coord_string)
59 if not m:
60 raise ValueError(f"{coord_string} is not a valid coordinate range")
62 d = m.groupdict('')
63 for k, v in d.items():
64 if v:
65 d[k] = f"${v}"
67 if d['max_col'] or d['max_row']:
68 fmt = "{min_col}{min_row}:{max_col}{max_row}"
69 else:
70 fmt = "{min_col}{min_row}"
71 return fmt.format(**d)
74def _get_column_letter(col_idx):
75 """Convert a column number into a column letter (3 -> 'C')
77 Right shift the column col_idx by 26 to find column letters in reverse
78 order. These numbers are 1-based, and can be converted to ASCII
79 ordinals by adding 64.
81 """
82 # these indicies corrospond to A -> ZZZ and include all allowed
83 # columns
84 if not 1 <= col_idx <= 18278: 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true
85 raise ValueError("Invalid column index {0}".format(col_idx))
86 letters = []
87 while col_idx > 0:
88 col_idx, remainder = divmod(col_idx, 26)
89 # check for exact division and borrow if needed
90 if remainder == 0:
91 remainder = 26
92 col_idx -= 1
93 letters.append(chr(remainder+64))
94 return ''.join(reversed(letters))
97_COL_STRING_CACHE = {}
98_STRING_COL_CACHE = {}
99for i in range(1, 18279):
100 col = _get_column_letter(i)
101 _STRING_COL_CACHE[i] = col
102 _COL_STRING_CACHE[col] = i
105def get_column_letter(idx,):
106 """Convert a column index into a column letter
107 (3 -> 'C')
108 """
109 try:
110 return _STRING_COL_CACHE[idx]
111 except KeyError:
112 raise ValueError("Invalid column index {0}".format(idx))
115def column_index_from_string(str_col):
116 """Convert a column name into a numerical index
117 ('A' -> 1)
118 """
119 # we use a function argument to get indexed name lookup
120 try:
121 return _COL_STRING_CACHE[str_col.upper()]
122 except KeyError:
123 raise ValueError("{0} is not a valid column name".format(str_col))
126def range_boundaries(range_string):
127 """
128 Convert a range string into a tuple of boundaries:
129 (min_col, min_row, max_col, max_row)
130 Cell coordinates will be converted into a range with the cell at both end
131 """
132 msg = "{0} is not a valid coordinate or range".format(range_string)
133 m = ABSOLUTE_RE.match(range_string)
134 if not m:
135 raise ValueError(msg)
137 min_col, min_row, sep, max_col, max_row = m.groups()
139 if sep:
140 cols = min_col, max_col
141 rows = min_row, max_row
143 if not (
144 all(cols + rows) or
145 all(cols) and not any(rows) or
146 all(rows) and not any(cols)
147 ):
148 raise ValueError(msg)
150 if min_col is not None:
151 min_col = column_index_from_string(min_col)
153 if min_row is not None:
154 min_row = int(min_row)
156 if max_col is not None:
157 max_col = column_index_from_string(max_col)
158 else:
159 max_col = min_col
161 if max_row is not None:
162 max_row = int(max_row)
163 else:
164 max_row = min_row
166 return min_col, min_row, max_col, max_row
169def rows_from_range(range_string):
170 """
171 Get individual addresses for every cell in a range.
172 Yields one row at a time.
173 """
174 min_col, min_row, max_col, max_row = range_boundaries(range_string)
175 rows = range(min_row, max_row + 1)
176 cols = [get_column_letter(col) for col in range(min_col, max_col + 1)]
177 for row in rows:
178 yield tuple('{0}{1}'.format(col, row) for col in cols)
181def cols_from_range(range_string):
182 """
183 Get individual addresses for every cell in a range.
184 Yields one row at a time.
185 """
186 min_col, min_row, max_col, max_row = range_boundaries(range_string)
187 rows = range(min_row, max_row+1)
188 cols = (get_column_letter(col) for col in range(min_col, max_col+1))
189 for col in cols:
190 yield tuple('{0}{1}'.format(col, row) for row in rows)
193def coordinate_to_tuple(coordinate):
194 """
195 Convert an Excel style coordinate to (row, colum) tuple
196 """
197 for idx, c in enumerate(coordinate):
198 if c in digits:
199 break
200 col = coordinate[:idx].upper()
201 row = coordinate[idx:]
202 return int(row), _COL_STRING_CACHE[col]
205def range_to_tuple(range_string):
206 """
207 Convert a worksheet range to the sheetname and maximum and minimum
208 coordinate indices
209 """
210 m = SHEETRANGE_RE.match(range_string)
211 if m is None:
212 raise ValueError("Value must be of the form sheetname!A1:E4")
213 sheetname = m.group("quoted") or m.group("notquoted")
214 cells = m.group("cells")
215 boundaries = range_boundaries(cells)
216 return sheetname, boundaries
219def quote_sheetname(sheetname):
220 """
221 Add quotes around sheetnames if they contain spaces.
222 """
223 if "'" in sheetname:
224 sheetname = sheetname.replace("'", "''")
226 sheetname = u"'{0}'".format(sheetname)
227 return sheetname