pyxllib 0.3.96__py3-none-any.whl → 0.3.197__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.
- pyxllib/algo/geo.py +12 -0
- pyxllib/algo/intervals.py +1 -1
- pyxllib/algo/matcher.py +78 -0
- pyxllib/algo/pupil.py +187 -19
- pyxllib/algo/specialist.py +2 -1
- pyxllib/algo/stat.py +38 -2
- {pyxlpr → pyxllib/autogui}/__init__.py +1 -1
- pyxllib/autogui/activewin.py +246 -0
- pyxllib/autogui/all.py +9 -0
- pyxllib/{ext/autogui → autogui}/autogui.py +40 -11
- pyxllib/autogui/uiautolib.py +362 -0
- pyxllib/autogui/wechat.py +827 -0
- pyxllib/autogui/wechat_msg.py +421 -0
- pyxllib/autogui/wxautolib.py +84 -0
- pyxllib/cv/slidercaptcha.py +137 -0
- pyxllib/data/echarts.py +123 -12
- pyxllib/data/jsonlib.py +89 -0
- pyxllib/data/pglib.py +514 -30
- pyxllib/data/sqlite.py +231 -4
- pyxllib/ext/JLineViewer.py +14 -1
- pyxllib/ext/drissionlib.py +277 -0
- pyxllib/ext/kq5034lib.py +0 -1594
- pyxllib/ext/robustprocfile.py +497 -0
- pyxllib/ext/unixlib.py +6 -5
- pyxllib/ext/utools.py +108 -95
- pyxllib/ext/webhook.py +32 -14
- pyxllib/ext/wjxlib.py +88 -0
- pyxllib/ext/wpsapi.py +124 -0
- pyxllib/ext/xlwork.py +9 -0
- pyxllib/ext/yuquelib.py +1003 -71
- pyxllib/file/docxlib.py +1 -1
- pyxllib/file/libreoffice.py +165 -0
- pyxllib/file/movielib.py +9 -0
- pyxllib/file/packlib/__init__.py +112 -75
- pyxllib/file/pdflib.py +1 -1
- pyxllib/file/pupil.py +1 -1
- pyxllib/file/specialist/dirlib.py +1 -1
- pyxllib/file/specialist/download.py +10 -3
- pyxllib/file/specialist/filelib.py +266 -55
- pyxllib/file/xlsxlib.py +205 -50
- pyxllib/file/xlsyncfile.py +341 -0
- pyxllib/prog/cachetools.py +64 -0
- pyxllib/prog/filelock.py +42 -0
- pyxllib/prog/multiprogs.py +940 -0
- pyxllib/prog/newbie.py +9 -2
- pyxllib/prog/pupil.py +129 -60
- pyxllib/prog/specialist/__init__.py +176 -2
- pyxllib/prog/specialist/bc.py +5 -2
- pyxllib/prog/specialist/browser.py +11 -2
- pyxllib/prog/specialist/datetime.py +68 -0
- pyxllib/prog/specialist/tictoc.py +12 -13
- pyxllib/prog/specialist/xllog.py +5 -5
- pyxllib/prog/xlosenv.py +7 -0
- pyxllib/text/airscript.js +744 -0
- pyxllib/text/charclasslib.py +17 -5
- pyxllib/text/jiebalib.py +6 -3
- pyxllib/text/jinjalib.py +32 -0
- pyxllib/text/jsa_ai_prompt.md +271 -0
- pyxllib/text/jscode.py +159 -4
- pyxllib/text/nestenv.py +1 -1
- pyxllib/text/newbie.py +12 -0
- pyxllib/text/pupil/common.py +26 -0
- pyxllib/text/specialist/ptag.py +2 -2
- pyxllib/text/templates/echart_base.html +11 -0
- pyxllib/text/templates/highlight_code.html +17 -0
- pyxllib/text/templates/latex_editor.html +103 -0
- pyxllib/text/xmllib.py +76 -14
- pyxllib/xl.py +2 -1
- pyxllib-0.3.197.dist-info/METADATA +48 -0
- pyxllib-0.3.197.dist-info/RECORD +126 -0
- {pyxllib-0.3.96.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +1 -2
- pyxllib/ext/autogui/__init__.py +0 -8
- pyxllib-0.3.96.dist-info/METADATA +0 -51
- pyxllib-0.3.96.dist-info/RECORD +0 -333
- pyxllib-0.3.96.dist-info/top_level.txt +0 -2
- pyxlpr/ai/__init__.py +0 -5
- pyxlpr/ai/clientlib.py +0 -1281
- pyxlpr/ai/specialist.py +0 -286
- pyxlpr/ai/torch_app.py +0 -172
- pyxlpr/ai/xlpaddle.py +0 -655
- pyxlpr/ai/xltorch.py +0 -705
- pyxlpr/data/__init__.py +0 -11
- pyxlpr/data/coco.py +0 -1325
- pyxlpr/data/datacls.py +0 -365
- pyxlpr/data/datasets.py +0 -200
- pyxlpr/data/gptlib.py +0 -1291
- pyxlpr/data/icdar/__init__.py +0 -96
- pyxlpr/data/icdar/deteval.py +0 -377
- pyxlpr/data/icdar/icdar2013.py +0 -341
- pyxlpr/data/icdar/iou.py +0 -340
- pyxlpr/data/icdar/rrc_evaluation_funcs_1_1.py +0 -463
- pyxlpr/data/imtextline.py +0 -473
- pyxlpr/data/labelme.py +0 -866
- pyxlpr/data/removeline.py +0 -179
- pyxlpr/data/specialist.py +0 -57
- pyxlpr/eval/__init__.py +0 -85
- pyxlpr/paddleocr.py +0 -776
- pyxlpr/ppocr/__init__.py +0 -15
- pyxlpr/ppocr/configs/rec/multi_language/generate_multi_language_configs.py +0 -226
- pyxlpr/ppocr/data/__init__.py +0 -135
- pyxlpr/ppocr/data/imaug/ColorJitter.py +0 -26
- pyxlpr/ppocr/data/imaug/__init__.py +0 -67
- pyxlpr/ppocr/data/imaug/copy_paste.py +0 -170
- pyxlpr/ppocr/data/imaug/east_process.py +0 -437
- pyxlpr/ppocr/data/imaug/gen_table_mask.py +0 -244
- pyxlpr/ppocr/data/imaug/iaa_augment.py +0 -114
- pyxlpr/ppocr/data/imaug/label_ops.py +0 -789
- pyxlpr/ppocr/data/imaug/make_border_map.py +0 -184
- pyxlpr/ppocr/data/imaug/make_pse_gt.py +0 -106
- pyxlpr/ppocr/data/imaug/make_shrink_map.py +0 -126
- pyxlpr/ppocr/data/imaug/operators.py +0 -433
- pyxlpr/ppocr/data/imaug/pg_process.py +0 -906
- pyxlpr/ppocr/data/imaug/randaugment.py +0 -143
- pyxlpr/ppocr/data/imaug/random_crop_data.py +0 -239
- pyxlpr/ppocr/data/imaug/rec_img_aug.py +0 -533
- pyxlpr/ppocr/data/imaug/sast_process.py +0 -777
- pyxlpr/ppocr/data/imaug/text_image_aug/__init__.py +0 -17
- pyxlpr/ppocr/data/imaug/text_image_aug/augment.py +0 -120
- pyxlpr/ppocr/data/imaug/text_image_aug/warp_mls.py +0 -168
- pyxlpr/ppocr/data/lmdb_dataset.py +0 -115
- pyxlpr/ppocr/data/pgnet_dataset.py +0 -104
- pyxlpr/ppocr/data/pubtab_dataset.py +0 -107
- pyxlpr/ppocr/data/simple_dataset.py +0 -372
- pyxlpr/ppocr/losses/__init__.py +0 -61
- pyxlpr/ppocr/losses/ace_loss.py +0 -52
- pyxlpr/ppocr/losses/basic_loss.py +0 -135
- pyxlpr/ppocr/losses/center_loss.py +0 -88
- pyxlpr/ppocr/losses/cls_loss.py +0 -30
- pyxlpr/ppocr/losses/combined_loss.py +0 -67
- pyxlpr/ppocr/losses/det_basic_loss.py +0 -208
- pyxlpr/ppocr/losses/det_db_loss.py +0 -80
- pyxlpr/ppocr/losses/det_east_loss.py +0 -63
- pyxlpr/ppocr/losses/det_pse_loss.py +0 -149
- pyxlpr/ppocr/losses/det_sast_loss.py +0 -121
- pyxlpr/ppocr/losses/distillation_loss.py +0 -272
- pyxlpr/ppocr/losses/e2e_pg_loss.py +0 -140
- pyxlpr/ppocr/losses/kie_sdmgr_loss.py +0 -113
- pyxlpr/ppocr/losses/rec_aster_loss.py +0 -99
- pyxlpr/ppocr/losses/rec_att_loss.py +0 -39
- pyxlpr/ppocr/losses/rec_ctc_loss.py +0 -44
- pyxlpr/ppocr/losses/rec_enhanced_ctc_loss.py +0 -70
- pyxlpr/ppocr/losses/rec_nrtr_loss.py +0 -30
- pyxlpr/ppocr/losses/rec_sar_loss.py +0 -28
- pyxlpr/ppocr/losses/rec_srn_loss.py +0 -47
- pyxlpr/ppocr/losses/table_att_loss.py +0 -109
- pyxlpr/ppocr/metrics/__init__.py +0 -44
- pyxlpr/ppocr/metrics/cls_metric.py +0 -45
- pyxlpr/ppocr/metrics/det_metric.py +0 -82
- pyxlpr/ppocr/metrics/distillation_metric.py +0 -73
- pyxlpr/ppocr/metrics/e2e_metric.py +0 -86
- pyxlpr/ppocr/metrics/eval_det_iou.py +0 -274
- pyxlpr/ppocr/metrics/kie_metric.py +0 -70
- pyxlpr/ppocr/metrics/rec_metric.py +0 -75
- pyxlpr/ppocr/metrics/table_metric.py +0 -50
- pyxlpr/ppocr/modeling/architectures/__init__.py +0 -32
- pyxlpr/ppocr/modeling/architectures/base_model.py +0 -88
- pyxlpr/ppocr/modeling/architectures/distillation_model.py +0 -60
- pyxlpr/ppocr/modeling/backbones/__init__.py +0 -54
- pyxlpr/ppocr/modeling/backbones/det_mobilenet_v3.py +0 -268
- pyxlpr/ppocr/modeling/backbones/det_resnet_vd.py +0 -246
- pyxlpr/ppocr/modeling/backbones/det_resnet_vd_sast.py +0 -285
- pyxlpr/ppocr/modeling/backbones/e2e_resnet_vd_pg.py +0 -265
- pyxlpr/ppocr/modeling/backbones/kie_unet_sdmgr.py +0 -186
- pyxlpr/ppocr/modeling/backbones/rec_mobilenet_v3.py +0 -138
- pyxlpr/ppocr/modeling/backbones/rec_mv1_enhance.py +0 -258
- pyxlpr/ppocr/modeling/backbones/rec_nrtr_mtb.py +0 -48
- pyxlpr/ppocr/modeling/backbones/rec_resnet_31.py +0 -210
- pyxlpr/ppocr/modeling/backbones/rec_resnet_aster.py +0 -143
- pyxlpr/ppocr/modeling/backbones/rec_resnet_fpn.py +0 -307
- pyxlpr/ppocr/modeling/backbones/rec_resnet_vd.py +0 -286
- pyxlpr/ppocr/modeling/heads/__init__.py +0 -54
- pyxlpr/ppocr/modeling/heads/cls_head.py +0 -52
- pyxlpr/ppocr/modeling/heads/det_db_head.py +0 -118
- pyxlpr/ppocr/modeling/heads/det_east_head.py +0 -121
- pyxlpr/ppocr/modeling/heads/det_pse_head.py +0 -37
- pyxlpr/ppocr/modeling/heads/det_sast_head.py +0 -128
- pyxlpr/ppocr/modeling/heads/e2e_pg_head.py +0 -253
- pyxlpr/ppocr/modeling/heads/kie_sdmgr_head.py +0 -206
- pyxlpr/ppocr/modeling/heads/multiheadAttention.py +0 -163
- pyxlpr/ppocr/modeling/heads/rec_aster_head.py +0 -393
- pyxlpr/ppocr/modeling/heads/rec_att_head.py +0 -202
- pyxlpr/ppocr/modeling/heads/rec_ctc_head.py +0 -88
- pyxlpr/ppocr/modeling/heads/rec_nrtr_head.py +0 -826
- pyxlpr/ppocr/modeling/heads/rec_sar_head.py +0 -402
- pyxlpr/ppocr/modeling/heads/rec_srn_head.py +0 -280
- pyxlpr/ppocr/modeling/heads/self_attention.py +0 -406
- pyxlpr/ppocr/modeling/heads/table_att_head.py +0 -246
- pyxlpr/ppocr/modeling/necks/__init__.py +0 -32
- pyxlpr/ppocr/modeling/necks/db_fpn.py +0 -111
- pyxlpr/ppocr/modeling/necks/east_fpn.py +0 -188
- pyxlpr/ppocr/modeling/necks/fpn.py +0 -138
- pyxlpr/ppocr/modeling/necks/pg_fpn.py +0 -314
- pyxlpr/ppocr/modeling/necks/rnn.py +0 -92
- pyxlpr/ppocr/modeling/necks/sast_fpn.py +0 -284
- pyxlpr/ppocr/modeling/necks/table_fpn.py +0 -110
- pyxlpr/ppocr/modeling/transforms/__init__.py +0 -28
- pyxlpr/ppocr/modeling/transforms/stn.py +0 -135
- pyxlpr/ppocr/modeling/transforms/tps.py +0 -308
- pyxlpr/ppocr/modeling/transforms/tps_spatial_transformer.py +0 -156
- pyxlpr/ppocr/optimizer/__init__.py +0 -61
- pyxlpr/ppocr/optimizer/learning_rate.py +0 -228
- pyxlpr/ppocr/optimizer/lr_scheduler.py +0 -49
- pyxlpr/ppocr/optimizer/optimizer.py +0 -160
- pyxlpr/ppocr/optimizer/regularizer.py +0 -52
- pyxlpr/ppocr/postprocess/__init__.py +0 -55
- pyxlpr/ppocr/postprocess/cls_postprocess.py +0 -33
- pyxlpr/ppocr/postprocess/db_postprocess.py +0 -234
- pyxlpr/ppocr/postprocess/east_postprocess.py +0 -143
- pyxlpr/ppocr/postprocess/locality_aware_nms.py +0 -200
- pyxlpr/ppocr/postprocess/pg_postprocess.py +0 -52
- pyxlpr/ppocr/postprocess/pse_postprocess/__init__.py +0 -15
- pyxlpr/ppocr/postprocess/pse_postprocess/pse/__init__.py +0 -29
- pyxlpr/ppocr/postprocess/pse_postprocess/pse/setup.py +0 -14
- pyxlpr/ppocr/postprocess/pse_postprocess/pse_postprocess.py +0 -118
- pyxlpr/ppocr/postprocess/rec_postprocess.py +0 -654
- pyxlpr/ppocr/postprocess/sast_postprocess.py +0 -355
- pyxlpr/ppocr/tools/__init__.py +0 -14
- pyxlpr/ppocr/tools/eval.py +0 -83
- pyxlpr/ppocr/tools/export_center.py +0 -77
- pyxlpr/ppocr/tools/export_model.py +0 -129
- pyxlpr/ppocr/tools/infer/predict_cls.py +0 -151
- pyxlpr/ppocr/tools/infer/predict_det.py +0 -300
- pyxlpr/ppocr/tools/infer/predict_e2e.py +0 -169
- pyxlpr/ppocr/tools/infer/predict_rec.py +0 -414
- pyxlpr/ppocr/tools/infer/predict_system.py +0 -204
- pyxlpr/ppocr/tools/infer/utility.py +0 -629
- pyxlpr/ppocr/tools/infer_cls.py +0 -83
- pyxlpr/ppocr/tools/infer_det.py +0 -134
- pyxlpr/ppocr/tools/infer_e2e.py +0 -122
- pyxlpr/ppocr/tools/infer_kie.py +0 -153
- pyxlpr/ppocr/tools/infer_rec.py +0 -146
- pyxlpr/ppocr/tools/infer_table.py +0 -107
- pyxlpr/ppocr/tools/program.py +0 -596
- pyxlpr/ppocr/tools/test_hubserving.py +0 -117
- pyxlpr/ppocr/tools/train.py +0 -163
- pyxlpr/ppocr/tools/xlprog.py +0 -748
- pyxlpr/ppocr/utils/EN_symbol_dict.txt +0 -94
- pyxlpr/ppocr/utils/__init__.py +0 -24
- pyxlpr/ppocr/utils/dict/ar_dict.txt +0 -117
- pyxlpr/ppocr/utils/dict/arabic_dict.txt +0 -162
- pyxlpr/ppocr/utils/dict/be_dict.txt +0 -145
- pyxlpr/ppocr/utils/dict/bg_dict.txt +0 -140
- pyxlpr/ppocr/utils/dict/chinese_cht_dict.txt +0 -8421
- pyxlpr/ppocr/utils/dict/cyrillic_dict.txt +0 -163
- pyxlpr/ppocr/utils/dict/devanagari_dict.txt +0 -167
- pyxlpr/ppocr/utils/dict/en_dict.txt +0 -63
- pyxlpr/ppocr/utils/dict/fa_dict.txt +0 -136
- pyxlpr/ppocr/utils/dict/french_dict.txt +0 -136
- pyxlpr/ppocr/utils/dict/german_dict.txt +0 -143
- pyxlpr/ppocr/utils/dict/hi_dict.txt +0 -162
- pyxlpr/ppocr/utils/dict/it_dict.txt +0 -118
- pyxlpr/ppocr/utils/dict/japan_dict.txt +0 -4399
- pyxlpr/ppocr/utils/dict/ka_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/korean_dict.txt +0 -3688
- pyxlpr/ppocr/utils/dict/latin_dict.txt +0 -185
- pyxlpr/ppocr/utils/dict/mr_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/ne_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/oc_dict.txt +0 -96
- pyxlpr/ppocr/utils/dict/pu_dict.txt +0 -130
- pyxlpr/ppocr/utils/dict/rs_dict.txt +0 -91
- pyxlpr/ppocr/utils/dict/rsc_dict.txt +0 -134
- pyxlpr/ppocr/utils/dict/ru_dict.txt +0 -125
- pyxlpr/ppocr/utils/dict/ta_dict.txt +0 -128
- pyxlpr/ppocr/utils/dict/table_dict.txt +0 -277
- pyxlpr/ppocr/utils/dict/table_structure_dict.txt +0 -2759
- pyxlpr/ppocr/utils/dict/te_dict.txt +0 -151
- pyxlpr/ppocr/utils/dict/ug_dict.txt +0 -114
- pyxlpr/ppocr/utils/dict/uk_dict.txt +0 -142
- pyxlpr/ppocr/utils/dict/ur_dict.txt +0 -137
- pyxlpr/ppocr/utils/dict/xi_dict.txt +0 -110
- pyxlpr/ppocr/utils/dict90.txt +0 -90
- pyxlpr/ppocr/utils/e2e_metric/Deteval.py +0 -574
- pyxlpr/ppocr/utils/e2e_metric/polygon_fast.py +0 -83
- pyxlpr/ppocr/utils/e2e_utils/extract_batchsize.py +0 -87
- pyxlpr/ppocr/utils/e2e_utils/extract_textpoint_fast.py +0 -457
- pyxlpr/ppocr/utils/e2e_utils/extract_textpoint_slow.py +0 -592
- pyxlpr/ppocr/utils/e2e_utils/pgnet_pp_utils.py +0 -162
- pyxlpr/ppocr/utils/e2e_utils/visual.py +0 -162
- pyxlpr/ppocr/utils/en_dict.txt +0 -95
- pyxlpr/ppocr/utils/gen_label.py +0 -81
- pyxlpr/ppocr/utils/ic15_dict.txt +0 -36
- pyxlpr/ppocr/utils/iou.py +0 -54
- pyxlpr/ppocr/utils/logging.py +0 -69
- pyxlpr/ppocr/utils/network.py +0 -84
- pyxlpr/ppocr/utils/ppocr_keys_v1.txt +0 -6623
- pyxlpr/ppocr/utils/profiler.py +0 -110
- pyxlpr/ppocr/utils/save_load.py +0 -150
- pyxlpr/ppocr/utils/stats.py +0 -72
- pyxlpr/ppocr/utils/utility.py +0 -80
- pyxlpr/ppstructure/__init__.py +0 -13
- pyxlpr/ppstructure/predict_system.py +0 -187
- pyxlpr/ppstructure/table/__init__.py +0 -13
- pyxlpr/ppstructure/table/eval_table.py +0 -72
- pyxlpr/ppstructure/table/matcher.py +0 -192
- pyxlpr/ppstructure/table/predict_structure.py +0 -136
- pyxlpr/ppstructure/table/predict_table.py +0 -221
- pyxlpr/ppstructure/table/table_metric/__init__.py +0 -16
- pyxlpr/ppstructure/table/table_metric/parallel.py +0 -51
- pyxlpr/ppstructure/table/table_metric/table_metric.py +0 -247
- pyxlpr/ppstructure/table/tablepyxl/__init__.py +0 -13
- pyxlpr/ppstructure/table/tablepyxl/style.py +0 -283
- pyxlpr/ppstructure/table/tablepyxl/tablepyxl.py +0 -118
- pyxlpr/ppstructure/utility.py +0 -71
- pyxlpr/xlai.py +0 -10
- /pyxllib/{ext/autogui → autogui}/virtualkey.py +0 -0
- {pyxllib-0.3.96.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,744 @@
|
|
1
|
+
// 我个人一般使用的启动函数
|
2
|
+
function __main__() {
|
3
|
+
// 1 这里填上api、智能匹配要支持的函数接口清单
|
4
|
+
const funcsMap = {
|
5
|
+
findCol, writeArrToSheet, locateTableRange, packTableDataFields,
|
6
|
+
}
|
7
|
+
|
8
|
+
// 2 api:py-jsa脚本令牌模式永远是最高匹配优先级
|
9
|
+
if (Context.argv.funcName) return funcsMap[Context.argv.funcName](...Context.argv.args)
|
10
|
+
|
11
|
+
// 3 自定义:有需要可以打开这部分代码,手动设计要执行的功能,并使用return结束不用进入第4部分
|
12
|
+
// return 自动填充考勤表日期()
|
13
|
+
|
14
|
+
// 4 智能匹配,优先级:可以在第1个字符串自定义要运行的功能 > 选中单元格指定函数名 > 选中单元格所在第1列是触发函数名
|
15
|
+
let funcName = '' || Selection.Cells(1, 1).Value2
|
16
|
+
if (!funcsMap[funcName]) funcName = ActiveSheet.Cells(Selection.Row, 1)
|
17
|
+
if (funcsMap[funcName]) return funcsMap[funcName]()
|
18
|
+
}
|
19
|
+
|
20
|
+
// 这段一般要手动放到代码的最后面
|
21
|
+
// const res = sanitizeForJSON(main())
|
22
|
+
// console.log(res)
|
23
|
+
// return res
|
24
|
+
|
25
|
+
|
26
|
+
function __1_算法工具() {
|
27
|
+
|
28
|
+
}
|
29
|
+
|
30
|
+
function levenshteinDistance(a, b) {
|
31
|
+
const matrix = []
|
32
|
+
|
33
|
+
let i
|
34
|
+
for (i = 0; i <= b.length; i++) matrix[i] = [i];
|
35
|
+
|
36
|
+
let j
|
37
|
+
for (j = 0; j <= a.length; j++) matrix[0][j] = j;
|
38
|
+
|
39
|
+
for (i = 1; i <= b.length; i++) {
|
40
|
+
for (j = 1; j <= a.length; j++) {
|
41
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
42
|
+
matrix[i][j] = matrix[i - 1][j - 1]
|
43
|
+
} else {
|
44
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1))
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
return matrix[b.length][a.length]
|
50
|
+
}
|
51
|
+
|
52
|
+
function levenshteinSimilarity(a, b) {
|
53
|
+
return (1 - levenshteinDistance(a, b) / Math.max(a.length, b.length)).toFixed(4)
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* @description 找到与目标字符串匹配度最高的前K个结果。
|
58
|
+
* @param {string} target 目标字符串。
|
59
|
+
* @param {Array|Function} candidates 候选集合,有以下三种格式:
|
60
|
+
* 1. 字符串数组,例如 ["abc", "def"]。
|
61
|
+
* 2. Excel范围对象,例如 Range("A1:A10"),会提取范围中的文本内容。
|
62
|
+
* 3. 格式化数组,例如 [ [obj1, str1], [obj2, str2], ... ],
|
63
|
+
* 其中 obj 为原始对象,str 为用于匹配的字符串。
|
64
|
+
* @param {number} k 返回的匹配结果数量。
|
65
|
+
* @returns {Array} 包含 [obj, text, sim] 的数组,表示对象、文本及匹配度。
|
66
|
+
*/
|
67
|
+
function findTopKMatches(target, candidates, k) {
|
68
|
+
let stdCands
|
69
|
+
if (Array.isArray(candidates)) {
|
70
|
+
stdCands = typeof candidates[0] === "string" ? candidates.map((s, i) => [i, s]) : candidates
|
71
|
+
} else if (typeof candidates === "function") {
|
72
|
+
stdCands = []
|
73
|
+
for (let i = 1; i <= candidates.Rows.Count; i++) {
|
74
|
+
const cel = candidates.Cells(i, 1)
|
75
|
+
stdCands.push([cel, cel.Text])
|
76
|
+
}
|
77
|
+
} else {
|
78
|
+
throw new Error("Unsupported format.")
|
79
|
+
}
|
80
|
+
|
81
|
+
const results = stdCands
|
82
|
+
.map(([obj, str]) => [obj, str, parseFloat(levenshteinSimilarity(target, str))])
|
83
|
+
.sort((a, b) => b[2] - a[2])
|
84
|
+
return k ? results.slice(0, k) : results
|
85
|
+
}
|
86
|
+
|
87
|
+
function __2_定位工具() {
|
88
|
+
|
89
|
+
}
|
90
|
+
|
91
|
+
/**
|
92
|
+
* Find的参数很多:https://airsheet.wps.cn/docs/apiV2/excel/workbook/Range/%E6%96%B9%E6%B3%95/Find%20%E6%96%B9%E6%B3%95.html
|
93
|
+
* 但个人感觉比较可能需要配置到的就lookAt,如果有其他特殊定位需求,可以自己使用类似原理.Find找到行列就好
|
94
|
+
* @param what 要查找的内容
|
95
|
+
* @param ur 查找区域,默认当前表格UsedRange
|
96
|
+
* @param lookAt 可以选xlWhole(单元格内容=what)或xlPart(单元格内容包含了what)
|
97
|
+
* @return 找到的单元格
|
98
|
+
* todo 还没思考如果匹配情况不唯一怎么处理,目前都是返回第1个匹配项。因为这我自己重名场景不多,真遇到也可以手动约束ur范围后适当解决该问题。
|
99
|
+
* todo 支持多行表头的嵌套定位?比如料理一级标题下的二级标题合计定位方式:['料理', '合计'],可以区别于另一个"合计": ['酒水', '合计']
|
100
|
+
*/
|
101
|
+
function findCel(what, ur = ActiveSheet.UsedRange, lookAt = xlWhole) {
|
102
|
+
if (typeof ur === 'string') ur = ur.includes(':') ? Range(ur) : Sheets(ur).UsedRange
|
103
|
+
return ur.Find(what, undefined, undefined, lookAt)
|
104
|
+
}
|
105
|
+
|
106
|
+
function findRow(what, ur = ActiveSheet.UsedRange, lookAt = xlWhole) {
|
107
|
+
const cel = findCel(what, ur, lookAt)
|
108
|
+
if (cel) return cel.Row
|
109
|
+
}
|
110
|
+
|
111
|
+
function findCol(what, ur = ActiveSheet.UsedRange, lookAt = xlWhole) {
|
112
|
+
let cel = findCel(what, ur, lookAt)
|
113
|
+
if (cel) return cel.Column
|
114
|
+
}
|
115
|
+
|
116
|
+
// 判断 cells 集合是否全空
|
117
|
+
function isEmpty(cels) {
|
118
|
+
for (let i = 1; i <= cels.Count; i++) if (cels.Item(i).Text) return false
|
119
|
+
return true
|
120
|
+
}
|
121
|
+
|
122
|
+
// 获取ws实际使用的区域:会裁剪掉四周没有数据的空白区域(之前被使用过的区域或设置过格式等操作,默认ur会得到空白区域干扰数据范围定位)
|
123
|
+
function getUsedRange(ws = ActiveSheet) {
|
124
|
+
// 1 定位默认的UsedRange
|
125
|
+
if (typeof ws === 'string') ws = Sheets(ws)
|
126
|
+
let ur = ws.UsedRange
|
127
|
+
let firstRow = 1, firstCol = 1, lastRow = ur.Rows.Count, lastCol = ur.Columns.Count
|
128
|
+
|
129
|
+
// 2 裁剪四周
|
130
|
+
// todo 待官方支持TRIMRANGE后可能有更简洁的解决方案。期望官方底层不是这样暴力检索,应该有更高效的解决方式
|
131
|
+
|
132
|
+
// 找到最后一个非空行
|
133
|
+
for (; lastRow >= firstRow; lastRow--) if (!isEmpty(ur.Rows(lastRow).Cells)) break
|
134
|
+
// 最后一个非空列
|
135
|
+
for (; lastCol >= firstCol; lastCol--) if (!isEmpty(ur.Columns(lastCol).Cells)) break
|
136
|
+
// 第一个非空行
|
137
|
+
for (; firstRow <= lastRow; firstRow++) if (!isEmpty(ur.Rows(firstRow).Cells)) break
|
138
|
+
// 第一个非空列
|
139
|
+
for (; firstCol <= lastCol; firstCol++) if (!isEmpty(ur.Columns(firstCol).Cells)) break
|
140
|
+
|
141
|
+
// 3 创建一个新的 Range 对象,它只包含非空的行和列
|
142
|
+
return ws.Range(ur.Cells(firstRow, firstCol), ur.Cells(lastRow, lastCol))
|
143
|
+
}
|
144
|
+
|
145
|
+
/**
|
146
|
+
* 表格结构化定位工具
|
147
|
+
* @param ws 输入表格名,或表格对象
|
148
|
+
* @param dataRow 输入两个值的数组,第1个值标记(不含表头的)数据起始行,第2个值标记数据结束行。
|
149
|
+
* 只输入单数值,未传入第2个参数时,默认以0填充,例如:4 -> [4, 0]
|
150
|
+
* 起始行标记:
|
151
|
+
* 0,智能检测。如果cols有给入字段名,以找到的第1个字段的下一行作为起始行。否则默认设置为ur的第2行。
|
152
|
+
* 正整数,人工精确指定数据起始行(输入的是整张表格的绝对行号)
|
153
|
+
* '料理'等精确的字段名标记,以找到的单元格下一行作为数据起始行
|
154
|
+
* 负数,比如-2,表示基于第2列(B列),使用.End(xlDown)机制找到第1条有数据的行的下一行作为数据起始行
|
155
|
+
* 结束行标记:
|
156
|
+
* 0,智能检测。以getUsedRange的最后一行为准。
|
157
|
+
* 正整数,人工精确指定数据结束行(有时候数据实际可能有100行,可以只写10,实现少量部分样本的功能测试)
|
158
|
+
* '料理'等精确的字段名标记,同负数模式,以找到的所在列,配合.End(xlUp)确定最后一行有数据的位置
|
159
|
+
* 负数,比如-3,表示基于第3列(C列),使用.End(xlUp)对这列的最后一行数据位置做判定,作为数据最后一行的标记
|
160
|
+
* @param fields 后续要使用到的相关字段数据,使用as2.0版本的时候,该参数可以不输入,会在使用中动态检索
|
161
|
+
* @return [ur, rows, cols]
|
162
|
+
* ur,表格实际的UsedRange
|
163
|
+
* rows是字典,rows.start、rows.end分别存储了数据的起止行
|
164
|
+
* cols也是字典,存储了个字段名对应的所在列编号,比如cols['料理']
|
165
|
+
* 注:返回的行、列,都是相对ur的位置,所以可以类似这样 ur.Cells(rows.start, cols[x]) 取到第1条数据在x字段的值
|
166
|
+
*/
|
167
|
+
function as1_locateTableRange(ws, dataRow = [0, 0], fields = []) {
|
168
|
+
// 1 初步确定数据区域getUsedRange范围
|
169
|
+
const ur = getUsedRange(ws)
|
170
|
+
ws = ur.Worksheet
|
171
|
+
// dataRow可以输入单个数值
|
172
|
+
if (typeof dataRow === 'number') dataRow = [dataRow, 0]
|
173
|
+
let rows = {
|
174
|
+
start: dataRow[0] === 0 ? ur.Row + 1 : dataRow[0],
|
175
|
+
end: dataRow[1] === 0 ? ur.Row + ur.Rows.Count - 1 : dataRow[1]
|
176
|
+
}
|
177
|
+
|
178
|
+
// 2 获取列名对应的列号
|
179
|
+
let cols = {}
|
180
|
+
fields.forEach(colName => {
|
181
|
+
const col = findCol(colName, ur)
|
182
|
+
if (col) {
|
183
|
+
cols[colName] = col
|
184
|
+
// 如果此时rows.start还未确定,则以该单元格的下一行作为数据起始行
|
185
|
+
if (rows.start === 0) rows.start = findRow(colName, ur) + 1 || 0 // 有可能会找不到,则保持0
|
186
|
+
}
|
187
|
+
})
|
188
|
+
|
189
|
+
// 3 定位行号
|
190
|
+
if (typeof rows.start === 'string') rows.start = findRow(rows.start, ur) + 1
|
191
|
+
if (rows.start < 0) {
|
192
|
+
const col = -rows.start
|
193
|
+
rows.start = 2
|
194
|
+
if (isEmpty(ws.Cells(1, col))) rows.start = ws.Cells(1, col).End(xlDown).Row + 1
|
195
|
+
}
|
196
|
+
|
197
|
+
if (typeof rows.end === 'string') rows.end = -findCol(rows.end, ur)
|
198
|
+
if (rows.end < 0) {
|
199
|
+
const cel = ws.Cells(ws.Rows.Count, -rows.end)
|
200
|
+
rows.end = cel.Row
|
201
|
+
if (isEmpty(cel)) rows.end = cel.End(xlUp).Row
|
202
|
+
}
|
203
|
+
|
204
|
+
// 4 转成ur里的相对行号
|
205
|
+
rows.start -= ur.Row - 1
|
206
|
+
rows.end -= ur.Row - 1
|
207
|
+
for (const colName in cols) cols[colName] -= ur.Column - 1
|
208
|
+
|
209
|
+
return [ur, rows, cols]
|
210
|
+
}
|
211
|
+
|
212
|
+
|
213
|
+
function locateTableRange(ws, dataRow = [0, 0], fields = []) {
|
214
|
+
// 1 先获得基础版本的结果
|
215
|
+
let [ur, rows, cols] = as1_locateTableRange(ws, dataRow, fields)
|
216
|
+
|
217
|
+
// 2 使用 Proxy 实现动态查找未配置的字段(该功能仅AirScript2.0可用,1.0请使用as1_locateTableRange接口)
|
218
|
+
cols = new Proxy(cols, {
|
219
|
+
get(target, prop) {
|
220
|
+
if (prop in target) {
|
221
|
+
return target[prop] // 已配置字段,直接返回
|
222
|
+
} else {
|
223
|
+
const dynamicCol = findCol(prop, ur) // 动态查找
|
224
|
+
if (dynamicCol) {
|
225
|
+
target[prop] = dynamicCol // 缓存动态找到的列
|
226
|
+
return dynamicCol
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
230
|
+
})
|
231
|
+
|
232
|
+
// cols支持在使用中动态自增字段
|
233
|
+
return [ur, rows, cols]
|
234
|
+
}
|
235
|
+
|
236
|
+
/**
|
237
|
+
* 表格结构化定位工具的增强版本,在locateTableRange基础上增加了tools简化一些常用操作
|
238
|
+
* tools增加的工具详见内部实现的子函数注释
|
239
|
+
* todo 250109周四14:07 这套实现并不太好,过渡封装了,后续还是研究下怎么做出ur.Cells我感觉更好。
|
240
|
+
*/
|
241
|
+
function locateTableRange2(ws, dataRow = [0, 0], fields = []) {
|
242
|
+
let [ur, rows, cols] = locateTableRange(ws, dataRow, fields)
|
243
|
+
|
244
|
+
class TableTools {
|
245
|
+
constructor(ur, rows, cols) {
|
246
|
+
this.ur = ur
|
247
|
+
this.rows = rows
|
248
|
+
this.cols = cols
|
249
|
+
}
|
250
|
+
|
251
|
+
getcel(row, colName) {
|
252
|
+
return this.ur.Cells(row, this.cols[colName])
|
253
|
+
}
|
254
|
+
|
255
|
+
/**
|
256
|
+
* 获取指定行和列名的单元格值
|
257
|
+
* @param {number} row 行号
|
258
|
+
* @param {string} colName 列名
|
259
|
+
* @return {any} 单元格的Value2值
|
260
|
+
*/
|
261
|
+
getval(row, colName) {
|
262
|
+
return this.ur.Cells(row, this.cols[colName]).Value2
|
263
|
+
}
|
264
|
+
|
265
|
+
gettext(row, colName) {
|
266
|
+
return this.ur.Cells(row, this.cols[colName]).Text
|
267
|
+
}
|
268
|
+
|
269
|
+
/**
|
270
|
+
* 查找参数名对应的单元格
|
271
|
+
* @param {string} argName 参数名
|
272
|
+
* @param {string} direction 查找方向,'down' 表示下方,'right' 表示右侧,默认为 'down'
|
273
|
+
* @return {any} 单元格对象
|
274
|
+
*/
|
275
|
+
findargcel(argName, direction = 'down') {
|
276
|
+
const cel = findCel(argName, this.ur)
|
277
|
+
if (!cel) {
|
278
|
+
// 如果未找到参数名,返回 undefined
|
279
|
+
return undefined
|
280
|
+
}
|
281
|
+
|
282
|
+
let targetCell
|
283
|
+
if (direction === 'down') {
|
284
|
+
// 查找下方单元格
|
285
|
+
targetCell = cel.Offset(1, 0)
|
286
|
+
} else if (direction === 'right') {
|
287
|
+
// 查找右侧单元格
|
288
|
+
targetCell = cel.Offset(0, 1)
|
289
|
+
} else {
|
290
|
+
// 如果方向不正确,抛出错误
|
291
|
+
throw new Error(`未知的方向参数: ${direction}`)
|
292
|
+
}
|
293
|
+
|
294
|
+
// 返回目标单元格的值
|
295
|
+
return targetCell
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
let tools = new TableTools(ur, rows, cols)
|
300
|
+
return [ur, rows, cols, tools]
|
301
|
+
}
|
302
|
+
|
303
|
+
|
304
|
+
function __3_json数据导入导出() {
|
305
|
+
|
306
|
+
}
|
307
|
+
|
308
|
+
/**
|
309
|
+
* 打包sheet下多个字段fields的数据
|
310
|
+
* @param ws 表格名或表格对象
|
311
|
+
* @param fields 要打包的字段名或列号,支持字段名称或整数,明确指定某列的位置
|
312
|
+
* @param dataRow 数据起始行,默认[0, 0]
|
313
|
+
* @param filterEmptyRows 是否过滤空行,默认true
|
314
|
+
* @param useTextFormat 是否根据单元格格式返回Text格式,默认true
|
315
|
+
* @return {'名称': [x1, x2, ...], '标签': [y1, y2, ...]}
|
316
|
+
*/
|
317
|
+
// 使用示例:packTableDataFields('料理', ['名称', '标签']
|
318
|
+
// todo fields能否不输入,默认获取所有字段数据(此时需要给出表头所在行)
|
319
|
+
// todo 多级表头类的数据怎么处理?
|
320
|
+
// todo 支持一定的筛选功能?避免表格太大时要传输的数据过多。
|
321
|
+
function packTableDataFields(ws, fields, dataRow = [0, 0], filterEmptyRows = true, useTextFormat = true) {
|
322
|
+
// 1 确定数据范围和字段列号映射
|
323
|
+
const [ur, rows, cols] = locateTableRange(ws, dataRow, fields)
|
324
|
+
|
325
|
+
// 2 初始化字段数据和格式映射
|
326
|
+
const fieldsData = fields.reduce((dataMap, field) => {
|
327
|
+
dataMap[field] = []
|
328
|
+
return dataMap
|
329
|
+
}, {})
|
330
|
+
|
331
|
+
const formatMap = {}
|
332
|
+
if (useTextFormat) {
|
333
|
+
Object.entries(cols).forEach(([field, col]) => {
|
334
|
+
let firstCell = ur.Cells(rows.start, col)
|
335
|
+
let format = firstCell.NumberFormat
|
336
|
+
formatMap[field] = format !== 'G/通用格式' ? 'Text' : 'Value2'
|
337
|
+
})
|
338
|
+
}
|
339
|
+
|
340
|
+
// 3 遍历数据行填充字段数据
|
341
|
+
for (let row = rows.start; row <= rows.end; row++) {
|
342
|
+
if (filterEmptyRows) {
|
343
|
+
const isEmptyRow = Object.values(cols).every(col => ur.Cells(row, col).Value2 === undefined)
|
344
|
+
if (isEmptyRow) continue; // 跳过空行
|
345
|
+
}
|
346
|
+
|
347
|
+
// 填充每个字段的数据
|
348
|
+
Object.entries(cols).forEach(([field, col]) => {
|
349
|
+
let cell = ur.Cells(row, col)
|
350
|
+
if (useTextFormat) {
|
351
|
+
fieldsData[field].push(formatMap[field] === 'Text' ? cell.Text : cell.Value2)
|
352
|
+
} else {
|
353
|
+
fieldsData[field].push(cell.Value2)
|
354
|
+
}
|
355
|
+
})
|
356
|
+
}
|
357
|
+
|
358
|
+
// 4 返回结果
|
359
|
+
return fieldsData
|
360
|
+
}
|
361
|
+
|
362
|
+
// 和packTableDataFields仅差在返回的数据格式上,这个版本的返回值是主流的jsonl格式
|
363
|
+
// 返回格式:list[dict], [{'名称': x1, '标签': y1}, {'名称': x2, '标签': y2}, ...]
|
364
|
+
function packTableDataList(ws, fields, dataRow, filterEmptyRows = true) {
|
365
|
+
const fieldsData = packTableDataFields(ws, fields, dataRow, filterEmptyRows)
|
366
|
+
const rowCount = fieldsData[fields[0]].length
|
367
|
+
const listData = []
|
368
|
+
|
369
|
+
// 将紧凑格式转换为列表字典格式
|
370
|
+
for (let i = 0; i < rowCount; i++) {
|
371
|
+
const rowDict = {}
|
372
|
+
fields.forEach(field => {
|
373
|
+
rowDict[field] = fieldsData[field][i]
|
374
|
+
})
|
375
|
+
listData.push(rowDict)
|
376
|
+
}
|
377
|
+
return listData
|
378
|
+
}
|
379
|
+
|
380
|
+
function clearSheetData(headerRow = 1, dataStartRow = 2, ws = ActiveSheet) {
|
381
|
+
let headerStartRow, headerEndRow, dataEndRow
|
382
|
+
|
383
|
+
// 检查 headerRow 参数,-1 表示不处理表头
|
384
|
+
if (headerRow === -1) {
|
385
|
+
headerStartRow = headerEndRow = null
|
386
|
+
} else if (typeof headerRow === 'number') {
|
387
|
+
headerStartRow = headerEndRow = headerRow
|
388
|
+
} else if (Array.isArray(header)) {
|
389
|
+
[headerStartRow, headerEndRow] = headerRow
|
390
|
+
}
|
391
|
+
|
392
|
+
// 检查 dataStartRow 参数,-1 表示不处理数据区域
|
393
|
+
if (dataStartRow === -1) {
|
394
|
+
dataStartRow = dataEndRow = null
|
395
|
+
} else if (typeof dataStartRow === 'number') {
|
396
|
+
let usedRange = ws.UsedRange
|
397
|
+
dataEndRow = usedRange.Row + usedRange.Rows.Count - 1
|
398
|
+
} else if (Array.isArray(dataStartRow)) {
|
399
|
+
[dataStartRow, dataEndRow] = dataStartRow
|
400
|
+
}
|
401
|
+
|
402
|
+
// 清空表头区域(保留格式),若未设置为 -1
|
403
|
+
if (headerStartRow !== null && headerEndRow !== null) {
|
404
|
+
ws.Rows(headerStartRow + ':' + headerEndRow).ClearContents()
|
405
|
+
}
|
406
|
+
|
407
|
+
// 删除数据区域(不保留格式),若未设置为 -1
|
408
|
+
if (dataStartRow !== null && dataEndRow !== null) {
|
409
|
+
ws.Rows(dataStartRow + ':' + dataEndRow).Clear()
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
// 将py里df.to_dict(orient='split')的数据格式写入ws
|
414
|
+
// 这个数据一般有3个属性:index, columns, data
|
415
|
+
function writeDfSplitDictToSheet(jsonData, headerRow = 1, dataStartRow = 2, ws = ActiveSheet) {
|
416
|
+
let columns = jsonData.columns || []
|
417
|
+
let data = jsonData.data || []
|
418
|
+
|
419
|
+
// 若存在 index,则将其添加为第一列
|
420
|
+
if (jsonData.index) {
|
421
|
+
columns = ['index', ...columns]
|
422
|
+
data = jsonData.index.map((idx, i) => [idx, ...data[i]])
|
423
|
+
}
|
424
|
+
|
425
|
+
const startCol = ws.UsedRange.Column
|
426
|
+
|
427
|
+
// 写入表头
|
428
|
+
if (headerRow > 0) {
|
429
|
+
for (let j = 0; j < columns.length; j++) {
|
430
|
+
ws.Cells(headerRow, startCol + j).Value2 = columns[j]
|
431
|
+
}
|
432
|
+
}
|
433
|
+
|
434
|
+
// 写入数据内容
|
435
|
+
for (let i = 0; i < data.length; i++) {
|
436
|
+
for (let j = 0; j < data[i].length; j++) {
|
437
|
+
ws.Cells(dataStartRow + i, startCol + j).Value2 = data[i][j]
|
438
|
+
}
|
439
|
+
}
|
440
|
+
}
|
441
|
+
|
442
|
+
|
443
|
+
function writeArrToSheet(arr, startCel) {
|
444
|
+
// 1 startCel可以输入字符串,且注意这样是可以附带表格位置信息的 'Sheet1!A1'
|
445
|
+
// 如果是字符串,转Range对象
|
446
|
+
if (typeof startCel === 'string') startCel = Range(startCel)
|
447
|
+
|
448
|
+
// 2 遍历数组,将每行的数据写入 Excel
|
449
|
+
for (let i = 0; i < arr.length; i++) {
|
450
|
+
const row = arr[i]
|
451
|
+
// 如果当前行存在,则遍历该行的元素
|
452
|
+
if (Array.isArray(row)) {
|
453
|
+
for (let j = 0; j < row.length; j++) {
|
454
|
+
startCel.Offset(i, j).Value2 = row[j]
|
455
|
+
}
|
456
|
+
}
|
457
|
+
}
|
458
|
+
}
|
459
|
+
|
460
|
+
|
461
|
+
/**
|
462
|
+
* 插入新行并复制格式,兼容jsa1.0和2.0,并可选择格式复制方向
|
463
|
+
* @param {number} dataStartRow - 数据起始行
|
464
|
+
* @param {number} insertCount - 需要插入的行数
|
465
|
+
* @param {string} direction - 复制格式的方向,支持 'xlUp' 或 'xlDown'
|
466
|
+
* @param {object} ws - 工作表对象,默认为ActiveSheet
|
467
|
+
*/
|
468
|
+
function insertRowsWithFormat(dataStartRow, insertCount, ws = ActiveSheet, direction = 'xlUp') {
|
469
|
+
if (insertCount <= 0) return
|
470
|
+
const insertRange = `${dataStartRow}:${dataStartRow + insertCount - 1}`
|
471
|
+
|
472
|
+
if (ws.Rows.RowEnd) { // jsa1.0
|
473
|
+
ws.Rows(insertRange).Insert()
|
474
|
+
ws.Rows(insertRange).ClearContents() // 1.0有可能会出现插入的不是空行,还顺带拷贝了数据~
|
475
|
+
if (direction === 'xlUp') {
|
476
|
+
ws.Rows(dataStartRow + insertCount).Copy()
|
477
|
+
ws.Rows(insertRange).PasteSpecial(xlPasteFormats)
|
478
|
+
}
|
479
|
+
} else {
|
480
|
+
// 2.0的insert才能传参。1.0或默认不传参相当于是xlDown的效果,指新插入的行是拷贝的上面一行的格式。
|
481
|
+
ws.Rows(insertRange).Insert(direction)
|
482
|
+
}
|
483
|
+
}
|
484
|
+
|
485
|
+
|
486
|
+
function insertNewDataWithHeaders(jsonData, headerRow = 1, dataStartRow = 2, ws = ActiveSheet) {
|
487
|
+
// 1 预处理 index,将其合并到 columns 和 data
|
488
|
+
let columns = jsonData.columns || []
|
489
|
+
let data = jsonData.data || []
|
490
|
+
if (jsonData.index) {
|
491
|
+
columns = ['index', ...columns]
|
492
|
+
data = jsonData.index.map((idx, i) => [idx, ...data[i]])
|
493
|
+
}
|
494
|
+
|
495
|
+
// 2 处理可能出现的新字段
|
496
|
+
// 获取现有的表头
|
497
|
+
let existingHeaders = []
|
498
|
+
const usedRange = ws.UsedRange;
|
499
|
+
for (let col = usedRange.Column; col <= usedRange.Column + usedRange.Columns.Count - 1; col++) {
|
500
|
+
existingHeaders.push(ws.Cells(headerRow, col).Value2)
|
501
|
+
}
|
502
|
+
|
503
|
+
// 计算新增的字段
|
504
|
+
const newHeaders = columns.filter(column => !existingHeaders.includes(column))
|
505
|
+
const allHeaders = [...existingHeaders, ...newHeaders]
|
506
|
+
|
507
|
+
// 如果有新字段,扩展表头
|
508
|
+
if (newHeaders.length > 0) {
|
509
|
+
for (let j = 0; j < allHeaders.length; j++) {
|
510
|
+
ws.Cells(headerRow, usedRange.Column + j).Value2 = allHeaders[j]
|
511
|
+
}
|
512
|
+
}
|
513
|
+
|
514
|
+
// 构建插入数据的映射关系
|
515
|
+
const headerIndexMap = {}
|
516
|
+
for (let j = 0; j < allHeaders.length; j++) {
|
517
|
+
headerIndexMap[allHeaders[j]] = usedRange.Column + j
|
518
|
+
}
|
519
|
+
|
520
|
+
// 3 插入新行
|
521
|
+
insertRowsWithFormat(dataStartRow, data.length, ws)
|
522
|
+
for (let i = 0; i < data.length; i++) {
|
523
|
+
const rowData = data[i]
|
524
|
+
for (let j = 0; j < columns.length; j++) {
|
525
|
+
const colName = columns[j]
|
526
|
+
const colIdx = headerIndexMap[colName]
|
527
|
+
ws.Cells(dataStartRow + i, colIdx).Value2 = rowData[j]
|
528
|
+
}
|
529
|
+
}
|
530
|
+
}
|
531
|
+
|
532
|
+
function __4_py服务工具() {
|
533
|
+
|
534
|
+
}
|
535
|
+
|
536
|
+
// 服务器路径,url
|
537
|
+
const JSA_POST_HOST_URL = '{{JSA_POST_HOST_URL}}'
|
538
|
+
// 要处理的目标主机
|
539
|
+
const JSA_POST_DEFAULT_HOST = '{{JSA_POST_DEFAULT_HOST}}'
|
540
|
+
// 请求的header格式,以及对应的token
|
541
|
+
const JSA_HTTP_HEADERS = {
|
542
|
+
'Authorization': 'Bearer {{JSA_POST_TOKEN}}', 'Content-Type': 'application/json'
|
543
|
+
}
|
544
|
+
|
545
|
+
// 保留环境状态,运行短小任务,返回代码中print输出的内容
|
546
|
+
function runPyScript(script, query = '', host = JSA_POST_DEFAULT_HOST) {
|
547
|
+
const url = `${JSA_POST_HOST_URL}/${host}/common/run_py`
|
548
|
+
const resp = HTTP.post(url, {query, script}, {headers: JSA_HTTP_HEADERS})
|
549
|
+
return resp.json().output
|
550
|
+
}
|
551
|
+
|
552
|
+
// 每次都是独立环境状态,运行较长时间任务,返回代码中return的字典数据
|
553
|
+
function runIsolatedPyScript(script, host = JSA_POST_DEFAULT_HOST) {
|
554
|
+
const url = `${JSA_POST_HOST_URL}/${host}/common/run_isolated_py`
|
555
|
+
// 判断 script 的类型: script可以只输入py代码,也可以输入配置好的整个字典数据
|
556
|
+
const payload = typeof script === 'string' ? {script} : script
|
557
|
+
const resp = HTTP.post(url, payload, {headers: JSA_HTTP_HEADERS})
|
558
|
+
return resp.json()
|
559
|
+
}
|
560
|
+
|
561
|
+
function getPyTaskResult(taskId, retries = 1, host = JSA_POST_DEFAULT_HOST, delay = 5000) {
|
562
|
+
const url = `${JSA_POST_HOST_URL}/${host}/common/get_task_result/${taskId}`
|
563
|
+
for (let attempt = 0; attempt < retries; attempt++) {
|
564
|
+
const resp = HTTP.get(url, {headers: JSA_HTTP_HEADERS})
|
565
|
+
const jsonResp = resp.json()
|
566
|
+
if ('__get_task_result_error__' in jsonResp) {
|
567
|
+
console.log(`第 ${attempt + 1} 次获取任务结果失败,请稍后再试...`)
|
568
|
+
if (attempt < retries - 1) {
|
569
|
+
timeSleep(delay)
|
570
|
+
}
|
571
|
+
} else {
|
572
|
+
return jsonResp
|
573
|
+
}
|
574
|
+
}
|
575
|
+
}
|
576
|
+
|
577
|
+
function __5_日期处理() {
|
578
|
+
/*
|
579
|
+
文档:https://www.yuque.com/xlpr/pyxllib/zdgppdtls3a15nhg
|
580
|
+
*/
|
581
|
+
|
582
|
+
// 输入utc时间点
|
583
|
+
console.log(new Date("2024-11-20"))
|
584
|
+
const d = new Date(); // 当前时间
|
585
|
+
console.log(d)
|
586
|
+
|
587
|
+
// 输入本地时间
|
588
|
+
console.log(new Date("2024/11/20"))
|
589
|
+
|
590
|
+
// 看到的是本地时间的"小时"
|
591
|
+
console.log(d.getHours())
|
592
|
+
}
|
593
|
+
|
594
|
+
// 将Excel日期时间戳转为JS日期, adjustTimezone是是否默认按照本地时间处理
|
595
|
+
function excelDateToJSDate(excelDate, adjustTimezone = true) {
|
596
|
+
const excelEpochStart = new Date(Date.UTC(1899, 11, 30))
|
597
|
+
const jsDate = new Date(excelEpochStart.getTime() + excelDate * 86400000)
|
598
|
+
if (adjustTimezone) {
|
599
|
+
const timezoneOffset = jsDate.getTimezoneOffset() * 60000
|
600
|
+
return new Date(jsDate.getTime() + timezoneOffset)
|
601
|
+
}
|
602
|
+
return jsDate
|
603
|
+
}
|
604
|
+
|
605
|
+
// 将JS日期转为excel时间戳
|
606
|
+
function jsDateToExcelDate(jsDate = new Date(), adjustTimezone = true) {
|
607
|
+
let adjustedDate = jsDate
|
608
|
+
if (adjustTimezone) {
|
609
|
+
const timezoneOffset = jsDate.getTimezoneOffset() * 60000
|
610
|
+
adjustedDate = new Date(jsDate.getTime() - timezoneOffset)
|
611
|
+
}
|
612
|
+
const excelEpochStart = new Date(Date.UTC(1899, 11, 30))
|
613
|
+
const diffInMs = adjustedDate.getTime() - excelEpochStart.getTime()
|
614
|
+
return diffInMs / 86400000
|
615
|
+
}
|
616
|
+
|
617
|
+
// 判断日期是否在本周
|
618
|
+
function isCurrentWeek(date) {
|
619
|
+
const today = new Date()
|
620
|
+
today.setHours(0, 0, 0, 0); // 把时间设为午夜以准确地比较日期
|
621
|
+
const firstDayOfWeek = new Date(today.setDate(today.getDate() - today.getDay()))
|
622
|
+
const lastDayOfWeek = new Date(today.setDate(today.getDate() - today.getDay() + 6))
|
623
|
+
return date >= firstDayOfWeek && date <= lastDayOfWeek
|
624
|
+
}
|
625
|
+
|
626
|
+
// 判断日期是否在当前月份
|
627
|
+
function isCurrentMonth(date) {
|
628
|
+
const currentDate = new Date()
|
629
|
+
currentDate.setHours(0, 0, 0, 0) // 把时间设为午夜stdcode以准确地比较日期
|
630
|
+
return date.getMonth() === currentDate.getMonth() && date.getFullYear() === currentDate.getFullYear()
|
631
|
+
}
|
632
|
+
|
633
|
+
// 判断日期是否在下周
|
634
|
+
function isNextWeek(date) {
|
635
|
+
const today = new Date()
|
636
|
+
today.setHours(0, 0, 0, 0) // 把时间设为午夜以准确地比较日期
|
637
|
+
const nextWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7)
|
638
|
+
return date > today && date <= nextWeek
|
639
|
+
}
|
640
|
+
|
641
|
+
// 判断日期是否在下个月
|
642
|
+
function isNextMonth(date) {
|
643
|
+
const today = new Date()
|
644
|
+
today.setHours(0, 0, 0, 0) // 把时间设为午夜以准确地比较日期
|
645
|
+
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, 1)
|
646
|
+
const endDateOfNextMonth = new Date(today.getFullYear(), today.getMonth() + 2, 0)
|
647
|
+
return date >= nextMonth && date <= endDateOfNextMonth
|
648
|
+
}
|
649
|
+
|
650
|
+
// 正常应该下面这样写就行了。但是AirScript1.0运行这个会报错,AirScript2.0可以运行但没效果。就用了笨办法来做。
|
651
|
+
// setTimeout(function () {}, milliseconds);
|
652
|
+
function timeSleep(milliseconds) {
|
653
|
+
const startTime = new Date()
|
654
|
+
while (new Date() - startTime < milliseconds) {
|
655
|
+
}
|
656
|
+
}
|
657
|
+
|
658
|
+
// 格式化输出本地时间
|
659
|
+
function formatLocalDatetime(date = new Date()) {
|
660
|
+
function pad(num) { // 补齐两位数
|
661
|
+
return num < 10 ? '0' + num : num
|
662
|
+
}
|
663
|
+
|
664
|
+
let year = date.getFullYear()
|
665
|
+
let month = pad(date.getMonth() + 1) // getMonth() 返回的月份是从0开始的
|
666
|
+
let day = pad(date.getDate())
|
667
|
+
let hour = pad(date.getHours())
|
668
|
+
let minute = pad(date.getMinutes())
|
669
|
+
let second = pad(date.getSeconds())
|
670
|
+
// 我自己的日期风格,/格式一定是本地时间,-格式则本地和utc0时间都有可能
|
671
|
+
return `${year}/${month}/${day} ${hour}:${minute}:${second}`
|
672
|
+
}
|
673
|
+
|
674
|
+
function __6_其他() {
|
675
|
+
|
676
|
+
}
|
677
|
+
|
678
|
+
// 将数据转换为可标准化为json的格式
|
679
|
+
function sanitizeForJSON(data, depth = 1) {
|
680
|
+
const type = typeof data
|
681
|
+
|
682
|
+
// 1 基本类型直接返回
|
683
|
+
if (data === undefined) return null
|
684
|
+
if (data === null || type === 'number' || type === 'string' || type === 'boolean') return data
|
685
|
+
|
686
|
+
// 2 处理数组
|
687
|
+
if (Array.isArray(data)) return data.map(sanitizeForJSON)
|
688
|
+
|
689
|
+
// 3 通过关键词判定特殊类型
|
690
|
+
// 判定所用的key要尽量冷门,避免和普通字典的有效key冲突歧义。一般可以挑一个名字最长的,实在不行的时候也可以复合检查多个key。
|
691
|
+
if (data.hasOwnProperty('FillAcrossSheets')) return 'Sheets'
|
692
|
+
else if (data.hasOwnProperty('EnableFormatConditionsCalculation')) return `Sheets('${data.Name}')`
|
693
|
+
// Cells也算Range类型
|
694
|
+
else if (data.hasOwnProperty('CalculateRowMajorOrder')) return `Range('${data.Address(false, false)}')`
|
695
|
+
|
696
|
+
// 4 处理对象
|
697
|
+
const result = {}
|
698
|
+
let isEmpty = true
|
699
|
+
|
700
|
+
for (const key in data) {
|
701
|
+
if (data.hasOwnProperty(key)) {
|
702
|
+
result[key] = depth > 0 ? sanitizeForJSON(data[key], depth - 1) : ''
|
703
|
+
isEmpty = false
|
704
|
+
}
|
705
|
+
}
|
706
|
+
|
707
|
+
return isEmpty ? type : result
|
708
|
+
}
|
709
|
+
|
710
|
+
|
711
|
+
/**
|
712
|
+
* 为单元格添加或删除超链接
|
713
|
+
* @param {Object} cel - 单元格对象
|
714
|
+
* @param {string} [link] - 超链接地址(可选)。如果不提供,则删除超链接。
|
715
|
+
* @param {string} [text] - 显示文本(可选)。默认使用单元格当前内容,如果为空则显示链接地址。
|
716
|
+
*/
|
717
|
+
function setHyperlink(cel, link, text, screenTip) {
|
718
|
+
// 必须清空,否则如果在旧的url基础上操作,实测会有bug
|
719
|
+
cel.Hyperlinks.Delete()
|
720
|
+
if (link) {
|
721
|
+
// 如果文本参数未提供,则默认使用单元格当前内容
|
722
|
+
// 如果单元格内容为空,则设置为 undefined,这样会默认显示链接地址
|
723
|
+
const displayText = text !== undefined ? text : (cel.Value2 || undefined)
|
724
|
+
// 各位置参数意思:目标单元格,主链接(文件内引用可能会用到),次链接,悬浮提示文本,展示文本
|
725
|
+
// 不过我在wps表格上没测出screenTip效果
|
726
|
+
cel.Hyperlinks.Add(cel, link, undefined, screenTip, displayText)
|
727
|
+
}
|
728
|
+
}
|
729
|
+
|
730
|
+
// 将条件格式的应用范围从部分行扩展到整列(手动增删表格过程中,可能会破坏原本比如L:L条件格式范围为L1:L100,这里可以批量调整变回L:L)
|
731
|
+
function extendFormatConditionsToFullColumns(ws) {
|
732
|
+
const formatConditions = ws.UsedRange.FormatConditions
|
733
|
+
for (let i = 1; i <= formatConditions.Count; i++) {
|
734
|
+
const condition = formatConditions.Item(i)
|
735
|
+
const addr = condition.AppliesTo.Address()
|
736
|
+
// 使用正则匹配类似 $J$2:$J$1048576 的格式,变成$J:$J
|
737
|
+
const match = addr.match(/^\$([A-Z]+)\$\d+:\$\1\$\d+$/)
|
738
|
+
if (match) {
|
739
|
+
// match[1] 是捕获的列字母
|
740
|
+
const colLetter = match[1]
|
741
|
+
condition.ModifyAppliesToRange(st.Range(`${colLetter}:${colLetter}`))
|
742
|
+
}
|
743
|
+
}
|
744
|
+
}
|