-
Notifications
You must be signed in to change notification settings - Fork 55
/
Sheet.java
1619 lines (1488 loc) · 52.7 KB
/
Sheet.java
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
/*
* Copyright (c) 2017, guanquan.wang@yandex.com All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ttzero.excel.entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ttzero.excel.entity.e7.XMLWorksheetWriter;
import org.ttzero.excel.entity.style.Border;
import org.ttzero.excel.entity.style.BorderStyle;
import org.ttzero.excel.entity.style.Fill;
import org.ttzero.excel.entity.style.Font;
import org.ttzero.excel.entity.style.Horizontals;
import org.ttzero.excel.entity.style.NumFmt;
import org.ttzero.excel.entity.style.PatternType;
import org.ttzero.excel.entity.style.Styles;
import org.ttzero.excel.entity.style.Verticals;
import org.ttzero.excel.manager.Const;
import org.ttzero.excel.manager.RelManager;
import org.ttzero.excel.reader.Cell;
import org.ttzero.excel.reader.Dimension;
import org.ttzero.excel.util.FileUtil;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import static org.ttzero.excel.manager.Const.ROW_BLOCK_SIZE;
import static org.ttzero.excel.util.StringUtil.isEmpty;
import static org.ttzero.excel.util.StringUtil.isNotEmpty;
/**
* 工作表Worksheet是Excel最重要的组件,在Excel看见的所有内容都是由Worksheet工作表呈现。
* 本工具将工作表{@code Sheet}及其子类视为数据源,它本身除了收集数据外并不输出任何格式的文件,
* 它必须与输出协议搭配使用才会输出相应格式的文件。如与{@link XMLWorksheetWriter}输出协议搭配时,
* 每个{@code Sheet}对应一个Excel工作表和{@code sheet.xml}文件。
*
* <p>工作表对应的输出协议为{@link IWorksheetWriter},它会循环调用{@link #nextBlock}
* 方法获取数据并写入磁盘直到{@link RowBlock#isEOF}返回EOF标记为止,整个过程只有一个
* RowBlock行块常驻内存,一个{@code RowBlock}行块默认包含32个{@code Row}行,这样可以保证
* 较小的内存开销。</p>
*
* <p>当前支持的数据源有{@link ListSheet},{@link ListMapSheet},{@link TemplateSheet},{@link StatementSheet}
* 和{@link ResultSetSheet}5种,前三种较为常用,后两种可实现将数据库查询结果直接导出到Excel
* 省掉转Java实体的中间环节。继承Sheet并实现抽象方法{@link Sheet#resetBlockData}可以扩展新的数据源,
* 你需要在该方法中获取数据并使用{@link ICellValueAndStyle}转换器将数据转换为输出协议允许的结构。</p>
*
* <p>{@code ListSheet}及其子类支持超大数据导出,理论上导出数据行无上限,当超过Worksheet行数限制时
* 将会在下一个位置插入一个与当前{@code Sheet}一样的工作表,然后将剩余数据写到新插入的工作表中,
* 分页是自动触发的无需额外设置。超大数据导出时建议使用{@link ListSheet#more}方法分批查询数据,
* 返回空数组或{@code null}时结束。</p>
*
* <p>每个Worksheet都可以设置一个{@link #onProgress}窗口来观察导出进度,通过此窗口可以记录每个
* RowBlock导出时间然后预估整体的导出时间。</p>
*
* <p>能被直接导出的类型包含Java定义的简单类型、包装类型以及时间类型,除此之外的其它类型均会调用{@code toString}
* 后直接输出,所以自定义类型或枚举可以覆写{@code toString}方法转为可读的字符串,或者使用
* {@link org.ttzero.excel.processor.ConversionProcessor}动态转换将不可读的状态值{@code 1,2,3}转换为
* 可读的文字”申请中“,“二申中”,“通过”等,对于未知的类型还可以实现{@link ICellValueAndStyle}转换器并覆写
* {@link ICellValueAndStyle#unknownType}方法进行转换</p>
*
* <p>关于扩展属性:随着功能越加越多,在{@code Sheet}中定义的属性也越来越多,这样无限添加可不是个好主意,
* 所以在{@code v0.5.0}引入了一个{@code Map}类型的扩展属性{@link #extProp},通过{@link #putExtProp}
* 和{@link #getExtPropValue}方法添加和读取扩展属性,一般情况下在数据源中添加属性,在输出协议中读取属性,
* 像合并单元格、冻结首行都是通过扩展参数实现。</p>
*
* @author guanquan.wang on 2017/9/26.
* @see ListSheet
* @see ListMapSheet
* @see TemplateSheet
* @see ResultSetSheet
* @see StatementSheet
* @see CSVSheet
* @see EmptySheet
*/
public abstract class Sheet implements Cloneable, Storable {
/**
* LOGGER
*/
protected final Logger LOGGER = LoggerFactory.getLogger(getClass());
/**
* 工作薄
*/
protected Workbook workbook;
/**
* 工作表名称
*/
protected String name;
/**
* 表头
*/
protected Column[] columns;
/**
* 水印
*/
protected WaterMark waterMark;
/**
* 关系管理器
*/
protected RelManager relManager;
/**
* 工作表ID,与当前工作表在工作薄中的下标一致
*/
protected int id;
/**
* 表头批注
*/
protected Comments comments;
/**
* 自适应列宽标记,优先级从小到大为 0: 未设置 1: 自适应列宽 2: 固定宽度
*/
protected int autoSize;
/**
* 默认列宽
*/
protected double width = 20D;
/**
* 统计已写入数据行数,不包含表头
*/
protected int rows;
/**
* 标记是否“隐藏”
*/
protected boolean hidden;
/**
* 兜底的表头样式索引,优先级低Column独立设置的样式
*/
protected int headStyleIndex = -1;
/**
* 统一的表头样式,优先级低Column独立设置的样式
*/
protected int headStyle;
/**
* 斑马线样式索引
*/
protected int zebraFillStyle = -1;
/**
* 斑马线填充样式,斑马线从表头以下的第2行开始每隔一行进行一次填充
*/
protected Fill zebraFill;
/**
* 标记是否为自动分页的“复制”工作表
*/
protected boolean copySheet;
/**
* 记录自动分页的“复制”工作表数量
*/
protected int copyCount;
/**
* 行块,它由连续的一组默认大小为{@code 32}个{@code Row}组成的迭代器,该对象是内存共享的,
* 可以通过覆写{@link #getRowBlockSize()}指定其它大小,一般不建议修改。
*/
protected RowBlock rowBlock;
/**
* 工作表输出协议
*/
protected IWorksheetWriter sheetWriter;
/**
* 标记表头是否已采集,默认情况下会进行收集-排序-多表头合并等过程后状态才为"ready"
*/
protected boolean headerReady;
/**
* 标记是否需要关闭,自动分页情况下最后一个worksheet页需要关闭资源,因为所有的数据都是从最原始
* 的工作表获取,所以只有写完数据之后才能关闭。
*/
protected boolean shouldClose = true;
/**
* 转换器,将外部数据转换为Worksheet输出协议需要的数据类型并设置单元格样式
*/
protected ICellValueAndStyle cellValueAndStyle;
/**
* 忽略表头 -1 未设置, 0 输出表头, 1 忽略表头
*/
protected int nonHeader = -1;
/**
* 工作表body行数上限,它记录的是输出协议行上限-表头行数,例如xls格式最多{@code 65535}行,
* 导出的表头为1行,那{@code rowLimit = 65534}
*/
private int rowLimit;
/**
* 扩展属性
*/
protected Map<String, Object> extProp = new HashMap<>();
/**
* 扩展参数的位标志。如果存在扩展参数则相应的位为1,低16位由系统占用
*/
protected int extPropMark;
/**
* 是否显示"网格线",默认显示
*/
protected Boolean showGridLines;
/**
* 指定表头行高
*/
protected double headerRowHeight = 20.5D;
/**
* 指定数据行高
*/
protected Double rowHeight;
/**
* 指定起始行,默认从第1行开始,不同行java中的下标这里是指行号,也就是打开excel左侧看到的行号从1开始
*/
protected int startRowIndex = 1;
/**
* 导出进度窗口,默认情况下RowBlock每刷新一次就会更新一次进度,也就是每32行通知一次
*/
private BiConsumer<Sheet, Integer> progressConsumer;
/**
* 获取工作表ID,与当前工作表在工作薄中的下标一致,一般与其它资源关联使用
*
* @return 工作表ID
*/
public int getId() {
return id;
}
/**
* 设置工作表ID,请不要在外部随意修改,否则打开文件异常
*
* @param id 工作表ID
* @return 当前工作表
*/
public Sheet setId(int id) {
this.id = id;
return this;
}
/**
* 设置输出协议,必须与对应的{@link IWorkbookWriter}工作薄输出协议一起使用
*
* @param sheetWriter 工作表输出协议{@link IWorksheetWriter}
* @return 当前工作表
*/
public Sheet setSheetWriter(IWorksheetWriter sheetWriter) {
this.sheetWriter = sheetWriter;
this.sheetWriter.setWorksheet(this);
return this;
}
/**
* 获取工作表输出协议{@link IWorksheetWriter}
*
* @return 工作表输出协议
*/
public IWorksheetWriter getSheetWriter() {
return sheetWriter;
}
/**
* 设置数据转换器,用于将Java对象转为各工作表输出协议可接受的数据结构,一般会将每个单元格的值输出为{@link Cell}对象,
* 它与{@code Sheet}数据源中的Java类型完全分离,使得下游的输出协议有统一输入源。
*
* <p>除了数据转换外,该转换器还兼具采集样式,采集样式时会先从表头{@link Column#getCellStyle}中获取初始样式,
* 如果该列有动态样式则会将该初始样式做为入参传入{@link org.ttzero.excel.processor.StyleProcessor}
* 动态样式处理器以制定动态样式,如果设置有工作表级的样式处理器则会将动态样式的结果做为入参继续调工作表级
* 样式处理器制定最终的样式</p>
*
* @param cellValueAndStyle 数据转换器
* @return 当前工作表
*/
public Sheet setCellValueAndStyle(ICellValueAndStyle cellValueAndStyle) {
this.cellValueAndStyle = cellValueAndStyle;
return this;
}
/**
* 获取数据转换器
*
* @return 数据转换器 {@link ICellValueAndStyle}
*/
public ICellValueAndStyle getCellValueAndStyle() {
return cellValueAndStyle;
}
/**
* 实例化工作表,未指定工作表名称时默认以{@code 'Sheet'+id}命名
*/
public Sheet() {
relManager = new RelManager();
}
/**
* 实例化工作表并指定工作表名称
*
* @param name 工作表名称
*/
public Sheet(String name) {
this.name = name;
relManager = new RelManager();
}
/**
* 实例化工作表并指定表头信息
*
* @param columns 表头信息
*/
public Sheet(final Column... columns) {
this.columns = columns;
relManager = new RelManager();
}
/**
* 实例化工作表并指定工作表名称和表头信息
*
* @param name 工作表名称
* @param columns 表头信息
*/
public Sheet(String name, final Column... columns) {
this(name, null, columns);
}
/**
* 实例化工作表并指定工作表名称,水印和表头信息
*
* @param name 工作表名称
* @param waterMark 水印
* @param columns 表头信息
*/
public Sheet(String name, WaterMark waterMark, final Column... columns) {
this.name = name;
this.columns = columns;
this.waterMark = waterMark;
relManager = new RelManager();
}
/**
* 获取当前工作表对应的工作薄
*
* @return 当前工作表对应的 {@link Workbook}
*/
public Workbook getWorkbook() {
return workbook;
}
/**
* 设置工作薄,一般在调用{@link Workbook#addSheet}时设置工作薄,{@code Workbook}包含
* 样式、共享字符区、资源类型等全局配置,为了方便读取所以每个worksheet均包含Workbook句柄
*
* @param workbook 工作薄{@link Workbook}
* @return 当前工作表
*/
public Sheet setWorkbook(Workbook workbook) {
this.workbook = workbook;
if (columns != null) {
for (int i = 0; i < columns.length; i++) {
columns[i].styles = workbook.getStyles();
}
}
return this;
}
/**
* 获取默认列宽,如果未在Column上特殊指定宽度时该宽度将应用于每一列
*
* @return 默认列宽20
*/
public double getDefaultWidth() {
return width;
}
/**
* 标记当前工作表自适应列宽,此优先级低于使用{@link Column#setWidth}指定列宽
*
* @return 当前工作表
*/
public Sheet autoSize() {
this.autoSize = 1;
return this;
}
/**
* 获取工作表的全局自适应列宽标记
*
* @return 1: 自适应列宽 2: 固定宽度
*/
public int getAutoSize() {
return autoSize;
}
/**
* 是否全局自适应列宽,此值使用{@code autoSize()==1}判断
*
* @return true:自适应列宽
*/
public boolean isAutoSize() {
return autoSize == 1;
}
/**
* 设置当前工作表使用固定列宽,将默认使用{@link #getDefaultWidth()}返回的宽度
*
* @return 当前工作表
*/
public Sheet fixedSize() {
this.autoSize = 2;
return this;
}
/**
* 设置当前工作表使用固定列宽并指定宽度,此方法会对入参进行重算,当宽度为'零'时效果相当于隐藏该列
*
* @param width 列宽
* @return 当前工作表
*/
public Sheet fixedSize(double width) {
if (width < 0.0D) {
LOGGER.warn("Negative number {}", width);
width = 0.0D;
}
else if (width > Const.Limit.COLUMN_WIDTH) {
LOGGER.warn("Maximum width is {}, current is {}", Const.Limit.COLUMN_WIDTH, width);
width = Const.Limit.COLUMN_WIDTH;
}
this.autoSize = 2;
this.width = width;
if (headerReady) {
for (org.ttzero.excel.entity.Column hc : columns) {
hc.fixedSize(width);
}
}
return this;
}
/**
* 设置斑马线填充样式,为了不影响正常阅读建议使用浅色,默认无斑马线
*
* @param fill 斑马线填充 {@link Fill}
* @return 当前工作表
*/
public Sheet setZebraLine(Fill fill) {
this.zebraFill = fill;
return this;
}
/**
* 取消斑马线,如果在工作薄Workbook设置了全局斑马线可使用此方法取消当前工作表Worksheet的斑马线
*
* @return 当前工作表
*/
public Sheet cancelZebraLine() {
this.zebraFill = null;
this.zebraFillStyle = 0;
return this;
}
/**
* 获取当前工作表的斑马线填充样式,如果当前工作表未设置则从全局工作薄中获取
*
* @return 斑马线填充 {@link Fill}
*/
public Fill getZebraFill() {
return zebraFill != null ? zebraFill : workbook.getZebraFill();
}
/**
* 获取斑马线样式值,它返回的是全局样式中斑马线填充样式的值,全局第{@code n}个填充返回 {@code n<<INDEX_FILL},
* 更多参考{@link Styles#addFill}
*
* @return 斑马线样式值
*/
public int getZebraFillStyle() {
if (zebraFillStyle < 0 && zebraFill != null) {
this.zebraFillStyle = workbook.getStyles().addFill(zebraFill);
}
return zebraFillStyle;
}
/**
* 设置默认斑马线,默认填充色HEX值为{@code E9EAEC}
*
* @return 当前工作表
*/
public Sheet defaultZebraLine() {
return setZebraLine(new Fill(PatternType.solid, new Color(233, 234, 236)));
}
/**
* 获取当前工作表的表名
*
* <p>注意:仅返回实例化Worksheet时指定的表名或通过 {@link #setName}方法设置的表名,
* 对于未指定的表名的工作表受分页和{@link Workbook#insertSheet(int, Sheet)}插入指定位置
* 影响只能在最终执行输出时确定位置,在此之前表名均返回{@code null}</p>
*
* @return 外部指定的表名,未指定表名时返回{@code null}
*/
public String getName() {
return name;
}
/**
* 设置工作表表名,使用Office打开文件时它将显示在底部的Tab栏
*
* <p>注意:内部不会检查重名,所以请在外部保证在一个工作薄下所有工作表名唯一,否则打开文件异常</p>
*
* @param name 工作表表名,最多31个字符超过时截取前31个字符
* @return 当前工作表
*/
public Sheet setName(String name) {
if (name != null && name.length() > 31) {
LOGGER.warn("The worksheet name is too long, maximum length of 31 characters. Currently {}", name.length());
name = name.substring(0, 31);
}
this.name = name;
return this;
}
/**
* 获取批注 {@link Comments}
*
* @return 如果添加了批注则返回 {@code Comments}对象否则返回{@code null}
*/
public Comments getComments() {
if (comments != null && comments.id == 0) {
comments.id = this.id;
}
return comments;
}
/**
* 创建批注对象,一般由各工作表输出协议创建,外部用户勿用
*
* @return {@code Comments}实体,与工作表一一对应
*/
public Comments createComments() {
if (comments == null) {
comments = workbook != null ? new Comments(id, workbook.getCreator()) : new Comments();
// FIXME Removed at excel version 2013
if (id > 0) {
addRel(new Relationship("../drawings/vmlDrawing" + id + Const.Suffix.VML, Const.Relationship.VMLDRAWING));
addRel(new Relationship("../comments" + id + Const.Suffix.XML, Const.Relationship.COMMENTS));
}
}
return comments;
}
/**
* 是否显示“网格线”
*
* @return true: 显示 false: 不显示
*/
public boolean isShowGridLines() {
return showGridLines == null || showGridLines;
}
/**
* 设置显示“网格线”
*
* @return 当前工作表
*/
public Sheet showGridLines() {
this.showGridLines = true;
return this;
}
/**
* 设置隐藏“网格线”
*
* @return 当前工作表
*/
public Sheet hideGridLines() {
this.showGridLines = false;
return this;
}
/**
* 获取表头行高,默认20.5
*
* @return 表头行高
*/
public double getHeaderRowHeight() {
return headerRowHeight;
}
/**
* 设置表头行高,其优化级低于{@link Column#setHeaderHeight}设置的值
*
* <p>可接受负数和零,负数等价与未设置默认行高为{@code 13.5},零效果等价于隐藏,但不能通过右建“取消隐藏”</p>
*
* @param headerRowHeight 指定表头行高,建议表头行高比数据行大
* @return 当前工作表
*/
public Sheet setHeaderRowHeight(double headerRowHeight) {
this.headerRowHeight = headerRowHeight;
return this;
}
/**
* 获取数据行高
*
* @return 数据行高,返回{@code null}时使用默认行高
*/
public Double getRowHeight() {
return rowHeight;
}
/**
* 设置数据行高,未指定或负数时默认行高为{@code 13.5}
*
* @param rowHeight 指定数据行高
* @return 当前工作表
*/
public Sheet setRowHeight(double rowHeight) {
this.rowHeight = rowHeight;
return this;
}
/**
* 获取工作表的起始行号(从1开始),这里是行号也就是打开Excel左侧看到的行号,
* 此行号将决定从哪一行开始写数据
*
* @return 起始行号
*/
public int getStartRowIndex() {
return Math.abs(startRowIndex);
}
/**
* 是否滚动到可视区,当起始行列不在{@code A1}时,如果返回{@code true}则打开Excel文件时
* 自动将首行首列滚动左上角第一个位置,如果返回{@code false}时打开Excel文件左上角可视区为{@code A1}
*
* @return {@code true}将首行首列滚动到左上角第一个位置,否则{@code A1}将为左上角第一个位置
*/
public boolean isScrollToVisibleArea() {
return startRowIndex > 0;
}
/**
* 指定起始行并将该行自动滚到窗口左上角,行号必须大于0
*
* @param startRowIndex 起始行号(从1开始)
* @return 当前工作表
*/
public Sheet setStartRowIndex(int startRowIndex) {
return setStartRowIndex(startRowIndex, true);
}
/**
* 指定起始行并设置是否将该行滚动到窗口左上角,行号必须大于0
*
* <p>默认情况下左上角一定是{@code A1},如果{@code scrollToVisibleArea=0}则打开文件时{@code StartRowIndex}
* 将会显示在窗口的第一行</p>
*
* @param startRowIndex 起始行号(从1开始)
* @param scrollToVisibleArea 是否滚动起始行到窗口左上角
* @return 当前工作表
*/
public Sheet setStartRowIndex(int startRowIndex, boolean scrollToVisibleArea) {
if (startRowIndex <= 0)
throw new IndexOutOfBoundsException("The start row index must be greater than 0, current = " + startRowIndex);
if (sheetWriter != null && sheetWriter.getRowLimit() <= startRowIndex)
throw new IndexOutOfBoundsException("The start row index must be less than row-limit, current(" + startRowIndex + ") >= limit(" + sheetWriter.getRowLimit() + ")");
this.startRowIndex = scrollToVisibleArea ? startRowIndex : -startRowIndex;
return this;
}
/**
* 获取表头,对于非外部传入的表头,只有要执行导出的时候通过行数据进行反射或读取Meta元数据获取,
* 在此之前该接口将返回{@code null}
*
* @return 表头信息
*/
public Column[] getColumns() {
return columns;
}
/**
* 添加进度观察者,在数据较大的导出过程中添加观察者打印进度可避免被误解为程序假死
*
* <pre>
* new ListSheet<>().onProgress((sheet, row) -> {
* System.out.println(sheet + " write " + row + " rows");
* })</pre>
*
* @param progressConsumer 进度消费窗口
* @return 当前工作表
*/
public Sheet onProgress(BiConsumer<Sheet, Integer> progressConsumer) {
this.progressConsumer = progressConsumer;
return this;
}
/**
* 获取进度观察者
*
* @return 如果设置了观察者则返回观察者否则返回 {@code null}
*/
public BiConsumer<Sheet, Integer> getProgressConsumer() {
return progressConsumer;
}
/**
* 获取表头,子类覆写此方法创建表头
*
* @return 表头信息
*/
protected Column[] getHeaderColumns() {
if (!headerReady) {
if (columns == null) {
columns = new Column[0];
}
}
return columns;
}
/**
* 获取表头,Worksheet工作表输出协议调用此方法来获取表头信息
*
* <p>此方法先调用内部{@link #getHeaderColumns}获取基础信息,然后对其进行排序,列反转,
* 合并等深加工处理</p>
*
* @return 加工好的表头
*/
public Column[] getAndSortHeaderColumns() {
if (!headerReady) {
// 获取表头基础信息
this.columns = getHeaderColumns();
// Ready Flag
headerReady |= (this.columns.length > 0);
if (headerReady) {
// 排序
sortColumns(columns);
// 计算每列在Excel中的列下标
calculateRealColIndex();
// 列反转,由于尾部Column包含必要的信息,多行表头时为方便获取主要信息这里进行一次反转
reverseHeadColumn();
// 合并,将相同列名的列进行合并
mergeHeaderCellsIfEquals();
// 重置通用属性
resetCommonProperties(columns);
// Check the limit of columns
checkColumnLimit();
}
// Reset Row limit
// this.rowLimit = sheetWriter.getRowLimit() - (nonHeader == 1 || columns.length == 0 ? 0 : columns[0].subColumnSize()) - getStartRowIndex() + 1
// Mark ext-properties
markExtProp();
}
return columns;
}
protected void resetCommonProperties(Column[] columns) {
for (Column column : columns) {
if (column == null) continue;
if (column.styles == null) column.styles = workbook.getStyles();
if (column.next != null) {
for (Column col = column.next; col != null; col = col.next)
col.styles = workbook.getStyles();
}
// Column width
if (column.getAutoSize() == 0 && autoSize > 0) {
column.option |= autoSize << 1;
}
}
}
/**
* 列排序,首先会根据用户指定的{@code colIndex}进行一次排序,未指定{@code colIndex}的列排在最后,
* 然后将尾部没有{@code colIndex}的列插入到数组前方不连续的空白位,如果有重复的{@code colIndex}则按
* 列在当前数组中的顺序依次排序
*
* <p>示例:现有A:1,B,C:4,D,E五列,其中A的{@code colIndex=1},C的{@code colIndex=4}</p>
*
* <p>第一轮按{@code colIndex}排序后结果为 => {@code A:1,C:4,B,D,E}</p>
*
* <p>第二轮将尾部没有{@code colIndex}的BDE列插入到前方空白位,A在第1列它前方可以插入B,
* A:1和C:4之间有2,3两个空白位,将DE分别插入到2,3位,现在结果为 => {@code B:0,A:1,D:2,E:3,C:4}</p>
*
* @param columns 表头信息
*/
protected void sortColumns(Column[] columns) {
if (columns.length <= 1) return;
int j = 0;
for (int i = 0; i < columns.length; i++) {
if (columns[i].getTail().colIndex >= 0) {
int n = search(columns, j, columns[i].getTail().colIndex);
if (n < i) insert(columns, n, i);
j++;
}
}
// Finished
if (j == columns.length) return;
int n = columns[0].getTail().colIndex;
for (int i = 0; i < columns.length && j < columns.length; ) {
if (n > i) {
for (int k = Math.min(n - i, columns.length - j); k > 0; k--, j++)
insert(columns, i++, j);
} else i++;
if (i < columns.length) n = columns[i].getTail().colIndex;
}
}
protected int search(Column[] columns, int n, int k) {
int i = 0;
for (; i < n && columns[i].getTail().colIndex <= k; i++) ;
return i;
}
protected void insert(Column[] columns, int n, int k) {
Column t = columns[k];
System.arraycopy(columns, n, columns, n + 1, k - n);
columns[n] = t;
}
/**
* 计算列的实际下标,Excel下标从1开始,计算后的值将重置{@link Column#realColIndex}属性,
* 该属性将最终输出到Excel文件{@code col}属性中
*/
protected void calculateRealColIndex() {
for (int i = 0; i < columns.length; i++) {
Column hc = columns[i].getTail();
hc.realColIndex = hc.colIndex;
if (i > 0 && columns[i - 1].realColIndex >= hc.realColIndex)
hc.realColIndex = columns[i - 1].realColIndex + 1;
else if (hc.realColIndex <= i) hc.realColIndex = i + 1;
else hc.realColIndex = hc.colIndex + 1;
if (hc.prev != null) {
for (Column col = hc.prev; col != null; col = col.prev)
col.realColIndex = hc.realColIndex;
}
}
}
/**
* 设置表头,无数据时依然会导出该表头
*
* @param columns 表头数组
* @return 当前工作表
*/
public Sheet setColumns(final Column ... columns) {
this.columns = columns;
return this;
}
/**
* 设置表头,无数据时依然会导出该表头
*
* @param columns 表头数组
* @return 当前工作表
*/
public Sheet setColumns(List<Column> columns) {
if (columns != null && !columns.isEmpty()) {
this.columns = new Column[columns.size()];
columns.toArray(this.columns);
}
return this;
}
/**
* 获取水印
*
* @return 水印对象 {@link WaterMark}
*/
public WaterMark getWaterMark() {
return waterMark;
}
/**
* 设置水印,优先级高于Workbook中的全局水印
*
* @param waterMark 水印对象 {@link WaterMark}
* @return 当前工作表
*/
public Sheet setWaterMark(WaterMark waterMark) {
this.waterMark = waterMark;
return this;
}
/**
* 工作表是否隐藏
*
* @return true: 隐藏, false: 显示
*/
public boolean isHidden() {
return hidden;
}
/**
* 隐藏工作表
*
* @return 当前工作表
*/
public Sheet hidden() {
this.hidden = true;
return this;
}
/**
* 获取强制导出标识,只对{@link ListSheet}生效,用于
*
* @return 1: 强制导出 其它值均表示不强制导出
*/
public int getForceExport() {
return 0;
}
/**
* 回闭连接,回收资源,删除临时文件等
*
* @throws IOException if I/O error occur
*/
public void close() throws IOException {
if (sheetWriter != null) {
sheetWriter.close();
}
}
/**
* 落盘,将工作表写到指定路径
*
* @param path 指定保存路径
* @throws IOException if I/O error occur
*/
@Override
public void writeTo(Path path) throws IOException {
if (sheetWriter == null) {
throw new ExcelWriteException("Worksheet writer is not instanced.");
}
if (!headerReady) {
getAndSortHeaderColumns();
}
if (rowBlock == null) {
rowBlock = new RowBlock(getRowBlockSize());
} else rowBlock.reopen();
if (!copySheet) {
paging();
}
sheetWriter.writeTo(path);
}
/**
* 分批拉取数据
*/
protected void paging() { }
/**
* 添加关联,当工作表需要引入其它资源时必须将其添加进关联关系中,关联关系由{@link RelManager}管理。
*
* <p>例如:向工作表添加图片时,图片由media统一存放,工作表中只需要加入图片的关联关系,通过rId值找到图片。
* 除了图片外,像批注,公式,图表等都属于外部资源</p>
*
* @param rel 关联关系 {@link Relationship}
* @return 当前工作表
*/
public Sheet addRel(Relationship rel) {
relManager.add(rel);
return this;
}
/**
* 通过相对位置模糊匹配查找关联关系
*
* @param key 要查询的关联key
* @return 第一个匹配的关联,如果未匹配则返回{@code null}
*/
public Relationship findRel(String key) {
return relManager.likeByTarget(key);
}
/**
* 获取当前工作表的关系管理器
*
* @return {@link RelManager}