All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ttzero.excel.entity.Workbook Maven / Gradle / Ivy

/*
 * Copyright (c) 2017, [email protected] 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.ttzero.excel.entity.csv.CSVWorkbookWriter;
import org.ttzero.excel.entity.e7.ContentType;
import org.ttzero.excel.entity.e7.XMLWorkbookWriter;
import org.ttzero.excel.entity.style.Fill;
import org.ttzero.excel.entity.style.PatternType;
import org.ttzero.excel.entity.style.Styles;
import org.ttzero.excel.manager.docProps.Core;
import org.ttzero.excel.manager.docProps.CustomProperties;
import org.ttzero.excel.util.FileUtil;
import org.ttzero.excel.util.StringUtil;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.function.BiConsumer;

import static org.ttzero.excel.util.FileUtil.exists;

/**
 * 一个{@code Workbook}工作薄实例即表示一个Excel文件,它包含一个或多个{@link Sheet}工作表,
 * Workbook收集全局属性,如文档属性、样式,字符串共享区等。
 *
 * 

在导出Excel文件时需要遵循以下三个步骤:

*
    *
  1. 设置文件属性(非必须)
  2. *
  3. 调用{@link #addSheet}添加Worksheet工作表(必须)
  4. *
  5. 调用{@link #writeTo}方法来执行写入操作(必须)
  6. *
*

当前仅支持xlsx(默认)和csv格式输出,保存为csv格式须在调用writeTo之前调用{@link #saveAsCSV()}方法, * 如果当前Workbook包含多个Worksheet则会生成多个csv文件并将多个文件压缩为zip格式。

* *

常用的属性包含{@link #setCreator}设置作者, {@link #setCompany}设置公司名, * {@link #setAutoSize}设置自适应列宽和{@link #setZebraLine}设置斑马线, * 前两种需要打开文件详细属性查看,后两种起美化作用有利于阅读,后两个属性可以设置到Workbook或各Worksheet中, * 如果设置到Workbook则会应用于所有Worksheet,当Workbook和Worksheet均设置了同一属性则Worksheet优先。

* *

{@link #writeTo}方法是一个终止符它将执行实际的写操作,所以需要将该方法放在所有语句之后, * 任何放置在该方法之后的指令将会被忽略。

* *

本工具将数据源和输出协议分开设计,工作表Sheet为数据源,{@link IWorkbookWriter}和{@link IWorksheetWriter} * 为输出协议,实现不同的输出协议即实现不同格式化输出,已实现的{@link org.ttzero.excel.entity.e7.XMLWorksheetWriter}和 * {@link org.ttzero.excel.entity.csv.CSVWorksheetWriter}就是xlsx和csv格式的输出协议实现。 * 工作薄Workbook对应的输出协议为{@link IWorkbookWriter},它负责协调所有部件输出并将所有零散的文件组装为OpenXml格式。

* *

一个典型的导出示例: *

 * new Workbook("双11销量统计")
 *     // 设置作者
 *     .setCreator("作者")
 *     // 设置自适应列宽
 *     .setAutoSize(true)
 *     // 添加一个名为"总销量排行"的Worksheet
 *     .addSheet(new ListSheet<Item>("总销量排行")
 *         .setData(new ArrayList<>())) // <- 这里替换为实际数据
 *     // 添加一个名为"单品销量排行"的Worksheet
 *     .addSheet(new ListMapSheet<>("单品销量排行")
 *         .setData(new ArrayList<>())) // <- 这里替换为实际数据
 *     // 指定输出路径 '/tmp/"双11销量统计".xlsx'
 *     .writeTo(Paths.get("/tmp/"));
* *

参考文档:

*

POI

*

Office 365

*

DocumentFormat.OpenXml.Spreadsheet Namespace

*

介绍 Microsoft Office (2007) Open XML 文件格式

* * @author guanquan.wang on 2017/9/26. */ public class Workbook implements Storable { /** * 工作薄名 */ private String name; /** * 工作表数组,按数组顺序输出 */ private Sheet[] sheets; /** * 水印 */ private WaterMark waterMark; /** * 记录工作表几数 */ private int size; /** * 全局自适应列宽标识 */ private boolean autoSize; /** * 作者,未指定时将默认取当前系统登录名 */ private String creator; /** * 工作薄属性 */ private Core core; /** * 公司名 */ private String company; /** * 全局斑马线 */ private Fill zebraFill; /** * 导出进度监控器 */ private BiConsumer progressConsumer; /** * 全局字符串共享区 */ private SharedStrings sst; /** * 全局样式 */ private Styles styles; /** * WorkbookWriter输出协议,输出协议影响最终的文件格式 */ private IWorkbookWriter workbookWriter; /** * 强制导出,绕过安全限制导出全字段 */ private int forceExport; /** * 全局ContentType */ private final ContentType contentType; /** * 全局Drawing记数器 */ private int drawingCounter; /** * 全局多媒体记数器(当前仅支持图片) */ private int mediaCounter; /** * 自定义属性 */ private CustomProperties customProperties; /** * 创建一个未命名工作薄 * *

如果writeTo方法指定的File或Path为文件夹时,未命名工作薄将会以'新建文件'作为文件名

*/ public Workbook() { this(null); } /** * 创建一个工作薄并指定名称 * * @param name 工作薄名 */ public Workbook(String name) { this(name, null); } /** * 创建一个工作薄并指定名称和作者 * * @param name 工作薄名 * @param creator 作者,默认使用{@code System.getProperty("user.name")}命令获取系统当前登录用户名. */ public Workbook(String name, String creator) { this.name = name; this.creator = creator; sheets = new Sheet[3]; // Create three worksheet contentType = new ContentType(); } /** * 获取当前工作薄名称 * * @return 工作薄名称 */ public String getName() { return name; } /** * 设置工作薄名称,如果writeTo方法指定的Path或File为文件夹时该名称将作为最终文件名 * * @param name 工作薄名,长度最好不超过255个字符 * @return 当前工作薄 */ public Workbook setName(String name) { this.name = name; return this; } /** * 获取当前工作薄作者 * * @return 作者 */ public String getCreator() { return creator; } /** * 获取当前工作薄公司名 * * @return 公司名 */ public String getCompany() { return company; } /** * 获取当前工作薄包含的工作表个数 * * @return 工作表个数 */ public int getSize() { return size; } /** * 获取文档属性,包含主题,关键词,分类等信息 * * @return 文档属性 */ public Core getCore() { return core; } /** * 设置文档属性,包含主题,关键词,分类等信息 * * @param core 文档属性 * @return 当前工作薄 */ public Workbook setCore(Core core) { this.core = core; return this; } /** * 获取全局字符串共享区,此共享区独立于Worksheet,所有worksheet共享 * * @return 全局字符串共享区{@link SharedStrings} */ public SharedStrings getSharedStrings() { // CSV do not need SharedStringTable if (!(workbookWriter instanceof CSVWorkbookWriter) && sst == null) sst = new SharedStrings(); return sst; } /** * 获取所有{@link Sheet}集合 * *

注意:返回的对象是一个浅拷贝对其做任何修改将影响最终效果

* * @return {@link Sheet}集合 */ public final Sheet[] getSheets() { return Arrays.copyOf(sheets, size); } /** * 获取水印{@link WaterMark} * * @return 水印 */ public WaterMark getWaterMark() { return waterMark; } /** * 设置水印{@link WaterMark},可以使用{@link WaterMark#of}静态方法创建 * * @param waterMark 水印 * @return 当前工作薄 */ public Workbook setWaterMark(WaterMark waterMark) { this.waterMark = waterMark; return this; } /** * 设置全局自适应列宽 * * @param autoSize true: 自适应宽度,false:固定宽度(默认) * @return 当前工作薄 */ public Workbook setAutoSize(boolean autoSize) { this.autoSize = autoSize; return this; } /** * 获取当前工作薄是否为自适应宽度 * * @return true: 自适应宽度,false:固定宽度 */ public boolean isAutoSize() { return autoSize; } /** * 强制导出 * *

注意:设置此标记后将无视安全规则导出Java对象中的所有字段,请根据实际情况谨慎使用

* * @return 当前工作薄 */ public Workbook forceExport() { this.forceExport = 1; return this; } /** * 获取当前工作薄是否为“强制导出” * * @return 强制导出时返回1,其它情况返回0 */ public int getForceExport() { return forceExport; } /** * 获取全局样式{@link Styles} * * @return 全局样式 */ public Styles getStyles() { // CSV do not need Styles if (styles == null && !(workbookWriter instanceof CSVWorkbookWriter)) styles = Styles.create(); return styles; } /** * 设置全局样式{@link Styles} * * @param styles 定制化全局样式 * @return 当前工作薄 */ public Workbook setStyles(Styles styles) { this.styles = styles; return this; } /** * 设置作者 * *

默认使用{@code System.getProperty("user.name")}命令获取系统当前登录用户名,这通常不是一个好主意。 * 应用程序一般发布在容器或云服务器上,获取系统登录用户名是没有意义的。如果不想设置它并默认设置系统登录用户名, * 可以将其设置为空字符串("")。

* *

后续会去掉获取系统登录用户名的逻辑以防止信息泄露

* * @param creator 作者 * @return 当前工作薄 */ public Workbook setCreator(String creator) { this.creator = creator; return this; } /** * 设置公司名,建议控制在64个字符以内 * * @param company 公司名 * @return 当前工作薄 */ public Workbook setCompany(String company) { this.company = company; return this; } /** * 设置斑马线背景,斑马线是由相同间隔的背景色造成的视觉效果,有助于从视觉上区分每行数据, * 但刺眼的背景色可能造成相反的效果,设置之前最好在Office中提前预览效果 * * @param fill 背景样式{@link Fill} * @return 当前工作薄 */ public Workbook setZebraLine(Fill fill) { this.zebraFill = fill; return this; } /** * 取消斑马线 * * @return 当前工作薄 */ public Workbook cancelZebraLine() { this.zebraFill = null; return this; } /** * 指定以默认斑马线输出,默认背景颜色为{@code #EFF5EB} * * @return 当前工作薄 */ public Workbook defaultZebraLine() { return setZebraLine(new Fill(PatternType.solid, new Color(233, 234, 236))); } /** * 获取斑马线背景样式 * * @return 斑马线背景 {@link Fill}样式 */ public Fill getZebraFill() { return zebraFill; } /** * 判断当前工作薄是否设置了全局斑马线背景 * * @return true: 有全局斑马线 */ public boolean hasZebraFill() { return zebraFill != null && zebraFill.getPatternType() != PatternType.none; } /** * 获取工作薄输出协议{@link IWorkbookWriter} * * @return 工作薄输出协议 */ public IWorkbookWriter getWorkbookWriter() { if (workbookWriter == null) workbookWriter = new XMLWorkbookWriter(this); return workbookWriter; } /** * 添加一个工作表{@link Sheet},新添加的工作表总是排在队列最后, * 可以使用{@link #insertSheet}插入到指定位置 * * @param sheet 工作表 * @return 当前工作薄 */ public Workbook addSheet(Sheet sheet) { ensureCapacityInternal(); sheet.setWorkbook(this); sheets[size++] = sheet; return this; } /** * 在指定下标插入一个工作表{@link Sheet} * * @param index 指定工作表插入的位置(从0开始) * @param sheet 待插入的工作表 * @return 当前工作薄 */ public Workbook insertSheet(int index, Sheet sheet) { ensureCapacityInternal(); int _size = size; if (sheets[index] != null) { for (; _size > index; _size--) { sheets[_size] = sheets[_size - 1]; sheets[_size].setId(sheets[_size].getId() + 1); } } sheets[index] = sheet; sheet.setId(index + 1); sheet.setWorkbook(this); size++; return this; } /** * 移除指定位置的工作表{@link Sheet} * * @param index 待移除的工作表下标(从0开始) * @return 当前工作薄 */ public Workbook remove(int index) { if (index < 0 || index >= size) { return this; } if (index == size - 1) { sheets[index] = null; } else { for (; index < size - 1; index++) { sheets[index] = sheets[index + 1]; sheets[index].setId(sheets[index].getId() - 1); } } size--; return this; } /** * 获取指定位置的工作表 * *

如果使用{@link #insertSheet}方法插入了一个较大的下标,调用此方法可能返回null值。 * 例如在下标为100的位置插入了一个工作表,获取第90位的工作薄将返回一个null值。

* * @param index 工作表在队列中的位置(从0开始) * @return 指定位置的工作表 {@link Sheet} * @throws IndexOutOfBoundsException 如果下标为负数或者超过工作薄队列长度 */ public Sheet getSheetAt(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); return sheets[index]; } /** * 返回指定名称的工作表{@link Sheet} * *

注意:只能查找那些在创建时设置了名称的工作表

* * @param sheetName 待查找的工作表名称 * @return 按工作表名称查询,未找到时返回{code null} */ public Sheet getSheet(String sheetName) { if (StringUtil.isEmpty(sheetName)) return null; for (Sheet sheet : sheets) { if (sheetName.equals(sheet.getName())) { return sheet; } } return null; } /** * 添加一个进度监听器,可以在较大导出时展示进度 * *
     * new Workbook().onProgress((sheet, row) -> {
     *     System.out.println(sheet + " write " + row + " rows");
     * })
* * @param progressConsumer 进度监听器 * @return 当前工作薄 */ public Workbook onProgress(BiConsumer progressConsumer) { this.progressConsumer = progressConsumer; return this; } /** * 获取进度监听器 * * @return 如果设置了监听器就返回,未设置时返回null */ public BiConsumer getProgressConsumer() { return progressConsumer; } // /** // * Save as excel97~2003 // *

// * You mast add eec-e3-support.jar into class path to support excel97~2003 // * // * @return 当前工作薄 // * @throws OperationNotSupportedException if eec-e3-support not import into class path // */ // public Workbook saveAsExcel2003() throws OperationNotSupportedException { // try { // // Create Styles and SharedStringTable // Class clazz = Class.forName("org.ttzero.excel.entity.e3.BIFF8WorkbookWriter"); // Constructor constructor = clazz.getDeclaredConstructor(this.getClass()); // workbookWriter = (IWorkbookWriter) constructor.newInstance(this); // } catch (Exception e) { // throw new OperationNotSupportedException("Excel97-2003 Not support now."); // } // return this; // } /** * 另存为Comma-Separated Values格式,默认使用','逗号分隔 * * @return 当前工作薄 */ public Workbook saveAsCSV() { workbookWriter = new CSVWorkbookWriter(this); return this; } /** * 另存为Comma-Separated Values格式并保存BOM,默认使用','逗号分隔 * * @return 当前工作薄 */ public Workbook saveAsCSVWithBom() { workbookWriter = new CSVWorkbookWriter(this, true); return this; } /** * 以指定字符集保存为Comma-Separated Values格式,默认使用','逗号分隔 * * @param charset 指定输出字符集 * @return 当前工作薄 */ public Workbook saveAsCSV(Charset charset) { workbookWriter = new CSVWorkbookWriter(this).setCharset(charset); return this; } /** * 以指定字符集保存为Comma-Separated Values格式并保存BOM,默认使用','逗号分隔 * * @param charset 指定输出字符集 * @return 当前工作薄 */ public Workbook saveAsCSVWithBom(Charset charset) { workbookWriter = new CSVWorkbookWriter(this, true).setCharset(charset); return this; } /** * 确认边距并在越界时自动扩容 */ private void ensureCapacityInternal() { if (size >= sheets.length) { sheets = Arrays.copyOf(sheets, size + 1); } } //////////////////////////Print Out///////////////////////////// /** * 指定输出路径,Path可以是文件夹或者文件 * *

如果Path为文件夹时将在该文件夹下生成名为{@link #getName()} + {@link IWorkbookWriter#getSuffix()}的文件, * 文件后缀随输出协议变动。如果存在相同的文件名则会在文件名后面添加'(n)',n为自增的数字,例已存在"abc.xlsx", * 再次导出时将保存为"abc(1).xlsx"。如果Path为明确的文件绝对路径,那将保存在Path的绝对路径下,已存在相同文件时会覆盖原文件, * 需要注意覆盖失败的情况

* * @param path Excel保存位置 * @throws IOException I/O操作异常 */ @Override public void writeTo(Path path) throws IOException { checkAndInitWriter(); if (!exists(path)) { String name = path.getFileName().toString(); // write to file if (name.indexOf('.') > 0) { Path parent = path.getParent(); FileUtil.mkdir(parent); writeTo(path.toFile()); return; // write to directory } else FileUtil.mkdir(path); } else if (!Files.isDirectory(path)) { writeTo(path.toFile()); return; } try { workbookWriter.writeTo(path); } finally { workbookWriter.close(); } } /** * 导出到{@link OutputStream}流,适用于小文件Excel直接导出的场景 * *
     * public void export(HttpServletResponse response) throws IOException {
     *     String fileName = java.net.URLEncoder.encode("abc.xlsx", "UTF-8");
     *     response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""
     *         + fileName + "\"; filename*=utf-8''" + fileName);
     *     new Workbook()
     *         .addSheet(new ListSheet<Item>("总销量排行", new ArrayList<>()))
     *         // 直接写到Response
     *         .writeTo(response.getOutputStream());
     * }
* * @param os 输出流 * @throws IOException I/O操作异常 * @throws ExcelWriteException 其它运行时异常 */ public void writeTo(OutputStream os) throws IOException, ExcelWriteException { checkAndInitWriter(); try { workbookWriter.writeTo(os); } finally { workbookWriter.close(); } } /** * 指定输出路径,File可以是文件夹或者文件 * *

如果File为文件夹时将在该文件夹下生成名为{@link #getName()} + {@link IWorkbookWriter#getSuffix()}的文件, * 文件后缀随输出协议变动。如果存在相同的文件名则会在文件名后面添加'(n)',n为自增的数字,例已存在"abc.xlsx", * 再次导出时将保存为"abc(1).xlsx"。如果File为明确的文件绝对路径,那将保存在File的绝对路径下,已存在相同文件时会覆盖原文件, * 需要注意覆盖失败的情况

* * @param file Excel保存位置 * @throws IOException I/O操作异常 * @throws ExcelWriteException 其它运行时异常 */ public void writeTo(File file) throws IOException, ExcelWriteException { checkAndInitWriter(); if (!file.getParentFile().exists()) { FileUtil.mkdir(file.toPath().getParent()); } try { workbookWriter.writeTo(file); } finally { workbookWriter.close(); } } /** * 设置自定义工作薄输出协议 * * @param workbookWriter 自定义工作薄{@link IWorkbookWriter}协议 * @return 当前工作薄 */ public Workbook setWorkbookWriter(IWorkbookWriter workbookWriter) { this.workbookWriter = workbookWriter; this.workbookWriter.setWorkbook(this); return this; } /** * 初始化,创建全局样式和字符串共享区 * @deprecated 不需要主动初始化,后续将删除 */ @Deprecated protected void init() { // 创建全局字符串共享区 if (sst == null) { sst = new SharedStrings(); } // 创建全局样式 if (styles == null) { styles = Styles.create(); } } /** * 检查并创建工作薄协议{@link IWorkbookWriter} */ protected void checkAndInitWriter() { if (workbookWriter == null) { // // 初始化 // init(); workbookWriter = new XMLWorkbookWriter(this); } } /** * 添加资源类型,导出图片时按照图片格式添加不同的资源类型,一般情况下开发者不需要关心 * * @param type 资源类型{@link ContentType.Type} * @return 当前工作薄 */ public Workbook addContentType(ContentType.Type type) { contentType.add(type); return this; } /** * 添加ContentType关系,一般情况下开发者不需要关心 * * @param rel {@link Relationship}关系 * @return 当前工作薄 */ public Workbook addContentTypeRel(Relationship rel) { contentType.addRel(rel); return this; } /** * 获取全局的资源类型,一般情况下开发者不需要关心 * * @return 资源类型{@link ContentType}对象 */ public ContentType getContentType() { return contentType; } /** * 图片记数器自增 * * @return 图片记数器 */ public int incrementDrawingCounter() { return ++drawingCounter; } /** * 获取当前工作薄包含多少张图片 * * @return 图片数量 */ public int getDrawingCounter() { return drawingCounter; } /** * 媒体记数器,一般情况下media与worksheet对应 * * @return 媒体记数器 */ public int incrementMediaCounter() { return ++mediaCounter; } /** * 获取当前工作薄含有多媒体的工作表个数 * * @return 含有多媒体的工作表个数 */ public int getMediaCounter() { return mediaCounter; } /** * 添加自定义属性,自定义属性可以从"信息"->"属性"->"自定义属性"查看 * *

注意:只支持{@code "文本"}、{@code "数字"}、{@code "日期"}以及{@code "布尔值"},其它数据类型将使用{@code toString}强转换为文本

* * @param key 属性名,不超过{@code 256}个字符 * @param value 属性值, * @return 当前工作表 */ public Workbook putCustomProperty(String key, Object value) { if (customProperties == null) customProperties = new CustomProperties(); customProperties.put(key, value); return this; } /** * 添加自定义属性,自定义属性可以从"信息"->"属性"->"自定义属性"查看 * *

注意:只支持{@code "文本"}、{@code "数字"}、{@code "日期"}以及{@code "布尔值"},其它数据类型将使用{@code toString}强转换为文本

* * @param properties 批量属性 * @return 当前工作表 */ public Workbook putCustomProperties(Map properties) { if (customProperties == null) customProperties = new CustomProperties(); customProperties.putAll(properties); return this; } /** * 删除自定义属性 * * @param key 指定属性名 * @return 如果属性存在则返回属性值否则返回 {@code null} */ public Object removeCustomProperty(String key) { return customProperties != null ? customProperties.remove(key) : null; } /** * 获取自定义属性类 * * @return {@code Custom}自定义属性类 */ public CustomProperties getCustomProperties() { return customProperties; } /** * 文档保护-标记只读 * * @return 当前工作表 */ public Workbook markAsReadOnly() { if (customProperties == null) customProperties = new CustomProperties(); customProperties.markAsReadOnly(); return this; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy