Coverage for graphica/core/excel_utils.py: 92.6%
36 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""Excel の読み込みの補助。
3pandas は数式セルの計算済みの値を読むので、Excel 以外で書いたファイルや手動計算のまま保存したファイルでは
4値が無く空になる。欠損に見えて気づきにくいので、読む前に見つけて知らせる。
5"""
6import logging
8import openpyxl
10logger = logging.getLogger(__name__)
13def find_unevaluated_formula_cells(file_path: str, sheet_name: str | None = None, max_examples: int = 5,
14 max_scan_cells: int = 200_000) -> tuple[bool, list[str], bool]:
15 """数式なのに計算済みの値を持たないセルを探す。(見つかったか, 例のリスト, 全部を見たか)。
17 大きなファイルで重くならないよう max_scan_cells で打ち切る。
18 """
19 # .xls は openpyxl で開けず、xlrd は計算済みの値しか持たないので確かめようがない
20 if not str(file_path).lower().endswith('.xlsx'):
21 return False, [], True
23 wb_formulas = None
24 wb_values = None
25 try:
26 # 検査に失敗したら「見つからなかった」扱いにする約束なので、開くところから try に入れる
27 wb_formulas = openpyxl.load_workbook(file_path, data_only=False, read_only=True)
28 wb_values = openpyxl.load_workbook(file_path, data_only=True, read_only=True)
30 sheets = [sheet_name] if sheet_name else wb_formulas.sheetnames
31 examples = []
32 scanned = 0
34 for sname in sheets:
35 if sname not in wb_formulas.sheetnames or sname not in wb_values.sheetnames: 35 ↛ 36line 35 didn't jump to line 36 because the condition on line 35 was never true
36 continue
37 ws_formulas = wb_formulas[sname]
38 ws_values = wb_values[sname]
40 for row_f, row_v in zip(ws_formulas.iter_rows(), ws_values.iter_rows()):
41 for cell_f, cell_v in zip(row_f, row_v):
42 scanned += 1
43 if cell_f.data_type == 'f' and cell_v.value is None:
44 examples.append(f"{sname}!{cell_f.coordinate}")
45 if len(examples) >= max_examples:
46 return True, examples, (scanned < max_scan_cells)
47 if scanned >= max_scan_cells: 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true
48 return bool(examples), examples, False
50 return bool(examples), examples, True
51 except Exception:
52 logger.exception("数式セルの検査中にエラーが発生しました: %s", file_path)
53 return False, [], True
54 finally:
55 if wb_formulas is not None:
56 wb_formulas.close()
57 if wb_values is not None:
58 wb_values.close()