306 lines
10 KiB
Python
306 lines
10 KiB
Python
import openpyxl
|
||
import sys
|
||
try:
|
||
from openpyxl.styles.colors import COLOR_INDEX
|
||
except Exception:
|
||
COLOR_INDEX = []
|
||
|
||
FILE = 'global.xlsx'
|
||
SHEET = 'Sheet1'
|
||
CELLS = ['P547', 'X458']
|
||
|
||
try:
|
||
wb = openpyxl.load_workbook(FILE, data_only=False)
|
||
except Exception as e:
|
||
print(f"Failed to open {FILE}: {e}")
|
||
sys.exit(1)
|
||
|
||
if SHEET in wb.sheetnames:
|
||
ws = wb[SHEET]
|
||
else:
|
||
print(f"Sheet '{SHEET}' not found. Available sheets: {wb.sheetnames}. Using active sheet '{wb.active.title}'.")
|
||
ws = wb.active
|
||
|
||
|
||
def color_info(c):
|
||
if c is None:
|
||
return None
|
||
info = {}
|
||
for attr in ('type','rgb','indexed','index','theme','tint','auto'):
|
||
try:
|
||
info[attr] = getattr(c, attr, None)
|
||
except Exception as e:
|
||
info[attr] = f"<error: {e}>"
|
||
# 归一化尝试:把常见的 tuple/list/int/bytes 等 rgb 表示转换为可读字符串
|
||
info['normalized'], info['original_repr'] = normalize_color_value(c)
|
||
try:
|
||
kind, value = safe_resolve_color(c)
|
||
info['resolved'] = {'kind': kind, 'value': value}
|
||
except Exception as e:
|
||
info['resolved'] = {'error': str(e)}
|
||
return info
|
||
|
||
|
||
def normalize_color_value(val):
|
||
"""
|
||
尝试把颜色对象或多种表示归一化为一个字符串(优先 ARGB hex),并返回 (normalized_str_or_None, original_repr).
|
||
支持:
|
||
- openpyxl Color 对象 (会尝试读取 .rgb/.indexed/.index/.theme/.tint)
|
||
- str (如 'FF00B050', '#FF00B050', 'rgb(0,176,80)')
|
||
- tuple/list of ints (r,g,b) 或 (r,g,b,a)
|
||
- int (如 16711680)
|
||
- bytes
|
||
任何不能解析的值将返回 (None, repr(val)).
|
||
"""
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
|
||
def as_argb_from_rgb_tuple(tpl):
|
||
try:
|
||
comps = list(tpl)
|
||
if len(comps) == 3:
|
||
r, g, b = comps
|
||
a = 255
|
||
elif len(comps) >= 4:
|
||
r, g, b, a = comps[:4]
|
||
else:
|
||
return None
|
||
return "{0:02X}{1:02X}{2:02X}{3:02X}".format(a, r, g, b)
|
||
except Exception as e:
|
||
logger.debug(f"as_argb_from_rgb_tuple failed: {e} for {tpl}")
|
||
return None
|
||
|
||
# If it's an openpyxl Color-like object, try attributes first
|
||
try:
|
||
# guard: if object has .rgb/.indexed etc, prefer those
|
||
if hasattr(val, 'rgb') or hasattr(val, 'indexed') or hasattr(val, 'index') or hasattr(val, 'theme'):
|
||
# try .rgb
|
||
try:
|
||
rgb = getattr(val, 'rgb', None)
|
||
if rgb:
|
||
s = str(rgb)
|
||
# strip leading '#' if present
|
||
if s.startswith('#'):
|
||
s = s[1:]
|
||
if len(s) == 6:
|
||
s = 'FF' + s
|
||
return (s.upper(), repr(rgb))
|
||
except Exception as e:
|
||
logger.debug(f"error reading .rgb: {e}")
|
||
|
||
# try indexed/index mapping (best-effort)
|
||
try:
|
||
idx = getattr(val, 'indexed', None) or getattr(val, 'index', None)
|
||
if idx is not None:
|
||
try:
|
||
idx_int = int(idx)
|
||
# map using openpyxl COLOR_INDEX if available
|
||
if COLOR_INDEX and 0 <= idx_int < len(COLOR_INDEX):
|
||
hexv = COLOR_INDEX[idx_int]
|
||
if hexv:
|
||
hexs = hexv.upper()
|
||
if len(hexs) == 6:
|
||
hexs = 'FF' + hexs
|
||
return (hexs, repr(idx_int))
|
||
return (f'INDEXED_{idx_int}', repr(idx_int))
|
||
except Exception:
|
||
return (f'INDEXED_{idx}', repr(idx))
|
||
except Exception as e:
|
||
logger.debug(f"error reading .indexed/index: {e}")
|
||
|
||
# theme
|
||
try:
|
||
theme = getattr(val, 'theme', None)
|
||
if theme is not None:
|
||
return (f'THEME_{theme}', repr(theme))
|
||
except Exception as e:
|
||
logger.debug(f"error reading .theme: {e}")
|
||
|
||
except Exception:
|
||
# not an object we can introspect
|
||
pass
|
||
|
||
# Primitive types
|
||
# str
|
||
try:
|
||
if isinstance(val, str):
|
||
s = val.strip()
|
||
if s.startswith('#'):
|
||
s = s[1:]
|
||
# If it contains 'rgb(' leave as-is
|
||
if s.lower().startswith('rgb('):
|
||
return (s, repr(val))
|
||
# if hex-like
|
||
hexchars = ''.join(ch for ch in s if ch.isalnum())
|
||
if all(c in '0123456789ABCDEFabcdef' for c in hexchars) and len(hexchars) in (6,8):
|
||
if len(hexchars) == 6:
|
||
hexchars = 'FF' + hexchars
|
||
return (hexchars.upper(), repr(val))
|
||
except Exception as e:
|
||
logger.debug(f"error normalizing str val: {e}")
|
||
|
||
# tuple/list
|
||
try:
|
||
if isinstance(val, (tuple, list)):
|
||
# might be like (r,g,b) or (r,g,b,a)
|
||
normalized = as_argb_from_rgb_tuple(val)
|
||
if normalized:
|
||
return (normalized, repr(val))
|
||
except Exception as e:
|
||
logger.debug(f"error normalizing sequence val: {e}")
|
||
|
||
# int (maybe packed RGB)
|
||
try:
|
||
if isinstance(val, int):
|
||
v = val & 0xFFFFFFFF
|
||
# If it's 24-bit or 32-bit
|
||
if v <= 0xFFFFFF:
|
||
# assume RGB
|
||
r = (v >> 16) & 0xFF
|
||
g = (v >> 8) & 0xFF
|
||
b = v & 0xFF
|
||
a = 255
|
||
else:
|
||
a = (v >> 24) & 0xFF
|
||
r = (v >> 16) & 0xFF
|
||
g = (v >> 8) & 0xFF
|
||
b = v & 0xFF
|
||
return ("{0:02X}{1:02X}{2:02X}{3:02X}".format(a, r, g, b), repr(val))
|
||
except Exception as e:
|
||
logger.debug(f"error normalizing int val: {e}")
|
||
|
||
# bytes
|
||
try:
|
||
if isinstance(val, (bytes, bytearray)):
|
||
hexs = val.hex().upper()
|
||
if len(hexs) == 6:
|
||
hexs = 'FF' + hexs
|
||
return (hexs, repr(val))
|
||
except Exception as e:
|
||
logger.debug(f"error normalizing bytes val: {e}")
|
||
|
||
# fallback: try to stringify, but filter out known library error messages
|
||
try:
|
||
r = repr(val)
|
||
if 'MUST BE OF TYPE' in r.upper() or 'VALUES MUST' in r.upper():
|
||
return (None, r)
|
||
return (r, r)
|
||
except Exception:
|
||
return (None, '<unrepresentable>')
|
||
from openpyxl.styles import colors as _colors
|
||
|
||
def safe_resolve_color(fg):
|
||
# 返回 (kind, value):
|
||
# - ('argb', 'FF00B050') 或 ('indexed', 35) 或 ('theme', 2) 或 (None, None)
|
||
try:
|
||
if fg is None:
|
||
return (None, None)
|
||
except Exception:
|
||
return (None, None)
|
||
|
||
# 1) rgb 字符串
|
||
try:
|
||
rgb = getattr(fg, 'rgb', None)
|
||
if rgb:
|
||
s = str(rgb)
|
||
if s.startswith('#'):
|
||
s = s[1:]
|
||
if len(s) == 6:
|
||
s = 'FF' + s
|
||
return ('argb', s.upper())
|
||
except Exception:
|
||
# 读取 rgb 可能抛异常,记录并继续回退
|
||
pass
|
||
|
||
# 2) indexed / index -> 尝试映射到 hex
|
||
try:
|
||
idx = getattr(fg, 'indexed', None) or getattr(fg, 'index', None)
|
||
if idx is not None:
|
||
try:
|
||
idx_int = int(idx)
|
||
# COLOR_INDEX 是 openpyxl 内置的索引颜色表
|
||
hexv = None
|
||
try:
|
||
hexv = _colors.COLOR_INDEX[idx_int] # e.g. '00FF00'
|
||
except Exception:
|
||
hexv = None
|
||
if hexv:
|
||
hexs = hexv.upper()
|
||
if len(hexs) == 6:
|
||
hexs = 'FF' + hexs
|
||
return ('argb', hexs)
|
||
else:
|
||
return ('indexed', idx_int)
|
||
except Exception:
|
||
return ('indexed', idx)
|
||
except Exception:
|
||
pass
|
||
|
||
# 3) theme
|
||
try:
|
||
theme = getattr(fg, 'theme', None)
|
||
if theme is not None:
|
||
return ('theme', theme)
|
||
except Exception:
|
||
pass
|
||
|
||
return (None, None)
|
||
for coord in CELLS:
|
||
print(f"--- {coord} ---")
|
||
cell = ws[coord]
|
||
print('coordinate:', cell.coordinate)
|
||
print('row, column:', cell.row, cell.column)
|
||
print('value repr:', repr(cell.value))
|
||
print('data_type:', getattr(cell, 'data_type', None))
|
||
print('is_date:', getattr(cell, 'is_date', None))
|
||
print('number_format:', getattr(cell, 'number_format', None))
|
||
print('font:', cell.font)
|
||
try:
|
||
font_attrs = { 'name': cell.font.name, 'size': cell.font.sz, 'bold': cell.font.bold, 'italic': cell.font.italic, 'color': getattr(cell.font, 'color', None) }
|
||
except Exception as e:
|
||
font_attrs = {'error': str(e)}
|
||
print('font attributes:', font_attrs)
|
||
|
||
fill = getattr(cell, 'fill', None)
|
||
print('fill obj:', type(fill))
|
||
if fill is not None:
|
||
try:
|
||
print(' patternType:', getattr(fill, 'patternType', None))
|
||
except Exception as e:
|
||
print(' patternType error:', e)
|
||
fg = getattr(fill, 'fgColor', None)
|
||
bg = getattr(fill, 'bgColor', None)
|
||
print(' fgColor:', color_info(fg))
|
||
print(' bgColor:', color_info(bg))
|
||
fg_kind, fg_value = safe_resolve_color(fg)
|
||
bg_kind, bg_value = safe_resolve_color(bg)
|
||
print(' fgColor resolved:', {'kind': fg_kind, 'value': fg_value})
|
||
print(' bgColor resolved:', {'kind': bg_kind, 'value': bg_value})
|
||
else:
|
||
print(' fill is None')
|
||
|
||
print('alignment:', cell.alignment)
|
||
print('border:', cell.border)
|
||
print('protection:', cell.protection)
|
||
try:
|
||
comment = cell.comment.text if cell.comment else None
|
||
except Exception as e:
|
||
comment = f'<error reading comment: {e}>'
|
||
print('comment:', comment)
|
||
try:
|
||
hyperlink = cell.hyperlink.target if cell.hyperlink else None
|
||
except Exception as e:
|
||
hyperlink = f'<error reading hyperlink: {e}>'
|
||
print('hyperlink:', hyperlink)
|
||
|
||
# raw repr of the cell object's __dict__ if available
|
||
try:
|
||
d = {k: repr(v) for k,v in cell.__dict__.items()}
|
||
print('cell.__dict__ keys:', list(cell.__dict__.keys()))
|
||
except Exception:
|
||
d = None
|
||
# don't print full dict to avoid huge output, but show if present
|
||
print()
|
||
|
||
print('Done')
|