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

1# Copyright (c) 2010-2022 openpyxl 

2 

3""" 

4Collection of utilities used within the package and also available for client code 

5""" 

6import re 

7from string import digits 

8 

9from .exceptions import CellCoordinatesException 

10 

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) 

26 

27 

28def get_column_interval(start, end): 

29 """ 

30 Given the start and end columns, return all the columns in the series. 

31 

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)] 

40 

41 

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 

54 

55 

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") 

61 

62 d = m.groupdict('') 

63 for k, v in d.items(): 

64 if v: 

65 d[k] = f"${v}" 

66 

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) 

72 

73 

74def _get_column_letter(col_idx): 

75 """Convert a column number into a column letter (3 -> 'C') 

76 

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. 

80 

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)) 

95 

96 

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 

103 

104 

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)) 

113 

114 

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)) 

124 

125 

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) 

136 

137 min_col, min_row, sep, max_col, max_row = m.groups() 

138 

139 if sep: 

140 cols = min_col, max_col 

141 rows = min_row, max_row 

142 

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) 

149 

150 if min_col is not None: 

151 min_col = column_index_from_string(min_col) 

152 

153 if min_row is not None: 

154 min_row = int(min_row) 

155 

156 if max_col is not None: 

157 max_col = column_index_from_string(max_col) 

158 else: 

159 max_col = min_col 

160 

161 if max_row is not None: 

162 max_row = int(max_row) 

163 else: 

164 max_row = min_row 

165 

166 return min_col, min_row, max_col, max_row 

167 

168 

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) 

179 

180 

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) 

191 

192 

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] 

203 

204 

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 

217 

218 

219def quote_sheetname(sheetname): 

220 """ 

221 Add quotes around sheetnames if they contain spaces. 

222 """ 

223 if "'" in sheetname: 

224 sheetname = sheetname.replace("'", "''") 

225 

226 sheetname = u"'{0}'".format(sheetname) 

227 return sheetname