Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/openpyxl/utils/cell.py : 32%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (c) 2010-2020 openpyxl
3"""
4Collection of utilities used within the package and also available for client code
5"""
6import re
8from .exceptions import CellCoordinatesException
10# constants
11COORD_RE = re.compile(r'^[$]?([A-Za-z]{1,3})[$]?(\d+)$')
12COL_RANGE = """[A-Z]{1,3}:[A-Z]{1,3}:"""
13ROW_RANGE = r"""\d+:\d+:"""
14RANGE_EXPR = r"""
15[$]?(?P<min_col>[A-Za-z]{1,3})?
16[$]?(?P<min_row>\d+)?
17(:[$]?(?P<max_col>[A-Za-z]{1,3})?
18[$]?(?P<max_row>\d+)?)?
19"""
20ABSOLUTE_RE = re.compile('^' + RANGE_EXPR +'$', re.VERBOSE)
21SHEET_TITLE = r"""
22(('(?P<quoted>([^']|'')*)')|(?P<notquoted>[^'^ ^!]*))!"""
23SHEETRANGE_RE = re.compile("""{0}(?P<cells>{1})(?=,?)""".format(
24 SHEET_TITLE, RANGE_EXPR), re.VERBOSE)
27def get_column_interval(start, end):
28 """
29 Given the start and end columns, return all the columns in the series.
31 The start and end columns can be either column letters or 1-based
32 indexes.
33 """
34 if isinstance(start, str):
35 start = column_index_from_string(start)
36 if isinstance(end, str):
37 end = column_index_from_string(end)
38 return [get_column_letter(x) for x in range(start, end + 1)]
41def coordinate_from_string(coord_string):
42 """Convert a coordinate string like 'B12' to a tuple ('B', 12)"""
43 match = COORD_RE.match(coord_string)
44 if not match:
45 msg = f"Invalid cell coordinates ({coord_string})"
46 raise CellCoordinatesException(msg)
47 column, row = match.groups()
48 row = int(row)
49 if not row:
50 msg = f"There is no row 0 ({coord_string})"
51 raise CellCoordinatesException(msg)
52 return column, row
55def absolute_coordinate(coord_string):
56 """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)"""
57 m = ABSOLUTE_RE.match(coord_string)
58 if not m:
59 raise ValueError("{0} is not a valid coordinate range".format(
60 coord_string))
62 d = m.groupdict('')
63 for k, v in d.items():
64 if v:
65 d[k] = "${0}".format(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:
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 match = COORD_RE.split(coordinate)
198 col, row = match[1:3]
199 return int(row), _COL_STRING_CACHE[col]
202def range_to_tuple(range_string):
203 """
204 Convert a worksheet range to the sheetname and maximum and minimum
205 coordinate indices
206 """
207 m = SHEETRANGE_RE.match(range_string)
208 if m is None:
209 raise ValueError("Value must be of the form sheetname!A1:E4")
210 sheetname = m.group("quoted") or m.group("notquoted")
211 cells = m.group("cells")
212 boundaries = range_boundaries(cells)
213 return sheetname, boundaries
216def quote_sheetname(sheetname):
217 """
218 Add quotes around sheetnames if they contain spaces.
219 """
220 if "'" in sheetname:
221 sheetname = sheetname.replace("'", "''")
223 sheetname = u"'{0}'".format(sheetname)
224 return sheetname