forked from gloosace/sasGlue
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsasGlue.sas
10270 lines (8727 loc) · 290 KB
/
sasGlue.sas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
* ----------------------------------------------------------------------------------------------------------;
* --------------------------------------------- Introduction -----------------------------------------------;
* ----------------------------------------------------------------------------------------------------------;
* 建议的阅读顺序?;
* 首次使用者,建议按如下顺序阅读:Q&A -> Contents -> Coding Details -> 您感兴趣的宏与测试用例;
* -------------------------------------------------;
* ---------------------- Q&A ----------------------;
* -------------------------------------------------;
* 什么是sasGlue?;
* sasGlue是一个sas宏代码库,提供各种基础性、通用性的sas宏来辅助用户进行sas编程,是一个sas宏层面的utili库;
* sasGlue的功能主要包括字符串处理、宏与宏变量管理、各种数据结构、数据集管理、IO控制、日志等;
* 如何使用sasGlue?;
* 本文件包括所有说明与源代码,只要直接按F3编译本文件后,即可调用所有的宏,当然也可以使用%include调用本文件;
* 所有的宏使用两级分类,具体内容请查看下文的目录,使用文本搜索功能搜索相应的类名,即可跳转到相应章节;
* 每个章节开头为总体性、普适性的介绍,每个宏的源代码前为该宏的具体介绍;
* 每个宏都包含对应的测试用例,说明中的@test_xxxx表示对应的测试用例文件名,可以在test文件下查找;
* 测试用例同时也是功能示例,对于较复杂的宏可先查看测试用例了解使用方法;
* sasGlue对OS与SAS环境有哪些要求?;
* sasGlue在windows环境开发,如用于其他操作系统请注意重新测试;
* sasGlue绝大部分功能适应所有sas9.2以上版本,个别非核心功能可能有更高版本要求(如:sha256要求sas9.4);
* sasGlue所有代码文件基于UTF-8编码;
* 使用sasGlue的注意事项;
* 请注意不要与您自己编写的宏发生宏名称冲突;
* 请注意不要误操作Glue所使用的全局宏变量(以GLUE_开头);
* 请注意运行环境(如:options设置)可能与您的使用习惯不同;
* sasGlue中的一些宏会使用sas临时工作区(WORK)与当前文件夹(include sasGlue.sas的文件所在的文件夹);
* 在使用sasGlue中的日志功能前,请注意先完成相关sas配置,配置方法可参加测试用例中的说明;
* ----------------------------------------------------------------------------------------------------------;
* ---------------------------------------------- Contents --------------------------------------------------;
* ----------------------------------------------------------------------------------------------------------;
* --------------------------------------------- ENV SETTING ------------------------------------------------;
* 主要为各种option参数、全局宏变量的设定;
* ----------------------------------------------- FORMAT ---------------------------------------------------;
* format definition 各种format/informat定义,所有format将被保存在WORK中并自动引用;
* format basic format管理宏(不包括format的使用),如:format的检查、载入、复制、卸载、删除等;
* format ds 使用format进行数据操作的宏,如:对ds进行批量格式转换、改变ds附加的显示format等;
* ------------------------------------------------ FCMP ----------------------------------------------------;
* fcmp definition 各类数据步函数定义,所有fcmp func将被保存在WORK中并自动引用;
* fcmp macros fcmp的管理宏,主要包括fcmp lib的导入、注销等;
* ---------------------------------------------- DEV TOOLS -------------------------------------------------;
* log macros 日志宏;
* timer macros 计时器类宏;
* debug macros 测试宏,如:断言、临时变量清理情况检查等;
* options macros sas option管理宏,包括option的增加、删除等;
* ------------------------------------------------ BASIC ---------------------------------------------------;
* math macros 各类算术处理宏;
* time macros 日期与时间宏;
* random macros 随机数、随机字符串生成宏;
* others 其他基本功能,如零星封装的fcmp函数;
* ------------------------------------------------ STRING --------------------------------------------------;
* string format 字符串格式识别,判定字符串是否具有某种格式,例如:数字、引号内、括号内等;
* string conversion 字符串转换,对原字符串进行简单的加工修饰,例如:引用、去除空格、加引号等;
* string operation 字符串一般操作宏,例如:搜索、拆入、替换等;
* string hash hash相关操作,例如md5,sha256等;
* string binary 二进制字符串操作宏,例如:二进制AND、OR、XOR、转换为base32格式等;
* --------------------------------------------- VARS/ASSIGNS -----------------------------------------------;
* vars conversion 将vars转换为SAS中的不同场景适用的格式,例如:a b c转换为sql中的(a,b,c);
* vars position 基于位置的操作,例如:var计数、提取指定位置var等;
* vars matching 基于内容的操作,例如:查询某个var是否存在、正则表达式识别等;
* vars set 将vars视为集合的操作,例如:集合add、sub、or等;
* vars batch operation 重复对vars所有元素进行相同处理,例如:添加前后缀、padding等;
* vars others 其他vars宏;
* assigns macros assigns类字符串处理宏,assigns是vars的一个子集,其指元素为A='abc'形式;
* -------------------------------------------------- I/O ---------------------------------------------------;
* file IO macros 文件操作宏,主要包括路径检查、文件夹创建、文件检查、文件删除等;
* ods macros ODS控制与输出宏,包括page控制、变量批量ods输出等;
* ------------------------------------------------ MACRO ---------------------------------------------------;
* macro app macros 自定义宏管理与编写辅助,主要包括代码引用、获取当前宏名称等;
* macro variable macros 宏变量管理宏,主要包括批量获取宏变量、批量全局宏变量删除等;
* plan macros macro批处理宏,根据ds参数批量调用宏进行计算,并将结果回写ds;
* ----------------------------------------------- DATASET --------------------------------------------------;
* ds basic macros ds基础操作宏,如:获取lib名称、检查ds是否相同等、获取观测数量等;
* ds lib macros ds lib操作宏,主要包括lib的载入、注销等;
* ds operation macros ds整体操作宏,主要为以ds整体为操作对象的宏,包括拷贝、移动、删除等;
* ds index macros ds索引操作宏,主要索引创建、删除等;
* ds var operation macros ds变量操作宏,主要为以ds变量为操作对象的宏,包括获取变量名、变量改名、获取变量最大值等;
* ds obs macros ds观测批量操作宏,如:obs去重、key计数等;
* ds cell macros ds单元(即一个观测的一个变量值)操作宏,如:cell写入与读取;
* multi-ds macros 多个ds操作宏,包括merge,leftjoin,filter等;
* ------------------------------------------------- OBJECT -------------------------------------------------;
* obj basic macros obj基本宏,包括obj的创建、复制、删除、成员增减等;
* obj member macros obj成员批量处理宏,对obj所有成员批量进行某一种操作;
* obj ds macros obj存储宏,主要是obj与数据集间的读写交互;
* obj array macros 数组obj,以obj形式为基础实现的数组;
* --------------------------------------------- INITIALIZATION ---------------------------------------------;
* 运行环境初始化,包括自定义fcmp导入、ODS输出等;
* ----------------------------------------------------------------------------------------------------------;
* --------------------------------------- Coding Details ---------------------------------------------------;
* ----------------------------------------------------------------------------------------------------------;
* -------------------------------------------------;
* --------------- Coding Conventions --------------;
* -------------------------------------------------;
* Glue宏的参数格式;
* sas宏提供两种参数的书写格式:有名参数与无名参数;
* 有名参数 %copyDs(data=xxx,out=yyy);
* 无名参数 %strip(a);
* Glue中根据宏复杂度与使用频率来确定使用哪种模式,一般较复杂(参数个数大于3个、功能较为复杂)的宏使用有名参数模式;
* Glue宏运行结果的返回方式;
* Glue中的宏主要使用两种方式来返回运行结果:直接返回与RES返回;
* 直接返回指宏运行的结果是一个字符串,使用这种方式的宏,可以直接作为右值进行赋值。例如;
* %let varsNum=%varsLength(abc);
* RES返回指宏运行不返回字符串,而是将结果写入一个指定的宏变量,例如;
/* %local tres;%let tres=%createTempVar;%local &tres;
%varsCount(vars=&vars,res=&tres);%let varsNum=&&&tres; */
* 直接返回的方式使用简单,但需要调用proc或dataset的宏不能使用这种方式编写,而且一次只能返回一个值,灵活性有限;
* RES返回的方式语法稍微麻烦,但具有最大程度的灵活性,适合较为复杂的宏;
* Glue中的宏会根据复杂度、使用习惯来选择相应的返回模式,个别函数还会同时提供两种版本;
* Glue RES返回方式介绍;
* RES返回方式下,调用宏/caller需要声明用于传递结果的变量,并将该变量名传递给被调用宏/callee,其常见格式如下;
/* caller %macro someMacro;
%local tres;%let tres=%createTempVar;%local &tres;
...;
%varsCount(vars=&vars,res=&tres);%let varsNum=&&&tres;
...;
%mend;
callee %macro varsCount(vars=,res=);
...;
%let &res=&result;
%mend;
*/
* 其中第一句的含义是:caller创建一个随机名称的局部变量(名称保存在tres中),做为callee向其返回结果的载体;
* 采用随机名称而不是固定的名称,主要是为了以下几个原因;
* 避免caller/callee命名习惯不同导致的不兼容,例如:caller习惯使用RESULT,而callee习惯使用R;
* 避免无返回需求时的意外修改,例如:callee使用RESULT返回,但caller不需要返回值,因此未声明该变量;
* 避免caller中意外遗忘声明返回变量导致的意外修改;
* createTempVar创建随机名称的宏变量,并且事先进行变量名的冲突检查,因此可以避免这个问题;
* 该模式除宏参数外,还使用于宏变量与data set(symput)和SQL(select into)的场景;
* 当需要返回多个结果时,设置多个tres变量即可。如果需要返回的结果多于3个变量,则可以考虑使用obj来返回执行结果;
* -------------------------------------------------;
* ---------------------- VARS ---------------------;
* -------------------------------------------------;
* vars格式介绍;
* vars是Glue中常用的一种数据结构,其形式为以白空格分隔的字符串,例如:A B C,其中A被称为一个元素/单元/var;
* vars的用法;
* Glue中基于vars格式,配套了大量的宏以实现不同的功能目标;
* vars position 提供了所有基于位置的操作,可以用于实现对array、queue、stack等数据结构的模拟;
* vars set 提供了集合操作,可以实现对set的模拟;
* vars matching 提供了对元素的各种查找、过滤的支持;
* vars conversion 提供了对vars转换为其他场景sas格式的支持;
* vars batch 提供了对按元素进行的相同操作的批处理的支持;
* vars assigns 提供了基于vars的简单map的模拟;
* 具体使用方式请参加相应宏的使用说明;
* vars的适用场景;
* vars的优点在于交互便利,便于用户书写与阅读,缺点是容量与访问效率较差;
* vars适用于元素相对较少、且较简单的场景,一个最常见的使用场景是用来保存变量名列表,这也是vars名称的由来;
* 对于元素个数较多或元素数据量较大的场景,不建议适用vars,可以考虑在dataset中通过sql处理,或者使用后文的array;
* vars是sas宏变量保存的字符串,因此应注意不应超过65534的长度限制;
* -------------------------------------------------;
* ---------------------- OBJ ----------------------;
* -------------------------------------------------;
* obj格式介绍;
* obj是Glue中一种模仿struct的数据结构;
* obj的一般使用方式;
* obj创建 %newObj(members=name type value,res=&tres) %let obj=&&&tres;
* obj成员赋值 %let &obj.type=abc;
* obj成员引用 %let temp=&&&record.type;
* obj引用传递 %let obj_new=&obj_old;
* obj拷贝传递 %cloneObj(obj=&obj_old,res=&tres) %let obj_new=&&&tres;
* obj的实现方式;
* obj是通过分区使用变量名称来实现的,obj将一个32位的宏变量名划分为两部分:结构名12位 + 成员名20位;
* 例如:一个包含name,type两个成员的obj是由如下2个全局宏变量组成的;
* JLZZC21XZ9RZtype;
* JLZZC21XZ9RZname;
* 由于obj使用全局宏变量,因此可以实现对象的“引用传递”,但也要求使用完后必须使用dropObj来释放相关资源;
* obj与ds的交互操作;
* Glue的obj ds类宏提供了以obj为载体的,面向数据集的简易操作接口;
* 用户可以通过saveObj直接将一个obj当做一个观测写入数据集,或loadObj来从数据集中读出一个观测到obj;
* saveObj与loadObj只支持一次操作一个观测,且要求数据集的相关变量必须都是字符串格式;
* -------------------------------------------------;
* --------------------- ARRAY ---------------------;
* -------------------------------------------------;
* array obj格式介绍;
* array obj是Glue中一种模仿array的数据结构,因为与obj原理相似因此被称为array obj;
* array obj的实现方式与访问方法与obj大体相同,可以理解为是以数字为成员名的obj;
* array obj(以下简称array)的一般使用方式;
* array创建 %arrayNew(100,&tres) %let arr=&&&tres;
* array成员赋值 %let &arr.10=10;
* %let value=%arrayPush(some thing);
* array长度 %let v=&arr.length;
* array成员引用 %let value=&&&arr.10;
* %let value=%arrayPop(&arr);
* array引用传递 %let arr_new=&arr;
* array拷贝传递 %arrayClone(&arr,&tres) %let arr_new=&&&tres;
/* array遍历 %do i=1 %to &arr.length;
%let value=%dosomething(&arr.&i)
%end;*/
* -------------------------------------------------;
* ---------------------- Nesting ------------------;
* -------------------------------------------------;
* vars与obj/array间的嵌套;
* Glue中的vars是字符串,obj、array是字符地址(变量名),因此可以相互嵌套;
* 由于直观性有限,不建议采用表达式直接访问被嵌套的元素;
* 示例1:%varsK(&&&record.names,2) obj内嵌vars,表示提取一个记录对象中的名称列表,取名称中的第二个;
* 示例2:%unquote(&&%varsK(&students,4)name) vars内嵌obj,表示提取一个学生列表中的第4条记录,取其中的名字;
* obj/array之间的嵌套;
* 由于其描述的复杂性,不建议通过表达式直接访问,建议使用Glue提供的G函数与L函数来处理此类场景;
* 示例:对一个由obj与array交替嵌套组成的四级结构,可以按如下方式使用;
* 赋值 %L(&L1.type.5.name.21, 123456);
* 读取 %let temp = %G(&L1.type.5.name.21);
* 注意:L函数仅限于对已经声明并创建好的obj/array的元素进行赋值,L函数不能自动生成数据结构;
* 可以阅读/test/obj_basic/test_nested.sas来进一步了解嵌套的使用方式;
* ----------------------------------------------------------------------------------------------------------;
* ----------------------------------------------- Options -------------------------------------------------;
* ----------------------------------------------------------------------------------------------------------;
options dlcreatedir;
options nosource;
options nosource2;
options nonotes;
options noxwait;
options xsync;
options minoperator mindelimiter=' ';
* ----------------------------------------------------------------------------------------------------------;
* ----------------------------------------------- Globals -------------------------------------------------;
* ----------------------------------------------------------------------------------------------------------;
%global GLUE_LOGGER; %let GLUE_LOGGER=root;
%global GLUE_PAGE_DEFAULT_TYPE; %let GLUE_PAGE_DEFAULT_TYPE=HTML;
%global GLUE_PAGE_DEFAULT_FILE; %let GLUE_PAGE_DEFAULT_FILE=REPORT;
%global GLUE_PAGE_DEFAULT_PATH; %let GLUE_PAGE_DEFAULT_PATH=;
%global GLUE_PAGE_DEFAULT_NOTIMESTAMP; %let GLUE_PAGE_DEFAULT_NOTIMESTAMP=0;
%global GLUE_PAGE_DEFAULT_P1; %let GLUE_PAGE_DEFAULT_P1=0;
%global GLUE_GENID_SEED; %let GLUE_GENID_SEED=;
* -------------------------------------------------;
* --------------- Format Definition ---------------;
* -------------------------------------------------;
* 生成base32 format;
* base32 包含0-9十个数字,A-Z中剔除I、O、M、V,总计5bit,32个字符;
proc format;
* md5base32使用;
invalue $base32x
'00000'='0'
'00001'='1'
'00010'='2'
'00011'='3'
'00100'='4'
'00101'='5'
'00110'='6'
'00111'='7'
'01000'='8'
'01001'='9'
'01010'='A'
'01011'='B'
'01100'='C'
'01101'='D'
'01110'='E'
'01111'='F'
'10000'='G'
'10001'='H'
'10010'='J'
'10011'='K'
'10100'='L'
'10101'='N'
'10110'='P'
'10111'='Q'
'11000'='R'
'11001'='S'
'11010'='T'
'11011'='U'
'11100'='W'
'11101'='X'
'11110'='Y'
'11111'='Z';
* dsSame使用;
value procCompareErrBits
1='Data set labels differ'
2='Data set types differ'
3='Variable has different informat'
4='Variable has different format'
5='Variable has different length'
6='Variable has different label'
7='Base data set has observation not in comparison'
8='Comparison data set has observation not in base'
9='Base data set has BY group not in comparison'
10='Comparison data set has BY group not in base'
11='Base data set has variable not in comparison'
12='Comparison data set has variable not in base'
13='A value comparison was unequal'
14='Conflicting variable types'
15='BY variables do not match'
16='Fatal error: comparison not done';
quit;
* -------------------------------------------------;
* ---------------- FCMP Definition ----------------;
* -------------------------------------------------;
* 所有fcmp函数默认被保存于work.funcs.glue;
* 文件末尾执行importFuncs将work.funcs写入cmplib;
proc fcmp outlib=work.funcs.glue;
* ------------------------------------------------;
* ---------------- String functions --------------;
* ------------------------------------------------;
* ---- function strInit ----;
* fcmp字符变量初始化;
* test;
* @test_fcmp_strInit;
* details;
* 使用方式;
* s=strInit(.);
* 该语句等价于 length s $ 32767;
* 函数逻辑;
* fcmp函数中的字符串变量不支持不定长字符串,如果未使用length声明,则sas会自动推断该变量的长度并设置为固定长度;
* 这种自动推断可能导致意外的字符串截取,因此对函数中的字符串变量,都应该显式的声明长度;
* 对于一些不能确定长度的场景,可以使用strInit将该字符串设置为允许的最大值,即32767;
* sas字符串长度推断逻辑;
* (设:args1为参数名称,var1为其他变量名称,x为待推断的变量名);
* 情景 推断长度;
* x='abc' 长度为3;
* x=args1 args1实际长度<=33,则为实际长度,否则为33;
* x=var1 与var1相同;
* x=myfunc(...) 如果myfunc函数内声明了返回值长度,或返回值的长度可推断,则为相应的长度,例如:x=repeat('_',6),则x长度为6;
* 如果未声明且无法推断,则为固定200,例如:x=repeat('_',round(10*rand('uniform'))),x长度为200;
* data字符串变量的长度设定;
* strInit将长度设为最大值是为了在函数计算过程中尽可能避免截断的出现;
* 在data step中,应事先设定变量长度,以避免最终存储的变量长度推断为32767,占用过多空间;
* cat禁止使用;
* 在声明了str为32767长度后,注意不要直接使用cat或||进行字符串连接,否则会导致异常;
* 可以使用cats或先对该变量进行strip处理;
function strInit(null) $ 32767;
return('');
endsub;
* ---- function strcode ----;
* 字符串->utf8 unicode数值字符串转换函数;
* test;
* @test_fcmp_strcode;
function strcode(s $) $;
uc=strInit(.);
uc=unicodec(strip(s),'PAREN');
uc=transtrn(uc, '<u','');
uc=transtrn(uc, '>','');
return(uc);
endsub;
* ---- function rightPadding ----;
* 右截取与padding函数;
* 对于给定字符串,当超过目标长度将进行右侧截取,未超过将进行右侧补充;
* input;
* rs 字符串;
* len pad后的长度;
* test;
* @test_fcmp_rightPadding;
* details;
* 注意对于输入字符串s,是strip后处理的;
function rightPadding(rs $,len,pad $) $;
s=strInit(.);
s=strip(rs);
pad=strip(pad);
if missing(len) then return(s);
if missing(pad) then pad='.';
l=length(s);
lp=length(pad);
if missing(s) then return(repeat(strip(pad),len-1));
else if l>len then return(substr(s,1,len));
else if l<len then return(cats(s,substr(repeat(strip(pad),ceil((len-l)/lp)),1,len-l)));
else return(s);
endsub;
* ---- function leftPadding ----;
* 左侧截取与padding函数;
* test;
* @test_fcmp_leftPadding;
function leftPadding(rs $,len,pad $) $;
s=strInit(.);
s=strip(rs);
pad=strip(pad);
if missing(len) then return(s);
if missing(pad) then pad='.';
l=length(s);
lp=length(pad);
if missing(s) then return(repeat(strip(pad),len-1));
else if l>len then return(substr(s,l-len+1,len));
else if l<len then return(cats(substr(repeat(strip(pad),ceil((len-l)/lp)),1,len-l),s));
else return(s);
endsub;
* ---- function substring ----;
* 逻辑与宏substring相同;
* input;
* sraw 字符串;
* pStart 截取开始位置;
* pEnd 截取停止位置;
* test;
* @test_fcmp_substring;
function subString(sraw $,pStart,pEnd) $;
doReverse=0;
res=strInit(.);
s=strInit(.);
res=' ';
s=strip(sraw);
len=length(s);
if missing(s) then return(res);
* 默认值填充;
if missing(pStart) then pStart=1;
if missing(pEnd) then pEnd=-1;
* 负值转换;
if pStart<0 then pStart=pStart+len+1;
if pEnd<0 then pEnd=pEnd+len+1;
* 逆序调整;
if pStart>pEnd then do;
pTemp=pStart;
pStart=pEnd;
pEnd=pTemp;
doReverse=1;
end;
* 裁剪范围存在性判定;
if pStart>len then return(' ');
if pEnd<1 then return(' ');
* 实际裁剪范围范围确定;
pStart=min(max(pStart,1),len);
pEnd=max(min(pEnd,len),1);
* 字符串提取;
res=substr(s,pStart,pEnd-pStart+1);
* 逆序调整;
if doReverse=1 then res=reverse(res);
return(strip(res));
endsub;
* ---- function md5hex ----;
* 返回字符型,hex格式的md5数值;
* 注意:md5在计算时与输入字符串的变量长度有关,相同字符串,保存为不同长度的变量,其md5值不同;
* 为避免这种影响,md5hex在进行md5运算前,将进行strip操作;
* test;
* @test_fcmp_md5hex;
function md5hex(s $) $32;
a=md5(strip(s));
md5=putc(a,'hex32.');
return(md5);
endsub;
* ---- function md5base32 ----;
* 根据字符串生成MD5,并转换为base32格式,对应md5base32宏;
* 注意:由于md5受变量长度影响的问题,这里的md5也是进行strip再进行计算,因此在有首尾空格的场景下与宏md5base32计算的结果不同;
* test;
* @test_fcmp_md5base32;
function md5base32(s $) $ 26;
length binStr $ 130 base32 $ 28 b $ 5 c $ 1;
h=md5(strip(s));
binStr=putc(h,'binary128.');
l=length(binStr);
k=ceil(l/5);
p=k*5-l;
if p>0 then binStr=cats(binStr,repeat('0',p-1));
do i=1 to k;
start=(i-1)*5+1;
b=substr(binStr,start,5);
c=inputc(b,'$base32x');
base32=cats(base32,c);
end;
return(base32);
endsub;
* ------------------------------------------------;
* ---------------- Log functions -----------------;
* ------------------------------------------------;
* ---- function lerror ----;
* test;
* @test_fcmp_log;
subroutine lerror(log $);
call saslog("ERROR",log);
endsub;
* ---- function lwarn ----;
* test;
* @test_fcmp_log;
subroutine lwarn(log $);
call saslog("WARN",log);
endsub;
* ---- function linfo ----;
* test;
* @test_fcmp_log;
subroutine linfo(log $);
call saslog("INFO",log);
endsub;
* ---- function saslog ----;
* test;
* @test_fcmp_log;
subroutine saslog(level $,log $);
length msg $ 1000;
ts=put(datetime(),B8601DT.3);
msg=cats('[',ts,'][',level,']',log);
err=log4sas_logevent("&GLUE_LOGGER", level,strip(msg));
if level='INFO' then put msg;
endsub;
* ------------------------------------------------;
* ---------------- Date functions ----------------;
* ------------------------------------------------;
* ---- function timetype -----;
* 根据数值判断指定的数值是否是date或datetime;
* 要求的时间范围必须在[01/jan/1961,31/dec/9999]范围内;
* input;
* a 时间数值;
* output;
* 返回值 2 datetime;
* 1 date;
* 0 其他;
* test;
* @test_fcmp_timetype;
function timetype(a);
if a>=31622400 and a<=253717747199 then return(2);
if a>=366 and a<=2936547 then return(1);
return(0);
endsub;
* ---- function dateToDt ----;
* 将数值由date转换为datetime;
* test;
* @test_fcmp_dateToDt;
function dateToDt(d);
return(DHMS(d,0,0,0));
endsub;
* ---- function timeFloor -----;
* 对时间向下取整;
* input;
* d 时间变量;
* int 取整的单位,包括所有sas date与datetime interval;
* 注意提供的interval不需要增加dt前缀,函数会自动适配;
* base 窗口的基准;
* output;
* 返回值 取整后的时间;
* test;
* @test_fcmp_timeFloor;
function timeFloor(d,interval $);
timeType=timetype(d);
intv=interval;
if timeType=2 then intv=cats('dt',interval);
if timeType=0 then return(.);
return(intnx(intv,d,0,'BEGIN'));
endsub;
* ---- function timeCeil -----;
* 对时间向上取整;
* test;
* @test_fcmp_timeFloor;
function timeCeil(d,interval $);
timeType=timetype(d);
intv=interval;
if timeType=2 then intv=cats('dt',interval);
if timeType=0 then return(.);
return(intnx(intv,d,0,'END'));
endsub;
* ------------------------------------------------;
* ---------------- Test functions ----------------;
* ------------------------------------------------;
* ---- subroutine assertEqual ----;
* 数值变量相等断言;
subroutine assertEqual(a,b,msg $);
length str $ 1000;
if a=b then str=cats('[ASSERTEQUAL][PASS][',a,'][',b,']',msg);
else str=cats('[ASSERTEQUAL][FAILED][',a,'][',b,']',msg);
put str;
endsub;
* ---- subroutine assertStrEqual ----;
* 字符变量相等断言;
* details;
* 注意:前后的空格数量将被忽略不影响比较;
subroutine assertStrEqual(a $,b $,msg $);
length str $ 1000;
if a=b then str=cats('[ASSERTSTREQUAL][PASS][',a,'][',b,']',msg);
else str=cats('[ASSERTSTREQUAL][FAILED][',a,'][',b,']',msg);
put str;
endsub;
run;
* -----------------------------------------------------;
* ---------------- Format Basic macros ----------------;
* -----------------------------------------------------;
* format类宏中的format形参;
* format可能使用以下几种格式;
* full format 表示完整的format,如:BEST. BEST12.2 $ISO6401.2等,一般用于data step中;
* format name 表示format的名称,不包含小数点、位数等附加部分,如:BEST,$ISO6401等,一般用于以format(而不是数据)为对象的操作;
* format转换中无法识别情况的处理;
* 使用format进行转换时,如format的定义域没有完整覆盖变量的定义域,则会导致转换时无法识别的情况;
* 在data step中,使用input(var,??format)语法可以部分解决这个问题,但宏层面只能使用封装的inputn,inputc等,这类函数不支持这种语法;
* 因此在所有用于宏场景的format定义中,建议都应设置other选项;
* ---- macro inputn ----;
* inputn封装;
* details;
* 因为转换内容中可能包含特殊字符,因此封装使用qsysfunc;
%macro inputn(v,f);
%qsysfunc(inputn(&v,&f))
%mend;
* ---- macro inputc ----;
* inputc封装;
%macro inputc(v,f);
%qsysfunc(inputc(&v,&f))
%mend;
* ---- macro putn ----;
* putn封装;
%macro putn(v,f);
%qsysfunc(putn(&v,&f))
%mend;
* ---- macro putc ----;
* putc封装;
%macro putc(v,f);
%qsysfunc(putc(&v,&f))
%mend;
* ---- macro isStrFormat ----;
* 判定输入的format是否是包含$;
* test;
* @test_isStrFormat;
%macro isStrFormat(f);
%local res;
%let res=0;
%if %isBlank(&f) %then %let res=0;
%else %if %substr(%strip(&f),1,1)=%str($) %then %let res=1;
&res.
%mend;
* ---- macro fullFormat ----;
* 转换为full format;
* input;
* f 任意格式的format;
* details;
* fullFormat不涉及$的处理;
* test;
* @test_fullFormat;
%macro fullFormat(f);
%local res;
%if %isBlank(&f) %then %let res=%str();
%else %if %index(&f,%str(.)) %then %let res=&f;
%else %let res=&f..;
&res.
%mend;
* ---- macro dotFormat ----;
* fullFormat别名;
%macro dotFormat(f);
%fullFormat(&f)
%mend;
* ---- macro formatName ----;
* 转换为format name;
* input;
* f 任意格式的format;
* details;
* formatName不涉及$的处理;
* test;
* @test_formatName;
%macro formatName(f);
%prxchange(%str(s/^(\$?[a-z|A-Z|0-9]*[a-z|A-Z]+)\d*\.?\d*$/$1/),-1,%strip(&f))
%mend;
* ---- macro noDotFormat ----;
* formatName别名;
%macro noDotFormat(f);
%formatName(&f)
%mend;
* ---- macro checkFormats ----;
* format存在性检查函数;
* 注意:checkFormats用于检查指定的lib中是否存在指定的format,但不保证该format当前可用,检查format是否可用应使用pingFormats;
* input;
* lib 目标lib;
* path 目标path;
* 位置参数优先级 lib > path > work;
* formats/format format名称,必须是formatName;
* isInformat 0|1,查询format是否是informat,默认为0;
* 所有要检查的format必须类型相同;
* res 返回不存在的format;
* details;
* test;
* @test_checkFormats;
%macro checkFormats(lib=,path=,format=,formats=,isInformat=,res=) /parmbuff;
%local tres;%let tres=%createTempVar;%local &tres;
%local fmtSuffix fmtName fmtType isTempLib;
%local formatsN fmt i;
%local notExistFormats;
%let isTempLib=0;
* 参数检查;
%resCheck(&res);
%let formats=%paramAlias(&formats,&format);
%let isInformat=%nonBlank(&isInformat,0);
%if %isBlank(&formats) %then %return;
* 路径检查;
%if %notBlank(&lib) %then %let lib=%upcase(&lib);
%else %if %notBlank(&path) %then %do;
%if not %folderExist(&path) %then %return;
%importTempLib(&path,&tres);%let lib=&&&tres;
%let isTempLib=1;
%end;
%else %let lib=WORK;
%varsCount(vars=&formats,res=&tres);%let formatsN=&&&tres;
%do i=1 %to &formatsN;
%varsN(vars=&formats,n=&i,res=&tres);%let fmt=&&&tres;
%let fmtName=%formatName(&fmt);
%let fmtSuffix=%str();
%let fmtType=FORMAT;
%if %isStrFormat(&fmtName) %then %do;
%let fmtSuffix=C;
%let fmtName=%subString(&fmtName,2);
%end;
%if &isInformat=1 %then %let fmtType=INFMT;
%if %sysfunc(cexist(&lib..formats.&fmtName..&fmtType.&fmtSuffix.))=0 %then %let notExistFormats=¬ExistFormats &fmt;
%end;
%resSet(&res,¬ExistFormats);
* 资源释放;
%if &isTempLib %then %dropLib(&lib);
%mend;
* ---- macro checkformat ----;
%macro checkformat(lib=,path=,format=,formats=,isInformat=,res=);
%checkFormats(lib=&lib,path=&path,format=&format,formats=&formats,isInformat=&isInformat,res=&res);
%mend;
* ---- macro checkInformats ----;
%macro checkInformats(lib=,path=,format=,formats=,res=);
%checkFormats(lib=&lib,path=&path,format=&format,formats=&formats,isInformat=1,res=&res);
%mend;
* ---- macro checkInformat ----;
%macro checkInformat(lib=,path=,format=,formats=,res=);
%checkFormats(lib=&lib,path=&path,format=&format,formats=&formats,isInformat=1,res=&res);
%mend;
* ---- macro pingFormats ----;
* 检查指定的format是否可用;
* input;
* formats/format 目标format,vars格式,必须是formatName;
* res 当前不可用的format,vars格式;
* test;
* @test_pingFormats;
%macro pingFormats(format=,formats=,isInformat=,res=);
%local tres;%let tres=%createTempVar;%local &tres;
%local fmtType;
%local stmt_where;
%let fmtType=F;
%resCheck(&res);
%let formats=%paramAlias(&formats,&format);
%let isInformat=%nonBlank(&isInformat,0);
%if &isInformat=1 %then %let fmtType=I;
%if %isBlank(&formats) %then %return;
%let stmt_where=%quote(fmtName in (%sasVarsToQuote(%upcase(&format))));
proc sql noprint;
select fmtname into :&tres separated by ' ' from sashelp.vformat where fmttype="&fmtType" and (%unquote(&stmt_where));
quit;
%varsSub(a=&format,b=&&&tres,res=&res);
%mend;
* ---- macro pingFormat ----;
%macro pingFormat(format=,formats=,isInformat=,res=);
%pingFormats(format=&format,formats=&formats,isInformat=&isInformat,res=&res);
%mend;
* ---- macro pingInformats ----;
%macro pingInformats(format=,formats=,res=);
%pingFormats(format=&format,formats=&formats,isInformat=1,res=&res);
%mend;
* ---- macro pingInformat ----;
%macro pingInformat(format=,formats=,res=);
%pingFormats(format=&format,formats=&formats,isInformat=1,res=&res);
%mend;
* ---- macro deleteFormats ----;
* 删除指定的lib中的format;
* input;
* lib 目标lib;
* path 目标path;
* 位置优先级lib>path>WORK;
* formats/format 待删除format,必须是formatName格式;
* res 返回变量;
* test;
* @test_deleteFormats;
%macro deleteFormats(path=,lib=,formats=,format=,isInformat=);
%local tres;%let tres=%createTempVar;%local &tres;
%local tl;
%local fmtSuffix fmtName fmtType;
%local formatsN fmt i;
%local stmt_delete;
%let formats=%paramAlias(&formats,&format);
%let isInformat=%nonBlank(&isInformat,0);
%if %isBlank(&formats) %then %return;
%if %isBlank(&lib) %then %do;
%if %notBlank(&path) %then %do;
%importTempLib(&path,&tres);%let lib=&&&tres;
%let tl=&lib;
%end;
%else %let lib=WORK;
%end;
%varsCount(vars=&formats,res=&tres);%let formatsN=&&&tres;
%do i=1 %to &formatsN;
%varsN(vars=&formats,n=&i,res=&tres);%let fmt=&&&tres;
%let fmtName=%formatName(&fmt);
%let fmtSuffix=%str();
%let fmtType=FORMAT;
%if %isStrFormat(&fmtName) %then %do;
%let fmtSuffix=C;
%let fmtName=%subString(&fmtName,2);
%end;
%if &isInformat=1 %then %let fmtType=INFMT;
%if %sysfunc(cexist(&lib..formats.&fmtName..&fmtType.&fmtSuffix.))=1 %then %let stmt_delete=&stmt_delete &fmtName..&fmtType.&fmtSuffix;
%end;
%if %notBlank(&stmt_delete) %then %do;
proc catalog catalog=%unquote(&lib..FORMATS);
delete %unquote(&stmt_delete);
run;quit;
%end;
%dropLib(&tl);
%mend;
* ---- macro deleteFormat ----;
%macro deleteFormat(path=,lib=,format=,formats=,isInformat=);
%deleteFormats(path=&path,lib=&lib,format=&format,formats=&formats,isInformat=&isInformat);
%mend;
* ---- macro deleteInformats ----;
%macro deleteInformats(path=,lib=,format=,formats=);
%deleteFormats(path=&path,lib=&lib,format=&format,formats=&formats,isInformat=1);
%mend;
* ---- macro deleteInformat ----;
%macro deleteInformat(path=,lib=,format=,formats=);
%deleteFormats(path=&path,lib=&lib,format=&format,formats=&formats,isInformat=1);
%mend;
* ---- macro dropFormats ----;
%macro dropFormats(path=,lib=,format=,formats=,isInformat=);
%deleteFormats(path=&path,lib=&lib,format=&format,formats=&formats,isInformat=&isInformat);
%mend;
* ---- macro dropFormat ----;
%macro dropFormat(path=,lib=,format=,formats=,isInformat=);
%deleteFormats(path=&path,lib=&lib,format=&format,formats=&formats,isInformat=&isInformat);
%mend;
* ---- macro dropInformats ----;
%macro dropInformats(path=,lib=,format=,formats=);
%deleteFormats(path=&path,lib=&lib,format=&format,formats=&formats,isInformat=1);
%mend;
* ---- macro dropInformat ----;
%macro dropInformat(path=,lib=,format=,formats=);
%deleteFormats(path=&path,lib=&lib,format=&format,formats=&formats,isInformat=1);
%mend;
* ---- macro reportFormats ----;
* 显示指定的lib中的format;
* input;
* lib 目标lib;
* path 目标path;
* 目标优先级 lib>path>WORK;
* format 要显示的format名称,vars格式,为空时显示lib中的全部format与informat;
* msg 信息;
* test;
* @test_reportFormats;
%macro reportFormats(path=,lib=,formats=,format=,isInformat=,msg=);
%local tres;%let tres=%createTempVar;%local &tres;
%local tl;
%let isInformat=%nonBlank(&isInformat,0);
%let formats=%paramAlias(&formats,&format);
%if %isBlank(&lib) %then %do;
%if %notBlank(&path) %then %do;
%importTempLib(&path,&tres);%let lib=&&&tres;
%let tl=&lib;
%end;
%else %let lib=WORK;
%end;