py2ls 0.1.10.1__py3-none-any.whl → 0.1.10.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
py2ls/ips.py CHANGED
@@ -519,6 +519,23 @@ def str2num(s, *args, **kwargs):
519
519
  # print(str2num("12345.6789", " ", 2)) # Output: 12 345.68
520
520
  # print(str2num('111113.34555',3,',')) # Output: 111,113.346
521
521
  # print(str2num("123.55555 sec miniuets",3)) # Output: 1.3
522
+
523
+
524
+ def text2num(text):
525
+ # extract the digital nums from a text
526
+ num = []
527
+ for s in ssplit(text, by="digital"):
528
+ try:
529
+ if float(s):
530
+ num.append(float(s))
531
+ except:
532
+ pass
533
+ if len(num) == 1:
534
+ return num[0]
535
+ else:
536
+ return num
537
+
538
+
522
539
  def num2str(num, *args, **kwargs):
523
540
  delimiter = kwargs.get("sep", None)
524
541
  round_digits = kwargs.get("round", None)
@@ -1328,8 +1345,28 @@ def fsave(
1328
1345
  df.to_csv(fpath, **kwargs)
1329
1346
 
1330
1347
  def save_xlsx(fpath, data, **kwargs):
1331
- df = pd.DataFrame(data)
1332
- df.to_excel(fpath, **kwargs)
1348
+ format = kwargs.get("format", None)
1349
+ if format:
1350
+ kwargs.pop("format", None)
1351
+ format_excel(df=data, filename=fpath, **kwargs)
1352
+ else:
1353
+ kwargs.pop("format", None)
1354
+ kwargs.pop("usage", None)
1355
+ kwargs.pop("cell", None)
1356
+ kwargs.pop("width", None)
1357
+ kwargs.pop("height", None)
1358
+ kwargs.pop("width", None)
1359
+ kwargs.pop("height_max", None)
1360
+ kwargs.pop("merge", None)
1361
+ kwargs.pop("shade", None)
1362
+ kwargs.pop("comment", None)
1363
+ kwargs.pop("link", None)
1364
+ kwargs.pop("protect", None)
1365
+ kwargs.pop("number_format", None)
1366
+ kwargs.pop("conditional_format", None)
1367
+ kwargs.pop("index_default", None)
1368
+ df = pd.DataFrame(data)
1369
+ df.to_excel(fpath, **kwargs)
1333
1370
 
1334
1371
  def save_ipynb(fpath, data, **kwargs):
1335
1372
  # Split the content by code fences to distinguish between code and markdown
@@ -1574,6 +1611,22 @@ def listdir(
1574
1611
  orient="list",
1575
1612
  output="df", # 'list','dict','records','index','series'
1576
1613
  ):
1614
+ if isinstance(kind, list):
1615
+ f_ = []
1616
+ for kind_ in kind:
1617
+ f_tmp = listdir(
1618
+ rootdir=rootdir,
1619
+ kind=kind_,
1620
+ sort_by=sort_by,
1621
+ ascending=ascending,
1622
+ contains=contains,
1623
+ orient=orient,
1624
+ output=output,
1625
+ )
1626
+ f_.append(f_tmp)
1627
+ if f_:
1628
+ return pd.concat(f_, ignore_index=True)
1629
+
1577
1630
  if not kind.startswith("."):
1578
1631
  kind = "." + kind
1579
1632
 
@@ -2923,3 +2976,718 @@ def finfo(fpath):
2923
2976
  kind=data["kind"],
2924
2977
  extra_info=extra_info,
2925
2978
  )
2979
+
2980
+
2981
+ # ! format excel file
2982
+ import pandas as pd
2983
+ from datetime import datetime
2984
+ from openpyxl import load_workbook
2985
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
2986
+ from openpyxl.utils import get_column_letter
2987
+ from openpyxl.worksheet.datavalidation import DataValidation
2988
+ from openpyxl.comments import Comment
2989
+ from openpyxl.formatting.rule import ColorScaleRule
2990
+
2991
+
2992
+ def hex2argb(hex_color):
2993
+ """
2994
+ Convert a hex color code to aARGB format required by openpyxl.
2995
+
2996
+ :param hex_color: A hex color code in the format #RRGGBB, RRGGBB, or aARRGGBB.
2997
+ :return: A hex color code in the format aARRGGBB.
2998
+
2999
+ # Example usage
3000
+ print(hex_to_argb("FFFF00")) # Outputs: FFFFFF00
3001
+ print(hex_to_argb("#DF4245")) # Outputs: FFFFFF00
3002
+ print(hex_to_argb("FF00FF00")) # Outputs: FF00FF00 (already in aARGB format)
3003
+ """
3004
+ # Remove the hash if present
3005
+ if hex_color.startswith("#"):
3006
+ hex_color = hex_color[1:]
3007
+
3008
+ # Check if it's already in aARGB format (8 characters)
3009
+ if len(hex_color) == 8:
3010
+ return hex_color
3011
+
3012
+ # Otherwise, assume it's in RRGGBB format and prepend FF for opaque
3013
+ if len(hex_color) == 6:
3014
+ return f"FF{hex_color}"
3015
+
3016
+ raise ValueError(
3017
+ "Invalid hex color format. Use RRGGBB, #RRGGBB, or aARRGGBB format."
3018
+ )
3019
+
3020
+
3021
+ def convert_indices_to_range(row_slice, col_slice):
3022
+ """Convert numerical row and column slices to Excel-style range strings."""
3023
+ start_row = row_slice.start + 1
3024
+ end_row = row_slice.stop if row_slice.stop is not None else None
3025
+ start_col = col_slice.start + 1
3026
+ end_col = col_slice.stop if col_slice.stop is not None else None
3027
+
3028
+ start_col_letter = get_column_letter(start_col)
3029
+ end_col_letter = get_column_letter(end_col) if end_col else None
3030
+ return (
3031
+ f"{start_col_letter}{start_row}:{end_col_letter}{end_row}"
3032
+ if end_col_letter
3033
+ else f"{start_col_letter}{start_row}"
3034
+ )
3035
+
3036
+
3037
+ def apply_format(ws, cell, cell_range):
3038
+ """Apply cell formatting to a specified range."""
3039
+ cell_font, cell_fill, cell_alignment, border = None, None, None, None
3040
+ kws_cell = ["font", "fill", "alignment", "border"]
3041
+ for K, _ in cell.items():
3042
+ print(strcmp(K, kws_cell)[0])
3043
+ if strcmp(K, kws_cell)[0] == "font":
3044
+ #! font
3045
+ font_color = "000000"
3046
+ font_name = "Arial"
3047
+ font_underline = "none"
3048
+ font_size = 14
3049
+ font_bold = False
3050
+ font_strike = False
3051
+ font_italic = False
3052
+ kws_font = [
3053
+ "name",
3054
+ "size",
3055
+ "bold",
3056
+ "underline",
3057
+ "color",
3058
+ "strike",
3059
+ "italic",
3060
+ ]
3061
+ for k_, v_ in cell.get(K, {}).items():
3062
+ if strcmp(k_, kws_font)[0] == "name":
3063
+ font_name = v_
3064
+ elif strcmp(k_, kws_font)[0] == "size":
3065
+ font_size = v_
3066
+ elif strcmp(k_, kws_font)[0] == "bold":
3067
+ font_bold = v_
3068
+ elif strcmp(k_, kws_font)[0] == "underline":
3069
+ font_underline = strcmp(v_, ["none", "single", "double"])[0]
3070
+ elif strcmp(k_, kws_font)[0] == "color":
3071
+ font_color = hex2argb(v_)
3072
+ elif strcmp(k_, kws_font)[0] == "strike":
3073
+ font_strike = v_
3074
+ elif strcmp(k_, kws_font)[0] == "italic":
3075
+ font_italic = v_
3076
+
3077
+ cell_font = Font(
3078
+ name=font_name,
3079
+ size=font_size,
3080
+ bold=font_bold,
3081
+ italic=font_italic,
3082
+ underline=font_underline,
3083
+ strike=font_strike,
3084
+ color=font_color,
3085
+ )
3086
+
3087
+ if strcmp(K, kws_cell)[0] == "fill":
3088
+ #! fill
3089
+ kws_fill = ["start_color", "end_color", "fill_type", "color"]
3090
+ kws_fill_type = [
3091
+ "darkVertical",
3092
+ "lightDown",
3093
+ "lightGrid",
3094
+ "solid",
3095
+ "darkDown",
3096
+ "lightGray",
3097
+ "lightUp",
3098
+ "gray0625",
3099
+ "lightVertical",
3100
+ "lightHorizontal",
3101
+ "darkHorizontal",
3102
+ "gray125",
3103
+ "darkUp",
3104
+ "mediumGray",
3105
+ "darkTrellis",
3106
+ "darkGray",
3107
+ "lightTrellis",
3108
+ "darkGrid",
3109
+ ]
3110
+ start_color, end_color, fill_type = "FFFFFF", "FFFFFF", "solid" # default
3111
+ for k, v in cell.get(K, {}).items():
3112
+ if strcmp(k, kws_fill)[0] == "color":
3113
+ start_color, end_color = hex2argb(v), hex2argb(v)
3114
+ break
3115
+ for k, v in cell.get(K, {}).items():
3116
+ if strcmp(k, kws_fill)[0] == "start_color":
3117
+ start_color = hex2argb(v)
3118
+ elif strcmp(k, kws_fill)[0] == "end_color":
3119
+ end_color = hex2argb(v)
3120
+ elif strcmp(k, kws_fill)[0] == "fill_type":
3121
+ fill_type = strcmp(v, kws_fill_type)[0]
3122
+ cell_fill = PatternFill(
3123
+ start_color=start_color,
3124
+ end_color=end_color,
3125
+ fill_type=fill_type,
3126
+ )
3127
+
3128
+ if strcmp(K, kws_cell)[0] == "alignment":
3129
+ #! alignment
3130
+ # default
3131
+ align_horizontal = "general"
3132
+ align_vertical = "center"
3133
+ align_rot = 0
3134
+ align_wrap = False
3135
+ align_shrink = False
3136
+ align_indent = 0
3137
+ kws_align = [
3138
+ "horizontal",
3139
+ "ha",
3140
+ "vertical",
3141
+ "va",
3142
+ "text_rotation",
3143
+ "rotat",
3144
+ "rot",
3145
+ "wrap_text",
3146
+ "wrap",
3147
+ "shrink_to_fit",
3148
+ "shrink",
3149
+ "indent",
3150
+ ]
3151
+ for k, v in cell.get(K, {}).items():
3152
+ if strcmp(k, kws_align)[0] in ["horizontal", "ha"]:
3153
+ align_horizontal = strcmp(
3154
+ v, ["general", "left", "right", "center"]
3155
+ )[0]
3156
+ elif strcmp(k, kws_align)[0] in ["vertical", "va"]:
3157
+ align_vertical = strcmp(v, ["top", "center", "bottom"])[0]
3158
+ elif strcmp(k, kws_align)[0] in ["text_rotation", "rotat", "rot"]:
3159
+ align_rot = v
3160
+ elif strcmp(k, kws_align)[0] in ["wrap_text", "wrap"]:
3161
+ align_wrap = v
3162
+ elif strcmp(k, kws_align)[0] in [
3163
+ "shrink_to_fit",
3164
+ "shrink",
3165
+ "wrap_text",
3166
+ "wrap",
3167
+ ]:
3168
+ align_shrink = v
3169
+ elif strcmp(k, kws_align)[0] in ["indent"]:
3170
+ align_indent = v
3171
+ cell_alignment = Alignment(
3172
+ horizontal=align_horizontal,
3173
+ vertical=align_vertical,
3174
+ text_rotation=align_rot,
3175
+ wrap_text=align_wrap,
3176
+ shrink_to_fit=align_shrink,
3177
+ indent=align_indent,
3178
+ )
3179
+
3180
+ if strcmp(K, kws_cell)[0] == "border":
3181
+ #! border
3182
+ kws_border = [
3183
+ "color_left",
3184
+ "color_l",
3185
+ "color_right",
3186
+ "color_r",
3187
+ "color_top",
3188
+ "color_t",
3189
+ "color_bottom",
3190
+ "color_b",
3191
+ "color_diagonal",
3192
+ "color_d",
3193
+ "color_outline",
3194
+ "color_o",
3195
+ "color_vertical",
3196
+ "color_v",
3197
+ "color_horizontal",
3198
+ "color_h",
3199
+ "color",
3200
+ "style_left",
3201
+ "style_l",
3202
+ "style_right",
3203
+ "style_r",
3204
+ "style_top",
3205
+ "style_t",
3206
+ "style_bottom",
3207
+ "style_b",
3208
+ "style_diagonal",
3209
+ "style_d",
3210
+ "style_outline",
3211
+ "style_o",
3212
+ "style_vertical",
3213
+ "style_v",
3214
+ "style_horizontal",
3215
+ "style_h",
3216
+ "style",
3217
+ ]
3218
+ # * border color
3219
+ border_color_l, border_color_r, border_color_t, border_color_b = (
3220
+ "FF000000",
3221
+ "FF000000",
3222
+ "FF000000",
3223
+ "FF000000",
3224
+ )
3225
+ border_color_d, border_color_o, border_color_v, border_color_h = (
3226
+ "FF000000",
3227
+ "FF000000",
3228
+ "FF000000",
3229
+ "FF000000",
3230
+ )
3231
+ # get colors config
3232
+ for k, v in cell.get(K, {}).items():
3233
+ if strcmp(k, kws_border)[0] in ["color"]:
3234
+ border_color_all = hex2argb(v)
3235
+ # 如果设置了color,表示其它的所有的都设置成为一样的
3236
+ # 然后再才开始自己定义其它的color
3237
+ border_color_l, border_color_r, border_color_t, border_color_b = (
3238
+ border_color_all,
3239
+ border_color_all,
3240
+ border_color_all,
3241
+ border_color_all,
3242
+ )
3243
+ border_color_d, border_color_o, border_color_v, border_color_h = (
3244
+ border_color_all,
3245
+ border_color_all,
3246
+ border_color_all,
3247
+ border_color_all,
3248
+ )
3249
+ elif strcmp(k, kws_border)[0] in ["color_left", "color_l"]:
3250
+ border_color_l = hex2argb(v)
3251
+ elif strcmp(k, kws_border)[0] in ["color_right", "color_r"]:
3252
+ border_color_r = hex2argb(v)
3253
+ elif strcmp(k, kws_border)[0] in ["color_top", "color_t"]:
3254
+ border_color_t = hex2argb(v)
3255
+ elif strcmp(k, kws_border)[0] in ["color_bottom", "color_b"]:
3256
+ border_color_b = hex2argb(v)
3257
+ elif strcmp(k, kws_border)[0] in ["color_diagonal", "color_d"]:
3258
+ border_color_d = hex2argb(v)
3259
+ elif strcmp(k, kws_border)[0] in ["color_outline", "color_o"]:
3260
+ border_color_o = hex2argb(v)
3261
+ elif strcmp(k, kws_border)[0] in ["color_vertical", "color_v"]:
3262
+ border_color_v = hex2argb(v)
3263
+ elif strcmp(k, kws_border)[0] in ["color_horizontal", "color_h"]:
3264
+ border_color_h = hex2argb(v)
3265
+ # *border style
3266
+ border_styles = [
3267
+ "thin",
3268
+ "medium",
3269
+ "thick",
3270
+ "dotted",
3271
+ "dashed",
3272
+ "hair",
3273
+ "mediumDashed",
3274
+ "dashDot",
3275
+ "dashDotDot",
3276
+ "slantDashDot",
3277
+ "none",
3278
+ ]
3279
+ border_style_l, border_style_r, border_style_t, border_style_b = (
3280
+ None,
3281
+ None,
3282
+ None,
3283
+ None,
3284
+ )
3285
+ border_style_d, border_style_o, border_style_v, border_style_h = (
3286
+ None,
3287
+ None,
3288
+ None,
3289
+ None,
3290
+ )
3291
+ # get styles config
3292
+ for k, v in cell.get("border", {}).items():
3293
+ if not "style" in k:
3294
+ break
3295
+ if strcmp(k, kws_border)[0] in ["style"]:
3296
+ border_style_all = strcmp(v, border_styles)[0]
3297
+ # 如果设置了style,表示其它的所有的都设置成为一样的
3298
+ # 然后再才开始自己定义其它的style
3299
+ border_style_l, border_style_r, border_style_t, border_style_b = (
3300
+ border_style_all,
3301
+ border_style_all,
3302
+ border_style_all,
3303
+ border_style_all,
3304
+ )
3305
+ border_style_d, border_style_o, border_style_v, border_style_h = (
3306
+ border_style_all,
3307
+ border_style_all,
3308
+ border_style_all,
3309
+ border_style_all,
3310
+ )
3311
+ elif strcmp(k, kws_border)[0] in ["style_left", "style_l"]:
3312
+ border_style_l = strcmp(v, border_styles)[0]
3313
+ elif strcmp(k, kws_border)[0] in ["style_right", "style_r"]:
3314
+ border_style_r = strcmp(v, border_styles)[0]
3315
+ elif strcmp(k, kws_border)[0] in ["style_top", "style_t"]:
3316
+ border_style_t = strcmp(v, border_styles)[0]
3317
+ elif strcmp(k, kws_border)[0] in ["style_bottom", "style_b"]:
3318
+ border_style_b = strcmp(v, border_styles)[0]
3319
+ elif strcmp(k, kws_border)[0] in ["style_diagonal", "style_d"]:
3320
+ border_style_d = strcmp(v, border_styles)[0]
3321
+ elif strcmp(k, kws_border)[0] in ["style_outline", "style_o"]:
3322
+ border_style_o = strcmp(v, border_styles)[0]
3323
+ elif strcmp(k, kws_border)[0] in ["style_vertical", "style_v"]:
3324
+ border_style_v = strcmp(v, border_styles)[0]
3325
+ elif strcmp(k, kws_border)[0] in ["style_horizontal", "style_h"]:
3326
+ border_style_h = strcmp(v, border_styles)[0]
3327
+ # * apply border config
3328
+ border = Border(
3329
+ left=Side(border_style=border_style_l, color=border_color_l),
3330
+ right=Side(border_style=border_style_r, color=border_color_r),
3331
+ top=Side(border_style=border_style_t, color=border_color_t),
3332
+ bottom=Side(border_style=border_style_b, color=border_color_b),
3333
+ diagonal=Side(border_style=border_style_d, color=border_color_d),
3334
+ diagonal_direction=0,
3335
+ outline=Side(border_style=border_style_o, color=border_color_o),
3336
+ vertical=Side(border_style=border_style_v, color=border_color_v),
3337
+ horizontal=Side(border_style=border_style_h, color=border_color_h),
3338
+ )
3339
+
3340
+ #! final apply configs
3341
+ for row in ws[cell_range]:
3342
+ for cell_ in row:
3343
+ if cell_font:
3344
+ cell_.font = cell_font
3345
+ if cell_fill:
3346
+ cell_.fill = cell_fill
3347
+ if cell_alignment:
3348
+ cell_.alignment = cell_alignment
3349
+ if border:
3350
+ cell_.border = border
3351
+
3352
+
3353
+ def format_excel(
3354
+ df=None,
3355
+ filename=None,
3356
+ sheet_name=0,
3357
+ usage=False,
3358
+ cell=None, # dict: or list for multiple locs setting:
3359
+ width=None, # dict
3360
+ height=None, # dict e.g., {2: 50, 3: 25}, keys are columns
3361
+ height_max=25,
3362
+ merge=None, # tuple e.g., (slice(0, 1), slice(1, 3)),
3363
+ shade=None, # dict
3364
+ comment=None, # dict e.g., {(2, 4): "This is a comment"},
3365
+ link=None, # dict e.g., {(2, 2): "https://example.com"},
3366
+ protect=None, # dict
3367
+ number_format=None, # dict: e.g., {1:"0.00", 2:"#,##0",3:"0%",4:"$#,##0.00"}
3368
+ data_validation=None, # dict
3369
+ conditional_format=None, # dict
3370
+ index_default=False,
3371
+ **kwargs,
3372
+ ):
3373
+ if not isinstance(df, pd.DataFrame):
3374
+ try:
3375
+ print(f"is loading file {os.path.basename(df)}")
3376
+ df = fload(df)
3377
+ except:
3378
+ print(f"check the fpath:{df}")
3379
+ if filename is None:
3380
+ filename = str(datetime.now().strftime("%y%m%d_%H.xlsx"))
3381
+ # !show usage:
3382
+ func_xample = """
3383
+ # Example usage
3384
+ data = {
3385
+ "Header 1": [10, 2000000, 30],
3386
+ "Header 2": [
3387
+ 40000,
3388
+ '"start_color": "4F81BD", "end_color","start_color": "a1cd1e","start_color": "4F81BD", "end_color","start_color": "a1cd1e"',
3389
+ 60,
3390
+ ],
3391
+ "Header 3": [70, 80, 90],
3392
+ }
3393
+ df = pd.DataFrame(data)
3394
+
3395
+
3396
+ format_excel(
3397
+ df,
3398
+ filename="example.xlsx",
3399
+ sheet_name="Sheet1",
3400
+ cell=[
3401
+ {
3402
+ (slice(0, 1), slice(0, len(df.columns))): {
3403
+ "font": {
3404
+ "name": "Calibri", # Font name
3405
+ "size": 14, # Font size
3406
+ "bold": True, # Bold text
3407
+ "italic": False, # Italic text
3408
+ "underline": "single", # Underline (single, double)
3409
+ "color": "#FFFFFF", # Font color
3410
+ },
3411
+ "fill": {
3412
+ "start_color": "a1cd1e", # Starting color
3413
+ "end_color": "4F81BD", # Ending color (useful for gradients)
3414
+ "fill_type": "solid", # Fill type (solid, gradient, etc.)
3415
+ },
3416
+ "alignment": {
3417
+ "horizontal": "center", # Horizontal alignment (left, center, right)
3418
+ "vertical": "center", # Vertical alignment (top, center, bottom)
3419
+ "wrap_text": True, # Wrap text in the cell
3420
+ "shrink_to_fit": True, # Shrink text to fit within cell
3421
+ "text_rotation": 0, # Text rotation angle
3422
+ },
3423
+ "border": {
3424
+ "left": "thin",
3425
+ "right": "thin",
3426
+ "top": "thin",
3427
+ "bottom": "thin",
3428
+ "color": "000000", # Border color
3429
+ },
3430
+ }
3431
+ },
3432
+ {
3433
+ (slice(0, 3), slice(1, 3)): {
3434
+ "font": {
3435
+ "name": "Arial", # Font name
3436
+ "size": 12, # Font size
3437
+ "bold": False, # Bold text
3438
+ "italic": True, # Italic text
3439
+ "underline": None, # No underline
3440
+ "color": "#000000", # Font color
3441
+ },
3442
+ "fill": {
3443
+ "start_color": "#c61313", # Background color
3444
+ "end_color": "#490606", # End color
3445
+ "fill_type": "solid", # Fill type
3446
+ },
3447
+ "alignment": {
3448
+ "horizontal": "left", # Horizontal alignment
3449
+ "vertical": "top", # Vertical alignment
3450
+ "wrap_text": True, # Enable text wrapping
3451
+ "shrink_to_fit": False, # Disable shrink to fit
3452
+ "indent": 1, # Indentation level
3453
+ "text_rotation": 0, # Text rotation angle
3454
+ },
3455
+ "border": {
3456
+ "left": "thin", # Left border style
3457
+ "right": "thin", # Right border style
3458
+ "top": "thin", # Top border style
3459
+ "bottom": "thin", # Bottom border style
3460
+ "color": "000000", # Border color
3461
+ },
3462
+ }
3463
+ # * border settings
3464
+ # "thin": Thin border line
3465
+ # "medium": Medium border line
3466
+ # "thick": Thick border line
3467
+ # "dotted": Dotted border line
3468
+ # "dashed": Dashed border line
3469
+ # "hair": Hairline border (very thin)
3470
+ # "mediumDashed": Medium dashed border line
3471
+ # "dashDot": Dash-dot border line
3472
+ # "dashDotDot": Dash-dot-dot border line
3473
+ # "slantDashDot": Slant dash-dot border line
3474
+ },
3475
+ ],
3476
+ width={2: 30}, # when it is None, it will automatic adjust width
3477
+ height={2: 50, 3: 25}, # keys are columns
3478
+ merge=(slice(0, 1), slice(1, 3)),
3479
+ shade={
3480
+ (slice(1, 4), slice(1, 3)): {
3481
+ "bg_color": "FFFF00", # Background color
3482
+ "pattern_type": "solid", # Fill pattern (e.g., solid, darkGrid, lightGrid)
3483
+ "fg_color": "#0000FF", # Foreground color, used in patterns
3484
+ "end_color": "0000FF", # End color, useful for gradients
3485
+ "fill_type": "solid", # Type of fill (solid, gradient, etc.)
3486
+ }
3487
+ },
3488
+ comment={(2, 4): "This is a comment"},
3489
+ link={(2, 2): "https://example.com"},
3490
+ protect={
3491
+ "password": "123", # Password for sheet protection
3492
+ "sheet": False, # True, # Protect the sheet
3493
+ "objects": False, # True, # Protect objects
3494
+ "scenarios": False, # True, # Protect scenarios
3495
+ "formatCells": False, # Disable formatting cells
3496
+ "formatColumns": False, # Disable formatting columns
3497
+ "formatRows": False, # Disable formatting rows
3498
+ "insertColumns": False, # Disable inserting columns
3499
+ "insertRows": False, # Disable inserting rows
3500
+ "deleteColumns": False, # Disable deleting columns
3501
+ "deleteRows": False, # Disable deleting rows
3502
+ },
3503
+ number_format={
3504
+ 1: "0.00", # Two decimal places for column index 1
3505
+ 2: "#,##0", # Thousands separator
3506
+ 3: "0%", # Percentage format
3507
+ 4: "$#,##0.00", # Currency format
3508
+ },
3509
+ data_validation={
3510
+ (slice(1, 2), slice(2, 10)): {
3511
+ "type": "list",
3512
+ "formula1": '"Option1,Option2,Option3"', # List of options
3513
+ "allow_blank": True,
3514
+ "showDropDown": True,
3515
+ "showErrorMessage": True,
3516
+ "errorTitle": "Invalid input",
3517
+ "error": "Please select a valid option.",
3518
+ }
3519
+ },
3520
+ conditional_format={
3521
+ (slice(1, 2), slice(2, 10)): [
3522
+ {
3523
+ "color_scale": {
3524
+ "start_type": "min", # Type of start point (min, max, percent)
3525
+ "start_color": "#FF0000", # Starting color
3526
+ "end_type": "max", # End type (min, max, percent)
3527
+ "end_color": "00FF00", # Ending color
3528
+ "mid_type": "percentile", # Midpoint type (optional)
3529
+ "mid_value": 50, # Midpoint value (optional)
3530
+ "mid_color": "FFFF00", # Midpoint color (optional)
3531
+ }
3532
+ }
3533
+ ]
3534
+ },
3535
+ )
3536
+ """
3537
+ if usage:
3538
+ print(func_xample)
3539
+ return None
3540
+ kwargs.pop("format", None) # 更好地跟fsave结合使用
3541
+
3542
+ # Save DataFrame to Excel file
3543
+ if not index_default:
3544
+ df.to_excel(filename, index=False, **kwargs)
3545
+ else:
3546
+ df.to_excel(filename, **kwargs)
3547
+
3548
+ wb = load_workbook(filename)
3549
+ ws = wb.worksheets[sheet_name]
3550
+
3551
+ # !Apply cell formatting
3552
+ if cell:
3553
+ if not isinstance(cell, list):
3554
+ cell = [cell]
3555
+ for cell_ in cell:
3556
+ for indices, format_options in cell_.items():
3557
+ cell_range = convert_indices_to_range(*indices)
3558
+ apply_format(ws, format_options, cell_range)
3559
+
3560
+ # !Apply cell shading
3561
+ if shade:
3562
+ for indices, shading in shade.items():
3563
+ cell_range = convert_indices_to_range(*indices)
3564
+ fill = PatternFill(
3565
+ start_color=hex2argb(shading.get("bg_color", "FFFFFF")),
3566
+ end_color=hex2argb(shading.get("end_color", "FFFFFF")),
3567
+ fill_type=shading.get("fill_type", "solid"),
3568
+ patternType=shading.get("pattern_type", "solid"),
3569
+ fgColor=hex2argb(shading.get("fg_color", "0000FF")),
3570
+ )
3571
+ for row in ws[cell_range]:
3572
+ for cell in row:
3573
+ cell.fill = fill
3574
+
3575
+ # !number formatting
3576
+ if number_format:
3577
+ for col_idx, fmt in number_format.items():
3578
+ col_letter = get_column_letter(col_idx)
3579
+ for cell in ws[col_letter][1:]: # Skip the header
3580
+ cell.number_format = fmt
3581
+
3582
+ # !widths
3583
+ if width is None: # automatic adust width
3584
+ for col in ws.columns:
3585
+ len_ = [len(str(cell.value)) for cell in col if cell.value]
3586
+ if len_:
3587
+ # scaling_factor = 1.2
3588
+ max_length = max(len_) # * scaling_factor
3589
+ ws.column_dimensions[get_column_letter(col[0].column)].width = (
3590
+ max_length
3591
+ )
3592
+ else:
3593
+ for col_idx, width_ in width.items():
3594
+ col_letter = get_column_letter(col_idx)
3595
+ ws.column_dimensions[col_letter].width = width_
3596
+
3597
+ # !heights
3598
+ if height is None: # automatic adust height
3599
+ for row in ws.iter_rows(min_row=1, max_row=ws.max_row):
3600
+ max_height = height_max
3601
+ for cell in row:
3602
+ if cell.value:
3603
+ lines = str(cell.value).split("\n")
3604
+ max_line_length = max(len(line) for line in lines)
3605
+ estimated_height = 15 * len(lines)
3606
+ if max_line_length > 20:
3607
+ estimated_height += 5 * (max_line_length // 20)
3608
+ max_height = max(max_height, estimated_height)
3609
+ ws.row_dimensions[row[0].row].height = max_height
3610
+ else:
3611
+ for row, height_ in height.items():
3612
+ ws.row_dimensions[row].height = height_
3613
+
3614
+ # !Merge cells using slice indices
3615
+ if merge:
3616
+ if isinstance(merge, tuple):
3617
+ merge = [merge]
3618
+ for indices in merge:
3619
+ # Ensure indices are slice objects
3620
+ if len(indices) == 2:
3621
+ row_slice, col_slice = indices
3622
+ merge_range = convert_indices_to_range(row_slice, col_slice)
3623
+ ws.merge_cells(merge_range)
3624
+ elif len(indices) == 4:
3625
+ start_row, start_col, end_row, end_col = indices
3626
+ # Convert column numbers to letters (e.g., 1 -> 'A')
3627
+ start_cell = f"{get_column_letter(start_col)}{start_row}"
3628
+ end_cell = f"{get_column_letter(end_col)}{end_row}"
3629
+ merge_range = f"{start_cell}:{end_cell}"
3630
+ ws.merge_cells(merge_range)
3631
+ else:
3632
+ raise ValueError(
3633
+ f"两种方式: 1. format: (start_row, start_col, end_row, end_col), 2. format: (slice(0, 3), slice(1, 2))"
3634
+ )
3635
+
3636
+ # !Add comment
3637
+ if comment:
3638
+ for (row, col), comment in comment.items():
3639
+ ws.cell(row=row, column=col).comment = Comment(comment, "Author")
3640
+
3641
+ # !Add link
3642
+ if link:
3643
+ for (row, col), link in link.items():
3644
+ ws.cell(row=row, column=col).hyperlink = link
3645
+
3646
+ # !Apply data validation
3647
+ if data_validation:
3648
+ for indices, validation in data_validation.items():
3649
+ cell_range = convert_indices_to_range(*indices)
3650
+ dv = DataValidation(**validation)
3651
+ ws.add_data_validation(dv)
3652
+ dv.add(cell_range)
3653
+
3654
+ # !Protect sheet with a password
3655
+ if protect:
3656
+ ws.protection.password = protect.get("password", False)
3657
+ ws.protection.objects = protect.get("objects", False)
3658
+ ws.protection.sheet = protect.get("sheet", False)
3659
+ ws.protection.scenarios = protect.get("scenarios", False)
3660
+ ws.protection.formatCells = protect.get("formatCells", False)
3661
+ ws.protection.formatColumns = protect.get("formatColumns", False)
3662
+ ws.protection.formatRows = protect.get("formatRows", False)
3663
+ ws.protection.insertColumns = protect.get("insertColumns", False)
3664
+ ws.protection.insertRows = protect.get("insertRows", False)
3665
+ ws.protection.deleteColumns = protect.get("deleteColumns", False)
3666
+ ws.protection.deleteRows = protect.get("deleteRows", False)
3667
+
3668
+ # !conditional formatting
3669
+ if conditional_format:
3670
+ for indices, rules in conditional_format.items():
3671
+ cell_range = convert_indices_to_range(*indices)
3672
+ for rule in rules:
3673
+ if "color_scale" in rule:
3674
+ color_scale = rule["color_scale"]
3675
+ start_color = hex2argb(color_scale.get("start_color", "FFFFFF"))
3676
+ mid_color = hex2argb(color_scale.get("mid_color", "FFFFFF"))
3677
+ end_color = hex2argb(color_scale.get("end_color", "FFFFFF"))
3678
+
3679
+ color_scale_rule = ColorScaleRule(
3680
+ start_type=color_scale.get("start_type", "min"),
3681
+ start_value=color_scale.get("start_value"),
3682
+ start_color=start_color,
3683
+ mid_type=color_scale.get("mid_type"),
3684
+ mid_value=color_scale.get("mid_value"),
3685
+ mid_color=mid_color,
3686
+ end_type=color_scale.get("end_type", "max"),
3687
+ end_value=color_scale.get("end_value"),
3688
+ end_color=end_color,
3689
+ )
3690
+ ws.conditional_formatting.add(cell_range, color_scale_rule)
3691
+ # Save the workbook
3692
+ wb.save(filename)
3693
+ print(f"Formatted Excel file saved as:\n{filename}")