Browse Source

Merge branch 'tm_2478' into master

pull/2478/head
Jiaju Zhuang 2 years ago committed by GitHub
parent
commit
b3442e700b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      .gitee/ISSUE_TEMPLATE.zh-CN.md
  2. 10
      .github/ISSUE_TEMPLATE/bug.md
  3. 12
      .github/ISSUE_TEMPLATE/question.md
  4. 6
      .github/ISSUE_TEMPLATE/suggest.md
  5. 15
      .github/workflows/ci.yml
  6. 55
      .github/workflows/release.yml
  7. 26
      .github/workflows/sync2gitee.yml
  8. 227
      README.md
  9. 1
      README_EN.md
  10. 53
      abouteasyexcel.md
  11. 1
      easyexcel-core/pom.xml
  12. 123
      easyexcel-core/src/main/java/com/alibaba/excel/EasyExcelFactory.java
  13. 5
      easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/FormulaRecordHandler.java
  14. 3
      easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java
  15. 46
      easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java
  16. 5
      easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/CellTagHandler.java
  17. 31
      easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/HyperlinkTagHandler.java
  18. 79
      easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java
  19. 10
      easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/XlsxRowHandler.java
  20. 19
      easyexcel-core/src/main/java/com/alibaba/excel/constant/EasyExcelConstants.java
  21. 42
      easyexcel-core/src/main/java/com/alibaba/excel/constant/ExcelXmlConstants.java
  22. 9
      easyexcel-core/src/main/java/com/alibaba/excel/converters/date/DateNumberConverter.java
  23. 5
      easyexcel-core/src/main/java/com/alibaba/excel/converters/localdatetime/LocalDateNumberConverter.java
  24. 5
      easyexcel-core/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java
  25. 40
      easyexcel-core/src/main/java/com/alibaba/excel/enums/ReadDefaultReturnEnum.java
  26. 3
      easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/CellData.java
  27. 37
      easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/ReadCellData.java
  28. 8
      easyexcel-core/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java
  29. 12
      easyexcel-core/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java
  30. 81
      easyexcel-core/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java
  31. 12
      easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java
  32. 2
      easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java
  33. 17
      easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java
  34. 8
      easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java
  35. 9
      easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java
  36. 13
      easyexcel-core/src/main/java/com/alibaba/excel/read/processor/DefaultAnalysisEventProcessor.java
  37. 46
      easyexcel-core/src/main/java/com/alibaba/excel/util/ConverterUtils.java
  38. 71
      easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java
  39. 2
      easyexcel-core/src/main/java/com/alibaba/excel/util/FileUtils.java
  40. 8
      easyexcel-core/src/main/java/com/alibaba/excel/write/metadata/RowData.java
  41. 2
      easyexcel-support/pom.xml
  42. 1
      easyexcel-test/pom.xml
  43. 86
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/compatibility/CompatibilityTest.java
  44. 44
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/extra/ExtraDataTest.java
  45. 52
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/nomodel/NoModelDataTest.java
  46. 129
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java
  47. 143
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/rare/WriteTest.java
  48. 42
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
  49. 1
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/DemoData.java
  50. 109
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java
  51. 35
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData2.java
  52. 24
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData3.java
  53. 314
      easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java
  54. BIN
      easyexcel-test/src/test/resources/compatibility/t02.xlsx
  55. BIN
      easyexcel-test/src/test/resources/compatibility/t03.xlsx
  56. BIN
      easyexcel-test/src/test/resources/compatibility/t04.xlsx
  57. BIN
      easyexcel-test/src/test/resources/compatibility/t05.xlsx
  58. BIN
      easyexcel-test/src/test/resources/compatibility/t06.xlsx
  59. BIN
      easyexcel-test/src/test/resources/compatibility/t07.xlsx
  60. BIN
      easyexcel-test/src/test/resources/extra/extraRelationships.xlsx
  61. 1
      easyexcel/pom.xml
  62. 26
      pom.xml
  63. 49
      update.md

9
.gitee/ISSUE_TEMPLATE.zh-CN.md

@ -0,0 +1,9 @@
# 建议先去看文档
[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/)
# 异常代码
```java
这里写你的代码
```
# 异常提示
大家尽量把问题一次性描述清楚,然后贴上全部异常,这样方便把问题一次性解决掉。
# 其他描述

10
.github/ISSUE_TEMPLATE/bug.md

@ -7,12 +7,12 @@ assignees: zhuangjiaju
---
**建议先去看文档**
[快速开始](https://www.yuque.com/easyexcel/doc/easyexcel) 、[常见问题](https://www.yuque.com/easyexcel/faq)
**触发场景描述**
# 建议先去看文档
[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/)
# 触发场景描述
**触发Bug的代码**
# 触发Bug的代码
```java
这里写代码
```
**提示的异常或者没有达到的效果**
# 提示的异常或者没有达到的效果

12
.github/ISSUE_TEMPLATE/question.md

@ -7,12 +7,12 @@ assignees: ''
---
**建议先去看文档**
[快速开始](https://www.yuque.com/easyexcel/doc/easyexcel) 、[常见问题](https://www.yuque.com/easyexcel/faq)
**异常代码**
# 建议先去看文档
[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/)
# 异常代码
```java
这里写你的代码
```
**异常提示**
请提供完整的异常提示,记住是全部异常!
**建议描述**
# 异常提示
大家尽量把问题一次性描述清楚,然后贴上全部异常,这样方便把问题一次性解决掉。
# 问题描述

6
.github/ISSUE_TEMPLATE/suggest.md

@ -7,6 +7,6 @@ assignees: ''
---
**建议先去看文档**
[快速开始](https://www.yuque.com/easyexcel/doc/easyexcel) 、[常见问题](https://www.yuque.com/easyexcel/faq)
**建议描述**
# 建议先去看文档
[快速开始](https://easyexcel.opensource.alibaba.com/docs/current/) 、[常见问题](https://easyexcel.opensource.alibaba.com/qa/)
# 建议描述

15
.github/workflows/ci.yml

@ -30,18 +30,19 @@ jobs:
name: Test JDK ${{ matrix.java }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@main
- name: Set up JDK
uses: actions/setup-java@v2
uses: actions/setup-java@main
with:
java-version: ${{ matrix.java }}
distribution: ${{ matrix.distribution }}
- name: Cache Maven
uses: actions/cache@v2
- name: Cache local Maven repository
uses: actions/cache@v3
with:
path: ~/.m2
key: m2
restore-keys: m2
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2
- name: Chmod
run: chmod +x mvnw
- name: Test with Maven

55
.github/workflows/release.yml

@ -0,0 +1,55 @@
#
# Copyright 2009-2021 the original author or authors.
#
# 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.
#
name: Publish package to the Maven Central Repository
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@main
- name: Install Java and Maven
uses: actions/setup-java@main
with:
java-version: 8
distribution: 'adopt'
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- name: Cache local Maven repository
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2
- id: install-secret-key
name: Install GPG secret key
run: |
cat <(echo -e "${{ secrets.GPG_PRIVATE_KEY }}") | gpg --batch --import
- name: Publish package
run: |
mvn --batch-mode -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} clean deploy -Dmaven.test.skip=true -Dmaven.javadoc.skip=false -Dgpg.skip=false
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}

26
.github/workflows/sync2gitee.yml

@ -0,0 +1,26 @@
# 通过 Github action, 在仓库的每一次 commit 后自动同步到 Gitee 上
name: Mirror the Github organization repos to Gitee
on: [push, pull_request]
jobs:
repo-sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
with:
persist-credentials: false
- name: Mirror the Github organization repos to Gitee.
uses: Yikun/hub-mirror-action@master
with:
# 必选,需要同步的 Github 这里记住选择的是仓库 或者账号 而不是具体的项目
src: github/alibaba
# 必选,需要同步到的 Gitee 这里记住选择的是仓库 或者账号 而不是具体的项目
dst: gitee/easyexcel
# 必选,Gitee公钥对应的私钥,https://gitee.com/profile/sshkeys
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
# 必选,Gitee对应的用于创建仓库的token,https://gitee.com/profile/personal_access_tokens
dst_token: ${{ secrets.GITEE_TOKEN }}
# 如果是组织,指定组织即可,默认为用户 user
account_type: org
# 需要同步的仓库里面的项目
static_list: "easyexcel"

227
README.md

@ -3,101 +3,125 @@ EasyExcel
[![Build Status](https://github.com/alibaba/easyexcel/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/alibaba/easyexcel/actions/workflows/ci.yml?query=branch%3Amaster)
[![Maven central](https://maven-badges.herokuapp.com/maven-central/com.alibaba/easyexcel/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.alibaba/easyexcel)
[![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![](https://img.shields.io/badge/EasyExcel-%E6%9F%A5%E7%9C%8B%E8%B4%A1%E7%8C%AE%E6%8E%92%E8%A1%8C%E6%A6%9C-orange)](https://opensource.alibaba.com/contribution_leaderboard/details?projectValue=easyexcel)
# JAVA解析Excel工具
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
# 网站
* 官方网站:[https://easyexcel.opensource.alibaba.com/](https://easyexcel.opensource.alibaba.com/)
* github地址:[https://github.com/alibaba/easyexcel](https://github.com/alibaba/easyexcel)
* gitee地址:[https://gitee.com/easyexcel/easyexcel](https://gitee.com/easyexcel/easyexcel)
# 64M内存20秒读取75M(46W行25列)的Excel(3.0.2+版本)
当然还有[极速模式](https://easyexcel.opensource.alibaba.com/qa/read#%E5%BC%80%E5%90%AF%E6%80%A5%E9%80%9F%E6%A8%A1%E5%BC%8F)
能更快,但是内存占用会在100M多一点
![img](img/readme/large.png)
# 最新版本
```xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.2.0</version>
</dependency>
```
# 帮忙点个⭐Star
开源不易,如果觉得EasyExcel对您的工作还是有帮助的话,请帮忙在<a target="_blank" href='https://github.com/alibaba/easyexcel'><img src="https://img.shields.io/github/stars/alibaba/easyexcel.svg?style=flat-square&label=Stars&logo=github" alt="github star"/></a>
的右上角点个⭐Star,您的支持是使EasyExcel变得更好最大的动力。
# 人员招聘
由于工作较忙,需要招聘一些同学加入`EasyExcel`开源,一起跟着`EasyExcel`成长。
## 你将获得
* 认识一帮热爱开源的小伙伴
* 你写的代码可以被无数人看到,提升自我编码能力
* 可能会有一定的物质奖励(在和公司申请,不一定能过)
## 你的工作
* 群&`issue`答疑
* 做一些代码的`PR`合并去修复bug
* 讨论`EasyExcel`规划
## 招聘要求
* 3年以上Java编程经验,基础扎实
* 对技术比较热爱,并且喜欢阅读源码
* 自驱力强,能主动的研究一些问题
* 需要持之以恒,开源需要长期维护
## 联系方式
直接加钉钉群,联系是仪即可
# 如何获取帮助
## 优先建议自己通过文档来解决问题
* [快速开始](https://easyexcel.opensource.alibaba.com/docs/current/)
* [常见问题](https://easyexcel.opensource.alibaba.com/docs/qa/)
* [API](https://easyexcel.opensource.alibaba.com/docs/current/api/)
## 其次建议通过`issues`来解决解决问题
可以尝试在以下2个链接搜索问题,如果不存在可以尝试创建`issue`。
* 去 [github](https://github.com/alibaba/easyexcel/issues) 搜索`issues`
* 去 [gitee](https://gitee.com/easyexcel/easyexcel/issues) 搜索`issues`
通过 `issues` 解决问题,可以给后面遇到相同问题的同学查看,所以比较推荐这种方式。
不管`github`、`gitee`都会定期有人回答您的问题,比较紧急可以在提完`issue`以后在钉钉群艾特群主并发送`issue`地址帮忙解决。
`QQ` 公司不让用,有时候也会去看,但是核心肯定还是在钉钉。
## 也可以加入钉钉&QQ群来解决问题
加入钉钉或QQ群,看完公告可以获得帮助 。
比较推荐钉钉群,`QQ` 公司不让用,当然QQ群也会有热心网友帮忙解决。
[QQ1群(已满): 662022184](https://jq.qq.com/?_wv=1027&k=1T21jJxh)
[QQ2群(已满): 1097936804](https://jq.qq.com/?_wv=1027&k=j5zEy6Xl)
[QQ3群(已满): 453928496](https://qm.qq.com/cgi-bin/qm/qr?k=e2ULsA5A0GldhV2CXJ8sIbAyu9I6qqs7&jump_from=webapi)
[QQ4群: 496594404](https://qm.qq.com/cgi-bin/qm/qr?k=e_aVG1Q7gi0PJUBkbrUGAgbeO3kUEInK&jump_from=webapi)
[QQ4群(已满): 496594404](https://qm.qq.com/cgi-bin/qm/qr?k=e_aVG1Q7gi0PJUBkbrUGAgbeO3kUEInK&jump_from=webapi)
[QQ5群(已满): 451925680](https://jq.qq.com/?_wv=1027&k=6VHhvxyf)
[QQ6群: 784741035](https://jq.qq.com/?_wv=1027&k=BbLBIo9P)
[钉钉1群(已满): 21960511](https://qr.dingtalk.com/action/joingroup?code=v1,k1,cchz6k12ci9B08NNqhNRFGXocNVHrZtW0kaOtTKg/Rk=&_dt_no_comment=1&origin=11)
[钉钉2群(已满): 32796397](https://qr.dingtalk.com/action/joingroup?code=v1,k1,jyU9GtEuNU5S0QTyklqYcYJ8qDZtUuTPMM7uPZTS8Hs=&_dt_no_comment=1&origin=11)
[钉钉3群(已满): 33797247](https://qr.dingtalk.com/action/joingroup?code=v1,k1,3UGlEScTGQaHpW2cIRo+gkxJ9EVZ5fz26M6nW3uFP30=&_dt_no_comment=1&origin=11)
[钉钉4群(已满): 33491624](https://qr.dingtalk.com/action/joingroup?code=v1,k1,V14Pb65Too70rQkEaJ9ohb6lZBZbtp6jIL/q9EWh9vA=&_dt_no_comment=1&origin=11)
[钉钉5群(已满): 32134498](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dingb9fa1325d9dccc3ecac589edd02f1650&5233a=71a83&cbdbhh=qwertyuiop)
[钉钉6群(已满): 34707941](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dingcf68008a1d443ac012d5427bdb061b7a&6ae36c3d-0c80-4=22398493-6c2a-4&cbdbhh=qwertyuiop)
[钉钉7群: 35235427](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=ding532b9018c06c7fc8660273c4b78e6440&167fb=ed003&cbdbhh=qwertyuiop)
[官方网站: https://yuque.com/easyexcel](https://www.yuque.com/easyexcel/doc/easyexcel)
[常见问题](https://www.yuque.com/easyexcel/faq)
#### 因为公司不方便用QQ,所以建议加钉钉群
[钉钉7群(已满): 35235427](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=ding532b9018c06c7fc8660273c4b78e6440&167fb=ed003&cbdbhh=qwertyuiop)
[钉钉8群(已满): 44752220](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dingea96808beee421693fd4ba7542d6e5da&0380092a-fa46=a6a40905-7951&cbdbhh=qwertyuiop)
[钉钉9群(已满): 11045002277](https://h5.dingtalk.com/circle/healthCheckin.html?dtaction=os&corpId=dinge338d2215891c0459c13cd6b2cb108a6&6972d=b92f9&cbdbhh=qwertyuiop)
[钉钉10群: 27360019755](https://qr.dingtalk.com/action/joingroup?code=v1,k1,v25LHn2liWmrWUKlkhIzOTcK7s7onp/sZP8mO5oIYSs=&_dt_no_comment=1&origin=11)
# JAVA解析Excel工具EasyExcel
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
# 维护者
## 64M内存20秒读取75M(46W行25列)的Excel(3.0.2+版本)
当然还有极速模式能更快,但是内存占用会在100M多一点
![img](img/readme/large.png)
## 关于版本选择
如果项目中没有使用过poi,且jdk版本在8-17之间,直接使用最新版本,别犹豫。以下表格适用于不满足以上2个情况的。
| 版本 | poi依赖版本 (支持范围) | jdk版本支持范围 | 备注 |
|--------------------|-----------------------|--------------|---------------------------------------------|
| 3.1.0+ | 4.1.2 (4.1.2 - 5.2.2) | jkd8 - jdk17 | 推荐使用,会更新的版本 |
| 3.0.0-beta1 - 3.0.5 | 4.1.2 (4.1.2 - 5.2.2) | jkd8 - jdk11 | 不推荐项目新引入此版本,除非超级严重bug,否则不再更新 |
| 2.0.0-beta1-2.2.11 | 3.17 (3.17 - 4.1.2) | jdk6 - jdk11 | 不推荐项目新引入此版本,除非是jdk6否则不推荐使用,除非超级严重bug,否则不再更新 |
| 1+版本 | 3.17 (3.17 - 4.1.2) | jdk6 - jdk11 | 不推荐项目新引入此版本,超级严重bug,也不再更新 |
注意: 3+版本的的easyexcel,使用poi 5+版本时,需要手动排除:poi-ooxml-schemas,例如:
```xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
<exclusions>
<exclusion>
<artifactId>poi-ooxml-schemas</artifactId>
<groupId>org.apache.poi</groupId>
</exclusion>
</exclusions>
</dependency>
```
姬朋飞(玉霄)、庄家钜、怀宇
### 关于版本升级
* 不建议跨大版本升级 尤其跨2个大版本
* 2+ 升级到 3+ 一些不兼容的地方
* 使用了自定义拦截器去修改样式的会出问题(不会编译报错)
* 读的时候`invoke`里面抛出异常,不会再额外封装一层`ExcelAnalysisException` (不会编译报错)
* 样式等注解涉及到 `boolean` or 一些枚举 值的 有变动,新增默认值(会编译报错,注解改就行)
* 大版本升级后建议相关内容重新测试下
# 快速开始
### 最新版本
```xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
```
## 读Excel
### easyexcel人员招募
由于工作较忙,有意愿做开源的同学可以报名,主要负责群里回答&issue处理,当然也可以做一些PR.
由于开源没有任何物质回报,然后现在的维护同学也是课余时间维护的,所以想加入的同学需要持之以恒,而不是一时兴起.
要求如下:
* 有一定java编码能力 & 良好的编码习惯
* 了解easyexcel 读&写的原理
* 热爱开源项目
* 能长期坚持的去做
* 相对工作没那么忙
## 相关文档
* [快速开始](https://www.yuque.com/easyexcel/doc/easyexcel)
* [关于软件](/abouteasyexcel.md)
* [更新记事](/update.md)
* [贡献代码](https://www.yuque.com/easyexcel/doc/contribute)
## 维护者
姬朋飞(玉霄)、庄家钜、怀宇
## 快速开始
### 读Excel
DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java)
demo代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java)
详细文档地址:[https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read](https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read)
```java
/**
* 最简单的读
* <p>1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <p>3. 直接读即可
*/
* 最简单的读
* <p>1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <p>3. 直接读即可
*/
@Test
public void simpleRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
@ -106,8 +130,11 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-t
}
```
### 写Excel
DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java)
## 写Excel
demo代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java)
详细文档地址:[https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write](https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write)
```java
/**
* 最简单的写
@ -116,34 +143,36 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-t
*/
@Test
public void simpleWrite() {
String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
String fileName=TestFileUtil.getPath()+"write"+System.currentTimeMillis()+".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
EasyExcel.write(fileName,DemoData.class).sheet("模板").doWrite(data());
}
```
### web上传、下载
DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java)
## web上传、下载
demo代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java)
```java
/**
* 文件下载(失败了会返回一个有部分数据的Excel)
* <p>
* 1. 创建excel对应的实体对象 参照{@link DownloadData}
* <p>
* 2. 设置返回的 参数
* <p>
* 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
*/
/**
* 文件下载(失败了会返回一个有部分数据的Excel)
* <p>
* 1. 创建excel对应的实体对象 参照{@link DownloadData}
* <p>
* 2. 设置返回的 参数
* <p>
* 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
*/
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
String fileName=URLEncoder.encode("测试","UTF-8").replaceAll("\\+","%20");
response.setHeader("Content-disposition","attachment;filename*=utf-8''"+fileName+".xlsx");
EasyExcel.write(response.getOutputStream(),DownloadData.class).sheet("模板").doWrite(data());
}
/**
@ -154,10 +183,8 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/easyexcel-t
*/
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();
return "success";
public String upload(MultipartFile file)throws IOException{
EasyExcel.read(file.getInputStream(),UploadData.class,new UploadDataListener(uploadDAO)).sheet().doRead();
return"success";
}
```
### 联系我们
有问题阿里同事可以通过钉钉找到我,阿里外同学可以通过git留言。其他技术非技术相关的也欢迎一起探讨。

1
README_EN.md

@ -3,6 +3,7 @@ EasyExcel
[![Build Status](https://github.com/alibaba/easyexcel/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/alibaba/easyexcel/actions/workflows/ci.yml?query=branch%3Amaster)
[![Maven central](https://maven-badges.herokuapp.com/maven-central/com.alibaba/easyexcel/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.alibaba/easyexcel)
[![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
[![](https://img.shields.io/badge/EasyExcel-Check%20Your%20Contribution-orange)](https://opensource.alibaba.com/contribution_leaderboard/details?projectValue=easyexcel)
[Communication Group 1 in QQ(Full): 662022184](https://jq.qq.com/?_wv=1027&k=1T21jJxh)
[Communication Group 2 in QQ(Full): 1097936804](https://jq.qq.com/?_wv=1027&k=j5zEy6Xl)

53
abouteasyexcel.md

@ -1,53 +0,0 @@
# easyexcel要去解决的问题
## Excel读写时候内存溢出
虽然POI是目前使用最多的用来做excel解析的框架,但这个框架并不那么完美。大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。
## 其他开源框架使用复杂
对POI有过深入了解的估计才知道原来POI还有SAX模式。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。
## 其他开源框架存在一些BUG修复不及时
由于我们的系统大多数都是大并发的情况下运行的,在大并发情况下,我们会发现poi存在一些bug,如果让POI团队修复估计遥遥无期了。所以我们在easyexcel对这些bug做了规避。
如下一段报错就是在大并发情况下poi抛的一个异常。
```
Caused by: java.io.IOException: Could not create temporary directory '/home/admin/dio2o/.default/temp/poifiles'
at org.apache.poi.util.DefaultTempFileCreationStrategy.createTempDirectory(DefaultTempFileCreationStrategy.java:93) ~[poi-3.15.jar:3.15]
at org.apache.poi.util.DefaultTempFileCreationStrategy.createPOIFilesDirectory(DefaultTempFileCreationStrategy.java:82) ~[poi-3.15.jar:3.15]
```
报错地方poi源码如下
```
private void createTempDirectory(File directory) throws IOException {
if (!(directory.exists() || directory.mkdirs()) || !directory.isDirectory()) {
throw new IOException("Could not create temporary directory '" + directory + "'");
}
}
```
仔细看代码容易明白如果在并发情况下,如果2个线程同时判断directory.exists()都 为false,但执行directory.mkdirs()如果一些线程优先执行完,另外一个线程就会返回false。最终 throw new IOException("Could not create temporary directory '" + directory + "'")。针对这个问题easyexcel在写文件时候首先创建了该临时目录,避免poi在并发创建时候引起不该有的报错。
## Excel格式分析格式分析
- xls是Microsoft Excel2007前excel的文件存储格式,实现原理是基于微软的ole db是微软com组件的一种实现,本质上也是一个微型数据库,由于微软的东西很多不开源,另外也已经被淘汰,了解它的细节意义不大,底层的编程都是基于微软的com组件去开发的。
- xlsx是Microsoft Excel2007后excel的文件存储格式,实现是基于openXml和zip技术。这种存储简单,安全传输方便,同时处理数据也变的简单。
- csv 我们可以理解为纯文本文件,可以被excel打开。他的格式非常简单,解析起来和解析文本文件一样。
## 核心原理
写有大量数据的xlsx文件时,POI为我们提供了SXSSFWorkBook类来处理,这个类的处理机制是当内存中的数据条数达到一个极限数量的时候就flush这部分数据,再依次处理余下的数据,这个在大多数场景能够满足需求。
读有大量数据的文件时,使用WorkBook处理就不行了,因为POI对文件是先将文件中的cell读入内存,生成一个树的结构(针对Excel中的每个sheet,使用TreeMap存储sheet中的行)。如果数据量比较大,则同样会产生java.lang.OutOfMemoryError: Java heap space错误。POI官方推荐使用“XSSF and SAX(event API)”方式来解决。
分析清楚POI后要解决OOM有3个关键。
### 1、文件解压文件读取通过文件形式
![屏幕快照 2018-01-22 上午8.52.08.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/e3a3500014c95f7118d8c200a51acab4.png)
### 2、避免将全部全部数据一次加载到内存
采用sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理。
![基础模板1 (2).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/82bb195ac62532963b2364d2e4da23e5.png)
### 3、抛弃不重要的数据
Excel解析时候会包含样式,字体,宽度等数据,但这些数据是我们不关心的,如果将这部分数据抛弃可以大大降低内存使用。Excel中数据如下Style占了相当大的空间。

1
easyexcel-core/pom.xml

@ -13,6 +13,7 @@
<packaging>jar</packaging>
<artifactId>easyexcel-core</artifactId>
<name>easyexcel-core</name>
<dependencies>
<dependency>

123
easyexcel-core/src/main/java/com/alibaba/excel/EasyExcelFactory.java

@ -14,25 +14,10 @@ import com.alibaba.excel.write.builder.ExcelWriterTableBuilder;
/**
* Reader and writer factory class
*
* <h1>Quick start</h1>
* <h2>Read</h2>
* <h3>Sample1</h3>
*
* <h3>Sample2</h3>
*
* <h2>Write</h2>
*
* <h3>Sample1</h3>
*
* <h3>Sample2</h3>
*
*
*
* @author jipengfei
*/
public class EasyExcelFactory {
/**
* Build excel the write
*
@ -45,8 +30,7 @@ public class EasyExcelFactory {
/**
* Build excel the write
*
* @param file
* File to write
* @param file File to write
* @return Excel writer builder
*/
public static ExcelWriterBuilder write(File file) {
@ -56,10 +40,8 @@ public class EasyExcelFactory {
/**
* Build excel the write
*
* @param file
* File to write
* @param head
* Annotate the class for configuration information
* @param file File to write
* @param head Annotate the class for configuration information
* @return Excel writer builder
*/
public static ExcelWriterBuilder write(File file, Class head) {
@ -74,8 +56,7 @@ public class EasyExcelFactory {
/**
* Build excel the write
*
* @param pathName
* File path to write
* @param pathName File path to write
* @return Excel writer builder
*/
public static ExcelWriterBuilder write(String pathName) {
@ -85,10 +66,8 @@ public class EasyExcelFactory {
/**
* Build excel the write
*
* @param pathName
* File path to write
* @param head
* Annotate the class for configuration information
* @param pathName File path to write
* @param head Annotate the class for configuration information
* @return Excel writer builder
*/
public static ExcelWriterBuilder write(String pathName, Class head) {
@ -103,8 +82,7 @@ public class EasyExcelFactory {
/**
* Build excel the write
*
* @param outputStream
* Output stream to write
* @param outputStream Output stream to write
* @return Excel writer builder
*/
public static ExcelWriterBuilder write(OutputStream outputStream) {
@ -114,10 +92,8 @@ public class EasyExcelFactory {
/**
* Build excel the write
*
* @param outputStream
* Output stream to write
* @param head
* Annotate the class for configuration information.
* @param outputStream Output stream to write
* @param head Annotate the class for configuration information.
* @return Excel writer builder
*/
public static ExcelWriterBuilder write(OutputStream outputStream, Class head) {
@ -141,8 +117,7 @@ public class EasyExcelFactory {
/**
* Build excel the <code>writerSheet</code>
*
* @param sheetNo
* Index of sheet,0 base.
* @param sheetNo Index of sheet,0 base.
* @return Excel sheet writer builder.
*/
public static ExcelWriterSheetBuilder writerSheet(Integer sheetNo) {
@ -152,8 +127,7 @@ public class EasyExcelFactory {
/**
* Build excel the 'writerSheet'
*
* @param sheetName
* The name of sheet.
* @param sheetName The name of sheet.
* @return Excel sheet writer builder.
*/
public static ExcelWriterSheetBuilder writerSheet(String sheetName) {
@ -163,10 +137,8 @@ public class EasyExcelFactory {
/**
* Build excel the 'writerSheet'
*
* @param sheetNo
* Index of sheet,0 base.
* @param sheetName
* The name of sheet.
* @param sheetNo Index of sheet,0 base.
* @param sheetName The name of sheet.
* @return Excel sheet writer builder.
*/
public static ExcelWriterSheetBuilder writerSheet(Integer sheetNo, String sheetName) {
@ -192,8 +164,7 @@ public class EasyExcelFactory {
/**
* Build excel the 'writerTable'
*
* @param tableNo
* Index of table,0 base.
* @param tableNo Index of table,0 base.
* @return Excel table writer builder.
*/
public static ExcelWriterTableBuilder writerTable(Integer tableNo) {
@ -216,8 +187,7 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param file
* File to read.
* @param file File to read.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(File file) {
@ -227,10 +197,8 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param file
* File to read.
* @param readListener
* Read listener.
* @param file File to read.
* @param readListener Read listener.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(File file, ReadListener readListener) {
@ -240,12 +208,9 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param file
* File to read.
* @param head
* Annotate the class for configuration information.
* @param readListener
* Read listener.
* @param file File to read.
* @param head Annotate the class for configuration information.
* @param readListener Read listener.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(File file, Class head, ReadListener readListener) {
@ -263,8 +228,7 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param pathName
* File path to read.
* @param pathName File path to read.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(String pathName) {
@ -274,10 +238,8 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param pathName
* File path to read.
* @param readListener
* Read listener.
* @param pathName File path to read.
* @param readListener Read listener.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(String pathName, ReadListener readListener) {
@ -287,12 +249,9 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param pathName
* File path to read.
* @param head
* Annotate the class for configuration information.
* @param readListener
* Read listener.
* @param pathName File path to read.
* @param head Annotate the class for configuration information.
* @param readListener Read listener.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(String pathName, Class head, ReadListener readListener) {
@ -310,8 +269,7 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param inputStream
* Input stream to read.
* @param inputStream Input stream to read.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(InputStream inputStream) {
@ -321,10 +279,8 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param inputStream
* Input stream to read.
* @param readListener
* Read listener.
* @param inputStream Input stream to read.
* @param readListener Read listener.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(InputStream inputStream, ReadListener readListener) {
@ -334,12 +290,9 @@ public class EasyExcelFactory {
/**
* Build excel the read
*
* @param inputStream
* Input stream to read.
* @param head
* Annotate the class for configuration information.
* @param readListener
* Read listener.
* @param inputStream Input stream to read.
* @param head Annotate the class for configuration information.
* @param readListener Read listener.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(InputStream inputStream, Class head, ReadListener readListener) {
@ -366,8 +319,7 @@ public class EasyExcelFactory {
/**
* Build excel the 'readSheet'
*
* @param sheetNo
* Index of sheet,0 base.
* @param sheetNo Index of sheet,0 base.
* @return Excel sheet reader builder.
*/
public static ExcelReaderSheetBuilder readSheet(Integer sheetNo) {
@ -377,8 +329,7 @@ public class EasyExcelFactory {
/**
* Build excel the 'readSheet'
*
* @param sheetName
* The name of sheet.
* @param sheetName The name of sheet.
* @return Excel sheet reader builder.
*/
public static ExcelReaderSheetBuilder readSheet(String sheetName) {
@ -388,10 +339,8 @@ public class EasyExcelFactory {
/**
* Build excel the 'readSheet'
*
* @param sheetNo
* Index of sheet,0 base.
* @param sheetName
* The name of sheet.
* @param sheetNo Index of sheet,0 base.
* @param sheetName The name of sheet.
* @return Excel sheet reader builder.
*/
public static ExcelReaderSheetBuilder readSheet(Integer sheetNo, String sheetName) {

5
easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/FormulaRecordHandler.java

@ -5,6 +5,7 @@ import java.util.Map;
import com.alibaba.excel.analysis.v03.IgnorableXlsRecordHandler;
import com.alibaba.excel.constant.BuiltinFormats;
import com.alibaba.excel.constant.EasyExcelConstants;
import com.alibaba.excel.context.xls.XlsReadContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.enums.RowTypeEnum;
@ -56,7 +57,9 @@ public class FormulaRecordHandler extends AbstractXlsRecordHandler implements Ig
break;
case NUMERIC:
tempCellData.setType(CellDataTypeEnum.NUMBER);
tempCellData.setNumberValue(BigDecimal.valueOf(frec.getValue()));
tempCellData.setOriginalNumberValue(BigDecimal.valueOf(frec.getValue()));
tempCellData.setNumberValue(
tempCellData.getOriginalNumberValue().round(EasyExcelConstants.EXCEL_MATH_CONTEXT));
int dataFormat =
xlsReadContext.xlsReadWorkbookHolder().getFormatTrackingHSSFListener().getFormatIndex(frec);
DataFormatData dataFormatData = new DataFormatData();

3
easyexcel-core/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java

@ -4,6 +4,7 @@ import java.math.BigDecimal;
import com.alibaba.excel.analysis.v03.IgnorableXlsRecordHandler;
import com.alibaba.excel.constant.BuiltinFormats;
import com.alibaba.excel.constant.EasyExcelConstants;
import com.alibaba.excel.context.xls.XlsReadContext;
import com.alibaba.excel.enums.RowTypeEnum;
import com.alibaba.excel.metadata.data.DataFormatData;
@ -22,7 +23,7 @@ public class NumberRecordHandler extends AbstractXlsRecordHandler implements Ign
@Override
public void processRecord(XlsReadContext xlsReadContext, Record record) {
NumberRecord nr = (NumberRecord)record;
ReadCellData<?> cellData = ReadCellData.newInstance(BigDecimal.valueOf(nr.getValue()), nr.getRow(),
ReadCellData<?> cellData = ReadCellData.newInstanceOriginal(BigDecimal.valueOf(nr.getValue()), nr.getRow(),
(int)nr.getColumn());
short dataFormat = (short)xlsReadContext.xlsReadWorkbookHolder().getFormatTrackingHSSFListener().getFormatIndex(
nr);

46
easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java

@ -8,6 +8,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.xml.parsers.ParserConfigurationException;
@ -25,17 +26,23 @@ import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.read.metadata.holder.xlsx.XlsxReadWorkbookHolder;
import com.alibaba.excel.util.FileUtils;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.util.SheetUtils;
import com.alibaba.excel.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.CommentsTable;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook;
@ -52,6 +59,20 @@ import org.xml.sax.XMLReader;
@Slf4j
public class XlsxSaxAnalyser implements ExcelReadExecutor {
/**
* Storage sheet SharedStrings
*/
public static final PackagePartName SHARED_STRINGS_PART_NAME;
static {
try {
SHARED_STRINGS_PART_NAME = PackagingURIHelper.createPartName("/xl/sharedStrings.xml");
} catch (InvalidFormatException e) {
log.error("Initialize the XlsxSaxAnalyser failure", e);
throw new ExcelAnalysisException("Initialize the XlsxSaxAnalyser failure", e);
}
}
private final XlsxReadContext xlsxReadContext;
private final List<ReadSheet> sheetList;
private final Map<Integer, InputStream> sheetMap;
@ -68,11 +89,9 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor {
OPCPackage pkg = readOpcPackage(xlsxReadWorkbookHolder, decryptedStream);
xlsxReadWorkbookHolder.setOpcPackage(pkg);
ArrayList<PackagePart> packageParts = pkg.getPartsByContentType(XSSFRelation.SHARED_STRINGS.getContentType());
if (!CollectionUtils.isEmpty(packageParts)) {
PackagePart sharedStringsTablePackagePart = packageParts.get(0);
// Read the Shared information Strings
PackagePart sharedStringsTablePackagePart = pkg.getPart(SHARED_STRINGS_PART_NAME);
if (sharedStringsTablePackagePart != null) {
// Specify default cache
defaultReadCache(xlsxReadWorkbookHolder, sharedStringsTablePackagePart);
@ -89,6 +108,9 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor {
sheetList = new ArrayList<>();
sheetMap = new HashMap<>();
commentsTableMap = new HashMap<>();
Map<Integer, PackageRelationshipCollection> packageRelationshipCollectionMap = MapUtils.newHashMap();
xlsxReadWorkbookHolder.setPackageRelationshipCollectionMap(packageRelationshipCollectionMap);
XSSFReader.SheetIterator ite = (XSSFReader.SheetIterator)xssfReader.getSheetsData();
int index = 0;
if (!ite.hasNext()) {
@ -104,6 +126,20 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor {
commentsTableMap.put(index, commentsTable);
}
}
if (xlsxReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.HYPERLINK)) {
PackageRelationshipCollection packageRelationshipCollection = Optional.ofNullable(ite.getSheetPart())
.map(packagePart -> {
try {
return packagePart.getRelationships();
} catch (InvalidFormatException e) {
log.warn("Reading the Relationship failed", e);
return null;
}
}).orElse(null);
if (packageRelationshipCollection != null) {
packageRelationshipCollectionMap.put(index, packageRelationshipCollection);
}
}
index++;
}
}

5
easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/CellTagHandler.java

@ -2,6 +2,7 @@ package com.alibaba.excel.analysis.v07.handlers;
import java.math.BigDecimal;
import com.alibaba.excel.constant.EasyExcelConstants;
import com.alibaba.excel.constant.ExcelXmlConstants;
import com.alibaba.excel.context.xlsx.XlsxReadContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
@ -88,7 +89,9 @@ public class CellTagHandler extends AbstractXlsxTagHandler {
break;
}
tempCellData.setType(CellDataTypeEnum.NUMBER);
tempCellData.setNumberValue(BigDecimal.valueOf(Double.parseDouble(tempDataString)));
tempCellData.setOriginalNumberValue(new BigDecimal(tempDataString));
tempCellData.setNumberValue(
tempCellData.getOriginalNumberValue().round(EasyExcelConstants.EXCEL_MATH_CONTEXT));
break;
default:
throw new IllegalStateException("Cannot set values now");

31
easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/HyperlinkTagHandler.java

@ -1,5 +1,9 @@
package com.alibaba.excel.analysis.v07.handlers;
import java.util.Optional;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.xml.sax.Attributes;
import com.alibaba.excel.constant.ExcelXmlConstants;
@ -23,13 +27,32 @@ public class HyperlinkTagHandler extends AbstractXlsxTagHandler {
@Override
public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) {
String ref = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_REF);
String location = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_LOCATION);
if (StringUtils.isEmpty(ref)) {
return;
}
CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, location, ref);
xlsxReadContext.readSheetHolder().setCellExtra(cellExtra);
xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext);
// Hyperlink has 2 case:
// case 1,In the 'location' tag
String location = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_LOCATION);
if (location != null) {
CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, location, ref);
xlsxReadContext.readSheetHolder().setCellExtra(cellExtra);
xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext);
return;
}
// case 2, In the 'r:id' tag, Then go to 'PackageRelationshipCollection' to get inside
String rId = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_RID);
PackageRelationshipCollection packageRelationshipCollection = xlsxReadContext.xlsxReadSheetHolder()
.getPackageRelationshipCollection();
if (rId == null || packageRelationshipCollection == null) {
return;
}
Optional.ofNullable(packageRelationshipCollection.getRelationshipByID(rId))
.map(PackageRelationship::getTargetURI)
.ifPresent(uri -> {
CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, uri.toString(), ref);
xlsxReadContext.readSheetHolder().setCellExtra(cellExtra);
xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext);
});
}
}

79
easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/SharedStringsTableHandler.java

@ -4,6 +4,7 @@ import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import com.alibaba.excel.cache.ReadCache;
import com.alibaba.excel.constant.ExcelXmlConstants;
/**
* Sax read sharedStringsTable.xml
@ -11,12 +12,6 @@ import com.alibaba.excel.cache.ReadCache;
* @author Jiaju Zhuang
*/
public class SharedStringsTableHandler extends DefaultHandler {
private static final String T_TAG = "t";
private static final String SI_TAG = "si";
/**
* Mac 2016 2017 will have this extra field to ignore
*/
private static final String RPH_TAG = "rPh";
/**
* The final piece of data
@ -43,34 +38,64 @@ public class SharedStringsTableHandler extends DefaultHandler {
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) {
if (T_TAG.equals(name)) {
currentElementData = null;
isTagt = true;
} else if (SI_TAG.equals(name)) {
currentData = null;
} else if (RPH_TAG.equals(name)) {
ignoreTagt = true;
if (name == null) {
return;
}
switch (name) {
case ExcelXmlConstants.SHAREDSTRINGS_T_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_X_T_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_NS2_T_TAG:
currentElementData = null;
isTagt = true;
break;
case ExcelXmlConstants.SHAREDSTRINGS_SI_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_X_SI_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_NS2_SI_TAG:
currentData = null;
break;
case ExcelXmlConstants.SHAREDSTRINGS_RPH_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_X_RPH_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_NS2_RPH_TAG:
ignoreTagt = true;
break;
default:
// ignore
}
}
@Override
public void endElement(String uri, String localName, String name) {
if (T_TAG.equals(name)) {
if (currentElementData != null) {
if (name == null) {
return;
}
switch (name) {
case ExcelXmlConstants.SHAREDSTRINGS_T_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_X_T_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_NS2_T_TAG:
if (currentElementData != null) {
if (currentData == null) {
currentData = new StringBuilder();
}
currentData.append(currentElementData);
}
isTagt = false;
break;
case ExcelXmlConstants.SHAREDSTRINGS_SI_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_X_SI_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_NS2_SI_TAG:
if (currentData == null) {
currentData = new StringBuilder();
readCache.put(null);
} else {
readCache.put(currentData.toString());
}
currentData.append(currentElementData);
}
isTagt = false;
} else if (SI_TAG.equals(name)) {
if (currentData == null) {
readCache.put(null);
} else {
readCache.put(currentData.toString());
}
} else if (RPH_TAG.equals(name)) {
ignoreTagt = false;
break;
case ExcelXmlConstants.SHAREDSTRINGS_RPH_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_X_RPH_TAG:
case ExcelXmlConstants.SHAREDSTRINGS_NS2_RPH_TAG:
ignoreTagt = false;
break;
default:
// ignore
}
}

10
easyexcel-core/src/main/java/com/alibaba/excel/analysis/v07/handlers/sax/XlsxRowHandler.java

@ -26,33 +26,41 @@ import org.xml.sax.helpers.DefaultHandler;
@Slf4j
public class XlsxRowHandler extends DefaultHandler {
private final XlsxReadContext xlsxReadContext;
private static final Map<String, XlsxTagHandler> XLSX_CELL_HANDLER_MAP = new HashMap<String, XlsxTagHandler>(32);
private static final Map<String, XlsxTagHandler> XLSX_CELL_HANDLER_MAP = new HashMap<>(64);
static {
CellFormulaTagHandler cellFormulaTagHandler = new CellFormulaTagHandler();
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_FORMULA_TAG, cellFormulaTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_FORMULA_TAG, cellFormulaTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_CELL_FORMULA_TAG, cellFormulaTagHandler);
CellInlineStringValueTagHandler cellInlineStringValueTagHandler = new CellInlineStringValueTagHandler();
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_INLINE_STRING_VALUE_TAG, cellInlineStringValueTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_INLINE_STRING_VALUE_TAG, cellInlineStringValueTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_CELL_INLINE_STRING_VALUE_TAG, cellInlineStringValueTagHandler);
CellTagHandler cellTagHandler = new CellTagHandler();
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_TAG, cellTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_TAG, cellTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_CELL_TAG, cellTagHandler);
CellValueTagHandler cellValueTagHandler = new CellValueTagHandler();
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.CELL_VALUE_TAG, cellValueTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_CELL_VALUE_TAG, cellValueTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_CELL_VALUE_TAG, cellValueTagHandler);
CountTagHandler countTagHandler = new CountTagHandler();
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.DIMENSION_TAG, countTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_DIMENSION_TAG, countTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_DIMENSION_TAG, countTagHandler);
HyperlinkTagHandler hyperlinkTagHandler = new HyperlinkTagHandler();
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.HYPERLINK_TAG, hyperlinkTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_HYPERLINK_TAG, hyperlinkTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_HYPERLINK_TAG, hyperlinkTagHandler);
MergeCellTagHandler mergeCellTagHandler = new MergeCellTagHandler();
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.MERGE_CELL_TAG, mergeCellTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_MERGE_CELL_TAG, mergeCellTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_MERGE_CELL_TAG, mergeCellTagHandler);
RowTagHandler rowTagHandler = new RowTagHandler();
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.ROW_TAG, rowTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.X_ROW_TAG, rowTagHandler);
XLSX_CELL_HANDLER_MAP.put(ExcelXmlConstants.NS2_ROW_TAG, rowTagHandler);
}
public XlsxRowHandler(XlsxReadContext xlsxReadContext) {

19
easyexcel-core/src/main/java/com/alibaba/excel/constant/EasyExcelConstants.java

@ -0,0 +1,19 @@
package com.alibaba.excel.constant;
import java.math.MathContext;
import java.math.RoundingMode;
/**
* Used to store constant
*
* @author Jiaju Zhuang
*/
public class EasyExcelConstants {
/**
* Excel by default with 15 to store Numbers, and the double in Java can use to store number 17, led to the accuracy
* will be a problem. So you need to set up 15 to deal with precision
*/
public static final MathContext EXCEL_MATH_CONTEXT = new MathContext(15, RoundingMode.HALF_UP);
}

42
easyexcel-core/src/main/java/com/alibaba/excel/constant/ExcelXmlConstants.java

@ -17,16 +17,28 @@ public class ExcelXmlConstants {
public static final String HYPERLINK_TAG = "hyperlink";
public static final String X_DIMENSION_TAG = "x:dimension";
public static final String NS2_DIMENSION_TAG = "ns2:dimension";
public static final String X_ROW_TAG = "x:row";
public static final String NS2_ROW_TAG = "ns2:row";
public static final String X_CELL_FORMULA_TAG = "x:f";
public static final String NS2_CELL_FORMULA_TAG = "ns2:f";
public static final String X_CELL_VALUE_TAG = "x:v";
public static final String NS2_CELL_VALUE_TAG = "ns2:v";
/**
* When the data is "inlineStr" his tag is "t"
*/
public static final String X_CELL_INLINE_STRING_VALUE_TAG = "x:t";
public static final String NS2_CELL_INLINE_STRING_VALUE_TAG = "ns2:t";
public static final String X_CELL_TAG = "x:c";
public static final String NS2_CELL_TAG = "ns2:c";
public static final String X_MERGE_CELL_TAG = "x:mergeCell";
public static final String NS2_MERGE_CELL_TAG = "ns2:mergeCell";
public static final String X_HYPERLINK_TAG = "x:hyperlink";
public static final String NS2_HYPERLINK_TAG = "ns2:hyperlink";
/**
* s attribute
@ -49,9 +61,39 @@ public class ExcelXmlConstants {
*/
public static final String ATTRIBUTE_LOCATION = "location";
/**
* rId attribute
*/
public static final String ATTRIBUTE_RID = "r:id";
/**
* Cell range split
*/
public static final String CELL_RANGE_SPLIT = ":";
// The following is a constant read the `SharedStrings.xml`
/**
* text
*/
public static final String SHAREDSTRINGS_T_TAG = "t";
public static final String SHAREDSTRINGS_X_T_TAG = "x:t";
public static final String SHAREDSTRINGS_NS2_T_TAG = "ns2:t";
/**
* SharedStringItem
*/
public static final String SHAREDSTRINGS_SI_TAG = "si";
public static final String SHAREDSTRINGS_X_SI_TAG = "x:si";
public static final String SHAREDSTRINGS_NS2_SI_TAG = "ns2:si";
/**
* Mac 2016 2017 will have this extra field to ignore
*/
public static final String SHAREDSTRINGS_RPH_TAG = "rPh";
public static final String SHAREDSTRINGS_X_RPH_TAG = "x:rPh";
public static final String SHAREDSTRINGS_NS2_RPH_TAG = "ns2:rPh";
}

9
easyexcel-core/src/main/java/com/alibaba/excel/converters/date/DateNumberConverter.java

@ -9,6 +9,7 @@ import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.DateUtils;
import org.apache.poi.ss.usermodel.DateUtil;
@ -33,11 +34,11 @@ public class DateNumberConverter implements Converter<Date> {
public Date convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
return DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(),
globalConfiguration.getUse1904windowing(), null);
return DateUtils.getJavaDate(cellData.getNumberValue().doubleValue(),
globalConfiguration.getUse1904windowing());
} else {
return DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(),
contentProperty.getDateTimeFormatProperty().getUse1904windowing(), null);
return DateUtils.getJavaDate(cellData.getNumberValue().doubleValue(),
contentProperty.getDateTimeFormatProperty().getUse1904windowing());
}
}

5
easyexcel-core/src/main/java/com/alibaba/excel/converters/localdatetime/LocalDateNumberConverter.java

@ -9,6 +9,7 @@ import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.DateUtils;
import org.apache.poi.ss.usermodel.DateUtil;
@ -33,10 +34,10 @@ public class LocalDateNumberConverter implements Converter<LocalDateTime> {
public LocalDateTime convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
return DateUtil.getLocalDateTime(cellData.getNumberValue().doubleValue(),
return DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(),
globalConfiguration.getUse1904windowing());
} else {
return DateUtil.getLocalDateTime(cellData.getNumberValue().doubleValue(),
return DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(),
contentProperty.getDateTimeFormatProperty().getUse1904windowing());
}
}

5
easyexcel-core/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java

@ -37,9 +37,8 @@ public class StringNumberConverter implements Converter<String> {
GlobalConfiguration globalConfiguration) {
// If there are "DateTimeFormat", read as date
if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) {
return DateUtils.format(
DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(),
contentProperty.getDateTimeFormatProperty().getUse1904windowing(), null),
return DateUtils.format(cellData.getNumberValue(),
contentProperty.getDateTimeFormatProperty().getUse1904windowing(),
contentProperty.getDateTimeFormatProperty().getFormat());
}
// If there are "NumberFormat", read as number

40
easyexcel-core/src/main/java/com/alibaba/excel/enums/ReadDefaultReturnEnum.java

@ -0,0 +1,40 @@
package com.alibaba.excel.enums;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.util.StringUtils;
/**
* Read not to {@code com.alibaba.excel.metadata.BasicParameter#clazz} value, the default will return type.
*
* @author Jiaju Zhuang
*/
public enum ReadDefaultReturnEnum {
/**
* default.The content of cells into string, is the same as you see in the excel.
*/
STRING,
/**
* Returns the actual type.
* Will be automatically selected according to the cell contents what return type, will return the following class:
* <ol>
* <li>{@link BigDecimal}</li>
* <li>{@link Boolean}</li>
* <li>{@link String}</li>
* <li>{@link LocalDateTime}</li>
* </ol>
*/
ACTUAL_DATA,
/**
* Return to {@link com.alibaba.excel.metadata.data.ReadCellData}, can decide which field you need.
*/
READ_CELL_DATA,
;
}

3
easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/CellData.java

@ -1,6 +1,7 @@
package com.alibaba.excel.metadata.data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.AbstractCell;
@ -57,6 +58,7 @@ public class CellData<T> extends AbstractCell {
}
switch (type) {
case STRING:
case DIRECT_STRING:
case ERROR:
if (StringUtils.isEmpty(stringValue)) {
type = CellDataTypeEnum.EMPTY;
@ -76,5 +78,4 @@ public class CellData<T> extends AbstractCell {
}
}
}

37
easyexcel-core/src/main/java/com/alibaba/excel/metadata/data/ReadCellData.java

@ -1,7 +1,9 @@
package com.alibaba.excel.metadata.data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.alibaba.excel.constant.EasyExcelConstants;
import com.alibaba.excel.enums.CellDataTypeEnum;
import lombok.EqualsAndHashCode;
@ -11,6 +13,7 @@ import lombok.Setter;
/**
* read cell data
* <p>
*
* @author Jiaju Zhuang
*/
@ -19,6 +22,29 @@ import lombok.Setter;
@EqualsAndHashCode
@NoArgsConstructor
public class ReadCellData<T> extends CellData<T> {
/**
* originalNumberValue vs numberValue
* <ol>
* <li>
* NUMBER
* originalNumberValue: Original data and the accuracy of his is 17, but in fact the excel only 15 precision to
* process the data
* numberValue: After correction of the data and the accuracy of his is 15
* for example, originalNumberValue = `2087.0249999999996` , numberValue = `2087.03`
* </li>
* <li>
* DATE
* originalNumberValue: Storage is a data type double, accurate to milliseconds
* dateValue: Based on double converted to a date format, he will revised date difference, accurate to seconds
* for example, originalNumberValue = `44729.99998836806` ,time is:`2022-06-17 23:59:58.995`,
* But in excel is displayed:` 2022-06-17 23:59:59`, dateValue = `2022-06-17 23:59:59`
* </li>
* </ol>
* {@link CellDataTypeEnum#NUMBER} {@link CellDataTypeEnum#DATE}
*/
private BigDecimal originalNumberValue;
/**
* data format.
*/
@ -107,11 +133,21 @@ public class ReadCellData<T> extends CellData<T> {
return cellData;
}
public static ReadCellData<?> newInstanceOriginal(BigDecimal numberValue, Integer rowIndex, Integer columnIndex) {
ReadCellData<?> cellData = new ReadCellData<>(numberValue);
cellData.setRowIndex(rowIndex);
cellData.setColumnIndex(columnIndex);
cellData.setOriginalNumberValue(numberValue);
cellData.setNumberValue(numberValue.round(EasyExcelConstants.EXCEL_MATH_CONTEXT));
return cellData;
}
@Override
public ReadCellData<Object> clone() {
ReadCellData<Object> readCellData = new ReadCellData<>();
readCellData.setType(getType());
readCellData.setNumberValue(getNumberValue());
readCellData.setOriginalNumberValue(getOriginalNumberValue());
readCellData.setStringValue(getStringValue());
readCellData.setBooleanValue(getBooleanValue());
readCellData.setData(getData());
@ -123,5 +159,4 @@ public class ReadCellData<T> extends CellData<T> {
}
return readCellData;
}
}

8
easyexcel-core/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java

@ -212,10 +212,10 @@ public class DataFormatter {
CellFormat cfmt = CellFormat.getInstance(locale, formatStr);
// CellFormat requires callers to identify date vs not, so do so
Object cellValueO = data;
if (DateUtil.isADateFormat(dataFormat, formatStr) &&
if (DateUtils.isADateFormat(dataFormat, formatStr) &&
// don't try to handle Date value 0, let a 3 or 4-part format take care of it
data.doubleValue() != 0.0) {
cellValueO = DateUtil.getJavaDate(data, use1904windowing);
cellValueO = DateUtils.getJavaDate(data, use1904windowing);
}
// Wrap and return (non-cachable - CellFormat does that)
return new CellFormatResultWrapper(cfmt.apply(cellValueO));
@ -243,6 +243,8 @@ public class DataFormatter {
return format;
}
private Format createFormat(Short dataFormat, String dataFormatString) {
String formatStr = dataFormatString;
@ -628,7 +630,7 @@ public class DataFormatter {
// Hint about the raw excel value
((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(data);
}
return performDateFormatting(DateUtil.getJavaDate(data, use1904windowing), dateFormat);
return performDateFormatting(DateUtils.getJavaDate(data, use1904windowing), dateFormat);
}
/**

12
easyexcel-core/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java

@ -13,6 +13,7 @@ import com.alibaba.excel.cache.ReadCache;
import com.alibaba.excel.cache.selector.ReadCacheSelector;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.enums.ReadDefaultReturnEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.event.SyncReadListener;
import com.alibaba.excel.read.listener.ModelBuildEventListener;
@ -198,6 +199,17 @@ public class ExcelReaderBuilder extends AbstractExcelReaderParameterBuilder<Exce
return this;
}
/**
* Read not to {@code com.alibaba.excel.metadata.BasicParameter#clazz} value, the default will return type.
* Is only effective when set `useDefaultListener=true` or `useDefaultListener=null`.
*
* @see ReadDefaultReturnEnum
*/
public ExcelReaderBuilder readDefaultReturn(ReadDefaultReturnEnum readDefaultReturn) {
readWorkbook.setReadDefaultReturn(readDefaultReturn);
return this;
}
public ExcelReader build() {
return new ExcelReader(readWorkbook);
}

81
easyexcel-core/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java

@ -1,21 +1,30 @@
package com.alibaba.excel.read.listener;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
import javax.print.DocFlavor.STRING;
import com.alibaba.excel.constant.EasyExcelConstants;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.enums.HeadKindEnum;
import com.alibaba.excel.enums.ReadDefaultReturnEnum;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.DataFormatData;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.metadata.holder.ReadSheetHolder;
import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty;
import com.alibaba.excel.util.BeanMapUtils;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.util.ClassUtils;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.DateUtils;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.util.StringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cglib.beans.BeanMap;
/**
@ -25,14 +34,6 @@ import org.springframework.cglib.beans.BeanMap;
*/
public class ModelBuildEventListener implements IgnoreExceptionReadListener<Map<Integer, ReadCellData<?>>> {
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> cellDataMap, AnalysisContext context) {
if (context.readSheetHolder().getMaxDataHeadSize() == null
|| context.readSheetHolder().getMaxDataHeadSize() < CollectionUtils.size(cellDataMap)) {
context.readSheetHolder().setMaxDataHeadSize(CollectionUtils.size(cellDataMap));
}
}
@Override
public void invoke(Map<Integer, ReadCellData<?>> cellDataMap, AnalysisContext context) {
ReadSheetHolder readSheetHolder = context.readSheetHolder();
@ -41,13 +42,13 @@ public class ModelBuildEventListener implements IgnoreExceptionReadListener<Map<
.setCurrentRowAnalysisResult(buildUserModel(cellDataMap, readSheetHolder, context));
return;
}
context.readRowHolder().setCurrentRowAnalysisResult(buildStringList(cellDataMap, readSheetHolder, context));
context.readRowHolder().setCurrentRowAnalysisResult(buildNoModel(cellDataMap, readSheetHolder, context));
}
private Object buildStringList(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,
private Object buildNoModel(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,
AnalysisContext context) {
int index = 0;
Map<Integer, String> map = MapUtils.newLinkedHashMapWithExpectedSize(cellDataMap.size());
Map<Integer, Object> map = MapUtils.newLinkedHashMapWithExpectedSize(cellDataMap.size());
for (Map.Entry<Integer, ReadCellData<?>> entry : cellDataMap.entrySet()) {
Integer key = entry.getKey();
ReadCellData<?> cellData = entry.getValue();
@ -56,9 +57,23 @@ public class ModelBuildEventListener implements IgnoreExceptionReadListener<Map<
index++;
}
index++;
map.put(key,
(String)ConverterUtils.convertToJavaObject(cellData, null, null, readSheetHolder.converterMap(),
context, context.readRowHolder().getRowIndex(), key));
ReadDefaultReturnEnum readDefaultReturn = context.readWorkbookHolder().getReadDefaultReturn();
if (readDefaultReturn == ReadDefaultReturnEnum.STRING) {
// string
map.put(key,
(String)ConverterUtils.convertToJavaObject(cellData, null, null, readSheetHolder.converterMap(),
context, context.readRowHolder().getRowIndex(), key));
} else {
// retrun ReadCellData
ReadCellData<?> convertedReadCellData = convertReadCellData(cellData,
context.readWorkbookHolder().getReadDefaultReturn(), readSheetHolder, context, key);
if (readDefaultReturn == ReadDefaultReturnEnum.READ_CELL_DATA) {
map.put(key, convertedReadCellData);
} else {
map.put(key, convertedReadCellData.getData());
}
}
}
// fix https://github.com/alibaba/easyexcel/issues/2014
int headSize = calculateHeadSize(readSheetHolder);
@ -69,12 +84,44 @@ public class ModelBuildEventListener implements IgnoreExceptionReadListener<Map<
return map;
}
private ReadCellData convertReadCellData(ReadCellData<?> cellData, ReadDefaultReturnEnum readDefaultReturn,
ReadSheetHolder readSheetHolder, AnalysisContext context, Integer columnIndex) {
Class<?> classGeneric;
switch (cellData.getType()) {
case STRING:
case DIRECT_STRING:
case ERROR:
case EMPTY:
classGeneric = String.class;
break;
case BOOLEAN:
classGeneric = Boolean.class;
break;
case NUMBER:
DataFormatData dataFormatData = cellData.getDataFormatData();
if (dataFormatData != null && DateUtils.isADateFormat(dataFormatData.getIndex(),
dataFormatData.getFormat())) {
classGeneric = LocalDateTime.class;
} else {
classGeneric = BigDecimal.class;
}
break;
default:
classGeneric = ConverterUtils.defaultClassGeneric;
break;
}
return (ReadCellData)ConverterUtils.convertToJavaObject(cellData, null, ReadCellData.class,
classGeneric, null, readSheetHolder.converterMap(), context, context.readRowHolder().getRowIndex(),
columnIndex);
}
private int calculateHeadSize(ReadSheetHolder readSheetHolder) {
if (readSheetHolder.excelReadHeadProperty().getHeadMap().size() > 0) {
return readSheetHolder.excelReadHeadProperty().getHeadMap().size();
}
if (readSheetHolder.getMaxDataHeadSize() != null) {
return readSheetHolder.getMaxDataHeadSize();
if (readSheetHolder.getMaxNotEmptyDataHeadSize() != null) {
return readSheetHolder.getMaxNotEmptyDataHeadSize();
}
return 0;
}

12
easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java

@ -11,6 +11,7 @@ import com.alibaba.excel.cache.ReadCache;
import com.alibaba.excel.cache.selector.ReadCacheSelector;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.enums.ReadDefaultReturnEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.listener.ModelBuildEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
@ -62,7 +63,6 @@ public class ReadWorkbook extends ReadBasicParameter {
/**
* This object can be read in the Listener {@link AnalysisEventListener#invoke(Object, AnalysisContext)}
* {@link AnalysisContext#getCustom()}
*
*/
private Object customObject;
/**
@ -96,8 +96,18 @@ public class ReadWorkbook extends ReadBasicParameter {
* Whether to use the default listener, which is used by default.
* <p>
* The {@link ModelBuildEventListener} is loaded by default to convert the object.
* defualt is true.
*/
private Boolean useDefaultListener;
/**
* Read not to {@code com.alibaba.excel.metadata.BasicParameter#clazz} value, the default will return type.
* Is only effective when set `useDefaultListener=true` or `useDefaultListener=null`.
*
* @see ReadDefaultReturnEnum
*/
private ReadDefaultReturnEnum readDefaultReturn;
/**
* Read some additional fields. None are read by default.
*

2
easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java

@ -65,7 +65,7 @@ public class ReadSheetHolder extends AbstractReadHolder {
* Read the size of the largest head in sheet head data.
* see https://github.com/alibaba/easyexcel/issues/2014
*/
private Integer maxDataHeadSize;
private Integer maxNotEmptyDataHeadSize;
public ReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) {
super(readSheet, readWorkbookHolder);

17
easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java

@ -14,6 +14,7 @@ import com.alibaba.excel.cache.selector.SimpleReadCacheSelector;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.enums.HolderEnum;
import com.alibaba.excel.enums.ReadDefaultReturnEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.read.metadata.ReadSheet;
@ -64,10 +65,20 @@ public class ReadWorkbookHolder extends AbstractReadHolder {
* if false, Will transfer 'inputStream' to temporary files to improve efficiency
*/
private Boolean mandatoryUseInputStream;
/**
* Default true
*/
private Boolean autoCloseStream;
/**
* Read not to {@code com.alibaba.excel.metadata.BasicParameter#clazz} value, the default will return type.
* Is only effective when set `useDefaultListener=true` or `useDefaultListener=null`.
*
* @see ReadDefaultReturnEnum
*/
private ReadDefaultReturnEnum readDefaultReturn;
/**
* Excel type
*/
@ -146,6 +157,12 @@ public class ReadWorkbookHolder extends AbstractReadHolder {
this.autoCloseStream = readWorkbook.getAutoCloseStream();
}
if (readWorkbook.getReadDefaultReturn() == null) {
this.readDefaultReturn = ReadDefaultReturnEnum.STRING;
} else {
this.readDefaultReturn = readWorkbook.getReadDefaultReturn();
}
this.customObject = readWorkbook.getCustomObject();
if (readWorkbook.getIgnoreEmptyRow() == null) {
this.ignoreEmptyRow = Boolean.TRUE;

8
easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadSheetHolder.java

@ -10,6 +10,7 @@ import com.alibaba.excel.read.metadata.holder.ReadWorkbookHolder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
/**
* sheet holder
@ -36,9 +37,16 @@ public class XlsxReadSheetHolder extends ReadSheetHolder {
* Formula for current label.
*/
private StringBuilder tempFormula;
/**
* excel Relationship
*/
private PackageRelationshipCollection packageRelationshipCollection;
public XlsxReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) {
super(readSheet, readWorkbookHolder);
this.tagDeque = new LinkedList<String>();
packageRelationshipCollection
= ((XlsxReadWorkbookHolder)readWorkbookHolder).getPackageRelationshipCollectionMap().get(
readSheet.getSheetNo());
}
}

9
easyexcel-core/src/main/java/com/alibaba/excel/read/metadata/holder/xlsx/XlsxReadWorkbookHolder.java

@ -15,6 +15,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
@ -51,6 +52,11 @@ public class XlsxReadWorkbookHolder extends ReadWorkbookHolder {
*/
private Map<Integer, DataFormatData> dataFormatDataCache;
/**
* excel Relationship, key: sheetNo value: PackageRelationshipCollection
*/
private Map<Integer, PackageRelationshipCollection> packageRelationshipCollectionMap;
public XlsxReadWorkbookHolder(ReadWorkbook readWorkbook) {
super(readWorkbook);
this.saxParserFactoryName = readWorkbook.getXlsxSAXParserFactoryName();
@ -65,6 +71,9 @@ public class XlsxReadWorkbookHolder extends ReadWorkbookHolder {
return null;
}
XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(dateFormatIndexInteger);
if (xssfCellStyle == null) {
return null;
}
dataFormatData.setIndex(xssfCellStyle.getDataFormat());
dataFormatData.setFormat(BuiltinFormats.getBuiltinFormat(dataFormatData.getIndex(),
xssfCellStyle.getDataFormatString(), globalConfiguration().getLocale()));

13
easyexcel-core/src/main/java/com/alibaba/excel/read/processor/DefaultAnalysisEventProcessor.java

@ -3,8 +3,11 @@ package com.alibaba.excel.read.processor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.enums.HeadKindEnum;
import com.alibaba.excel.enums.RowTypeEnum;
import com.alibaba.excel.exception.ExcelAnalysisException;
@ -17,6 +20,8 @@ import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.StringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -111,6 +116,14 @@ public class DefaultAnalysisEventProcessor implements AnalysisEventProcessor {
}
private void buildHead(AnalysisContext analysisContext, Map<Integer, ReadCellData<?>> cellDataMap) {
// Rule out empty head, and then take the largest column
if (MapUtils.isNotEmpty(cellDataMap)) {
cellDataMap.entrySet()
.stream()
.filter(entry -> CellDataTypeEnum.EMPTY != entry.getValue().getType())
.forEach(entry -> analysisContext.readSheetHolder().setMaxNotEmptyDataHeadSize(entry.getKey()));
}
if (!HeadKindEnum.CLASS.equals(analysisContext.currentReadHolder().excelReadHeadProperty().getHeadKind())) {
return;
}

46
easyexcel-core/src/main/java/com/alibaba/excel/util/ConverterUtils.java

@ -84,24 +84,52 @@ public class ConverterUtils {
public static Object convertToJavaObject(ReadCellData<?> cellData, Field field,
ExcelContentProperty contentProperty, Map<ConverterKey, Converter<?>> converterMap, AnalysisContext context,
Integer rowIndex, Integer columnIndex) {
Class<?> clazz;
if (field == null) {
clazz = String.class;
} else {
clazz = field.getType();
return convertToJavaObject(cellData, field, null, null, contentProperty, converterMap, context, rowIndex,
columnIndex);
}
/**
* Convert it into a Java object
*
* @param cellData
* @param field
* @param clazz
* @param contentProperty
* @param converterMap
* @param context
* @param rowIndex
* @param columnIndex
* @return
*/
public static Object convertToJavaObject(ReadCellData<?> cellData, Field field, Class<?> clazz,
Class<?> classGeneric, ExcelContentProperty contentProperty, Map<ConverterKey, Converter<?>> converterMap,
AnalysisContext context, Integer rowIndex, Integer columnIndex) {
if (clazz == null) {
if (field == null) {
clazz = String.class;
} else {
clazz = field.getType();
}
}
if (clazz == CellData.class || clazz == ReadCellData.class) {
Class<?> classGeneric = getClassGeneric(field.getGenericType());
ReadCellData<Object> cellDataReturn = cellData.clone();
cellDataReturn.setData(doConvertToJavaObject(cellData, classGeneric, contentProperty, converterMap,
context, rowIndex, columnIndex));
cellDataReturn.setData(
doConvertToJavaObject(cellData, getClassGeneric(field, classGeneric), contentProperty,
converterMap, context, rowIndex, columnIndex));
return cellDataReturn;
}
return doConvertToJavaObject(cellData, clazz, contentProperty, converterMap, context, rowIndex,
columnIndex);
}
private static Class<?> getClassGeneric(Type type) {
private static Class<?> getClassGeneric(Field field, Class<?> classGeneric) {
if (classGeneric != null) {
return classGeneric;
}
if (field == null) {
return defaultClassGeneric;
}
Type type = field.getGenericType();
if (!(type instanceof ParameterizedType)) {
return defaultClassGeneric;
}

71
easyexcel-core/src/main/java/com/alibaba/excel/util/DateUtils.java

@ -1,9 +1,11 @@
package com.alibaba.excel.util;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
@ -11,6 +13,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.poi.ss.usermodel.DateUtil;
/**
* Date utils
*
@ -184,6 +188,33 @@ public class DateUtils {
}
}
/**
* Format date
*
* @param date
* @param dateFormat
* @return
*/
public static String format(LocalDateTime date, String dateFormat) {
return format(date, dateFormat, null);
}
/**
* Format date
*
* @param date
* @param dateFormat
* @return
*/
public static String format(BigDecimal date, Boolean use1904windowing, String dateFormat) {
if (date == null) {
return null;
}
LocalDateTime localDateTime = DateUtil.getLocalDateTime(date.doubleValue(),
BooleanUtils.isTrue(use1904windowing), true);
return format(localDateTime, dateFormat);
}
private static DateFormat getCacheDateFormat(String dateFormat) {
Map<String, SimpleDateFormat> dateFormatMap = DATE_FORMAT_THREAD_LOCAL.get();
if (dateFormatMap == null) {
@ -200,6 +231,46 @@ public class DateUtils {
return simpleDateFormat;
}
/**
* Given an Excel date with either 1900 or 1904 date windowing,
* converts it to a java.util.Date.
*
* Excel Dates and Times are stored without any timezone
* information. If you know (through other means) that your file
* uses a different TimeZone to the system default, you can use
* this version of the getJavaDate() method to handle it.
*
* @param date The Excel date.
* @param use1904windowing true if date uses 1904 windowing,
* or false if using 1900 date windowing.
* @return Java representation of the date, or null if date is not a valid Excel date
*/
public static Date getJavaDate(double date, boolean use1904windowing) {
//To calculate the Date, in the use of `org.apache.poi.ss.usermodel.DateUtil.getJavaDate(double, boolean,
// java.util.TimeZone, boolean), Date when similar `2023-01-01 00:00:00.500`, returns the`2023-01-01
// 00:00:01`, but excel in fact shows the `2023-01-01 00:00:00`.
// `org.apache.poi.ss.usermodel.DateUtil.getLocalDateTime(double, boolean, boolean)` There is no problem.
return Date.from(getLocalDateTime(date, use1904windowing).atZone(ZoneId.systemDefault()).toInstant());
}
/**
* Given an Excel date with either 1900 or 1904 date windowing,
* converts it to a java.time.LocalDateTime.
*
* Excel Dates and Times are stored without any timezone
* information. If you know (through other means) that your file
* uses a different TimeZone to the system default, you can use
* this version of the getJavaDate() method to handle it.
*
* @param date The Excel date.
* @param use1904windowing true if date uses 1904 windowing,
* or false if using 1900 date windowing.
* @return Java representation of the date, or null if date is not a valid Excel date
*/
public static LocalDateTime getLocalDateTime(double date, boolean use1904windowing) {
return DateUtil.getLocalDateTime(date, use1904windowing, true);
}
/**
* Determine if it is a date format.
*

2
easyexcel-core/src/main/java/com/alibaba/excel/util/FileUtils.java

@ -62,7 +62,6 @@ public class FileUtils {
// Initialize the cache directory
File cacheFile = new File(cachePath);
createDirectory(cacheFile);
cacheFile.deleteOnExit();
}
/**
@ -160,7 +159,6 @@ public class FileUtils {
File poiFilesPathFile = new File(poiFilesPath);
createDirectory(poiFilesPathFile);
TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(poiFilesPathFile));
poiFilesPathFile.deleteOnExit();
}
public static File createCacheTmpFile() {

8
easyexcel-core/src/main/java/com/alibaba/excel/write/metadata/RowData.java

@ -18,17 +18,17 @@ public interface RowData {
/**
* Returns the number of elements in this collection. If this collection
* contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
* contains more than <code>Integer.MAX_VALUE</code> elements, returns
* <code>Integer.MAX_VALUE</code>.
*
* @return the number of elements in this collection
*/
int size();
/**
* Returns <tt>true</tt> if this collection contains no elements.
* Returns <code>true</code> if this collection contains no elements.
*
* @return <tt>true</tt> if this collection contains no elements
* @return <code>true</code> if this collection contains no elements
*/
boolean isEmpty();

2
easyexcel-support/pom.xml

@ -13,6 +13,8 @@
<packaging>jar</packaging>
<artifactId>easyexcel-support</artifactId>
<name>easyexcel-support</name>
<dependencies>
<dependency>

1
easyexcel-test/pom.xml

@ -13,6 +13,7 @@
<packaging>jar</packaging>
<artifactId>easyexcel-test</artifactId>
<name>easyexcel-test</name>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>

86
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/compatibility/CompatibilityTest.java

@ -1,10 +1,14 @@
package com.alibaba.easyexcel.test.core.compatibility;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.constant.EasyExcelConstants;
import com.alibaba.excel.enums.ReadDefaultReturnEnum;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
@ -30,4 +34,86 @@ public class CompatibilityTest {
Map<Integer, Object> row1 = list.get(1);
Assert.assertEquals("Q235(碳钢)", row1.get(0));
}
@Test
public void t02() {
// Exist in `sharedStrings.xml` `x:t` start tag, need to be compatible
List<Map<Integer, Object>> list = EasyExcel.read(TestFileUtil.getPath() + "compatibility/t02.xlsx").sheet()
.headRowNumber(0).doReadSync();
log.info("data:{}", JSON.toJSONString(list));
Assert.assertEquals(3, list.size());
Map<Integer, Object> row2 = list.get(2);
Assert.assertEquals("1,2-戊二醇", row2.get(2));
}
@Test
public void t03() {
// In the presence of the first line of a lot of null columns, ignore null columns
List<Map<Integer, Object>> list = EasyExcel.read(TestFileUtil.getPath() + "compatibility/t03.xlsx").sheet()
.doReadSync();
log.info("data:{}", JSON.toJSONString(list));
Assert.assertEquals(1, list.size());
Map<Integer, Object> row0 = list.get(0);
Assert.assertEquals(12, row0.size());
}
@Test
public void t04() {
// Exist in `sheet1.xml` `ns2:t` start tag, need to be compatible
List<Map<Integer, Object>> list = EasyExcel.read(TestFileUtil.getPath() + "compatibility/t04.xlsx").sheet()
.doReadSync();
log.info("data:{}", JSON.toJSONString(list));
Assert.assertEquals(56, list.size());
Map<Integer, Object> row0 = list.get(0);
Assert.assertEquals("QQSJK28F152A012242S0081", row0.get(5));
}
@Test
public void t05() {
// https://github.com/alibaba/easyexcel/issues/1956
// Excel read date needs to be rounded
List<Map<Integer, String>> list = EasyExcel
.read(TestFileUtil.getPath() + "compatibility/t05.xlsx")
.sheet()
.doReadSync();
log.info("data:{}", JSON.toJSONString(list));
Assert.assertEquals("2023-01-01 00:00:00", list.get(0).get(0));
Assert.assertEquals("2023-01-01 00:00:00", list.get(1).get(0));
Assert.assertEquals("2023-01-01 00:00:00", list.get(2).get(0));
Assert.assertEquals("2023-01-01 00:00:01", list.get(3).get(0));
Assert.assertEquals("2023-01-01 00:00:01", list.get(4).get(0));
}
@Test
public void t06() {
// Keep error precision digital format
List<Map<Integer, String>> list = EasyExcel
.read(TestFileUtil.getPath() + "compatibility/t06.xlsx")
.headRowNumber(0)
.sheet()
.doReadSync();
log.info("data:{}", JSON.toJSONString(list));
Assert.assertEquals("2087.03", list.get(0).get(2));
}
@Test
public void t07() {
// https://github.com/alibaba/easyexcel/issues/2805
// Excel read date needs to be rounded
List<Map<Integer, Object>> list = EasyExcel
.read(TestFileUtil.getPath() + "compatibility/t07.xlsx")
.readDefaultReturn(ReadDefaultReturnEnum.ACTUAL_DATA)
.sheet()
.doReadSync();
log.info("data:{}", JSON.toJSONString(list));
Assert.assertEquals(0, new BigDecimal("24.1998124").compareTo((BigDecimal)list.get(0).get(11)));
list = EasyExcel
.read(TestFileUtil.getPath() + "compatibility/t07.xlsx")
.sheet()
.doReadSync();
log.info("data:{}", JSON.toJSONString(list));
Assert.assertEquals("24.20", list.get(0).get(11));
}
}

44
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/extra/ExtraDataTest.java

@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.core.extra;
import java.io.File;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
@ -9,10 +10,13 @@ import org.slf4j.LoggerFactory;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.fastjson.JSON;
/**
*
* @author Jiaju Zhuang
*/
public class ExtraDataTest {
@ -20,10 +24,13 @@ public class ExtraDataTest {
private static File file03;
private static File file07;
private static File extraRelationships;
@BeforeClass
public static void init() {
file03 = TestFileUtil.readFile("extra" + File.separator + "extra.xls");
file07 = TestFileUtil.readFile("extra" + File.separator + "extra.xlsx");
extraRelationships = TestFileUtil.readFile("extra" + File.separator + "extraRelationships.xlsx");
}
@Test
@ -36,6 +43,41 @@ public class ExtraDataTest {
read(file03);
}
@Test
public void t03Read() {
EasyExcel.read(extraRelationships, ExtraData.class, new ReadListener() {
@Override
public void invoke(Object data, AnalysisContext context) {
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
@Override
public void extra(CellExtra extra, AnalysisContext context) {
LOGGER.info("extra data:{}", JSON.toJSONString(extra));
switch (extra.getType()) {
case HYPERLINK:
if ("222222222".equals(extra.getText())) {
Assert.assertEquals(1, (int)extra.getRowIndex());
Assert.assertEquals(0, (int)extra.getColumnIndex());
} else if ("333333333333".equals(extra.getText())) {
Assert.assertEquals(1, (int)extra.getRowIndex());
Assert.assertEquals(1, (int)extra.getColumnIndex());
} else {
Assert.fail("Unknown hyperlink!");
}
break;
default:
}
}
})
.extraRead(CellExtraTypeEnum.HYPERLINK)
.sheet()
.doRead();
}
private void read(File file) {
EasyExcel.read(file, ExtraData.class, new ExtraDataListener()).extraRead(CellExtraTypeEnum.COMMENT)
.extraRead(CellExtraTypeEnum.HYPERLINK).extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();

52
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/core/nomodel/NoModelDataTest.java

@ -1,14 +1,20 @@
package com.alibaba.easyexcel.test.core.nomodel;
import java.io.File;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.enums.ReadDefaultReturnEnum;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.util.DateUtils;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
@ -19,6 +25,7 @@ import org.junit.runners.MethodSorters;
* @author Jiaju Zhuang
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Slf4j
public class NoModelDataTest {
private static File file07;
@ -28,7 +35,6 @@ public class NoModelDataTest {
private static File fileRepeat03;
private static File fileRepeatCsv;
@BeforeClass
public static void init() {
file07 = TestFileUtil.createNewFile("noModel07.xlsx");
@ -41,20 +47,20 @@ public class NoModelDataTest {
@Test
public void t01ReadAndWrite07() throws Exception {
readAndWrite(file07, fileRepeat07);
readAndWrite(file07, fileRepeat07, false);
}
@Test
public void t02ReadAndWrite03() throws Exception {
readAndWrite(file03, fileRepeat03);
readAndWrite(file03, fileRepeat03, false);
}
@Test
public void t03ReadAndWriteCsv() throws Exception {
readAndWrite(fileCsv, fileRepeatCsv);
readAndWrite(fileCsv, fileRepeatCsv, true);
}
private void readAndWrite(File file, File fileRepeat) throws Exception {
private void readAndWrite(File file, File fileRepeat, boolean isCsv) throws Exception {
EasyExcel.write(file).sheet().doWrite(data());
List<Map<Integer, String>> result = EasyExcel.read(file).headRowNumber(0).sheet().doReadSync();
Assert.assertEquals(10, result.size());
@ -63,6 +69,42 @@ public class NoModelDataTest {
Assert.assertEquals("109", data10.get(1));
Assert.assertEquals("2020-01-01 01:01:01", data10.get(2));
List<Map<Integer, Object>> actualDataList = EasyExcel.read(file)
.headRowNumber(0)
.readDefaultReturn(ReadDefaultReturnEnum.ACTUAL_DATA)
.sheet()
.doReadSync();
log.info("actualDataList:{}", JSON.toJSONString(actualDataList));
Assert.assertEquals(10, actualDataList.size());
Map<Integer, Object> actualData10 = actualDataList.get(9);
Assert.assertEquals("string19", actualData10.get(0));
if (isCsv) {
// CSV only string type
Assert.assertEquals("109", actualData10.get(1));
Assert.assertEquals("2020-01-01 01:01:01", actualData10.get(2));
} else {
Assert.assertEquals(0, new BigDecimal("109").compareTo((BigDecimal)actualData10.get(1)));
Assert.assertEquals(LocalDateTime.of(2020, 1, 1, 1, 1, 1), actualData10.get(2));
}
List<Map<Integer, ReadCellData<?>>> readCellDataList = EasyExcel.read(file)
.headRowNumber(0)
.readDefaultReturn(ReadDefaultReturnEnum.READ_CELL_DATA)
.sheet()
.doReadSync();
log.info("readCellDataList:{}", JSON.toJSONString(readCellDataList));
Assert.assertEquals(10, readCellDataList.size());
Map<Integer, ReadCellData<?>> readCellData10 = readCellDataList.get(9);
Assert.assertEquals("string19", readCellData10.get(0).getData());
if (isCsv) {
// CSV only string type
Assert.assertEquals("109", readCellData10.get(1).getData());
Assert.assertEquals("2020-01-01 01:01:01", readCellData10.get(2).getData());
} else {
Assert.assertEquals(0, new BigDecimal("109").compareTo((BigDecimal)readCellData10.get(1).getData()));
Assert.assertEquals(LocalDateTime.of(2020, 1, 1, 1, 1, 1), readCellData10.get(2).getData());
}
EasyExcel.write(fileRepeat).sheet().doWrite(result);
result = EasyExcel.read(fileRepeat).headRowNumber(0).sheet().doReadSync();
Assert.assertEquals(10, result.size());

129
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java

@ -83,28 +83,13 @@ public class FillTest {
return data();
});
// 方案3.1 分多次 填充 会使用文件缓存(省内存) 使用 try-with-resources @since 3.1.0
// 方案3 分多次 填充 会使用文件缓存(省内存)
fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
excelWriter.fill(data(), writeSheet);
excelWriter.fill(data(), writeSheet);
}
// 方案3.2 分多次 填充 会使用文件缓存(省内存) 不使用 try-with-resources
fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
excelWriter.fill(data(), writeSheet);
excelWriter.fill(data(), writeSheet);
} finally {
// 千万别忘记close 会帮忙关闭流
if (excelWriter != null) {
excelWriter.close();
}
}
}
/**
@ -120,7 +105,7 @@ public class FillTest {
TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx";
String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx";
// 方案1 : 使用 try-with-resources @since 3.1.0
// 方案1
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
@ -135,29 +120,6 @@ public class FillTest {
map.put("total", 1000);
excelWriter.fill(map, writeSheet);
}
// 方案2 : 不使用 try-with-resources
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
// forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
// 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
// 如果数据量大 list不是最后一行 参照下一个
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(data(), fillConfig, writeSheet);
excelWriter.fill(data(), fillConfig, writeSheet);
Map<String, Object> map = MapUtils.newHashMap();
map.put("date", "2019年10月9日13:28:28");
map.put("total", 1000);
excelWriter.fill(map, writeSheet);
} finally {
// 千万别忘记close 会帮忙关闭流
if (excelWriter != null) {
excelWriter.close();
}
}
}
/**
@ -177,7 +139,7 @@ public class FillTest {
String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx";
// 方案1 : 使用 try-with-resources @since 3.1.0
// 方案1
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 直接写入数据
@ -204,42 +166,6 @@ public class FillTest {
// 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以
// 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案
}
// 方案2 : 不使用 try-with-resources
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 直接写入数据
excelWriter.fill(data(), writeSheet);
excelWriter.fill(data(), writeSheet);
// 写入list之前的数据
Map<String, Object> map = new HashMap<String, Object>();
map.put("date", "2019年10月9日13:28:28");
excelWriter.fill(map, writeSheet);
// list 后面还有个统计 想办法手动写入
// 这里偷懒直接用list 也可以用对象
List<List<String>> totalListList = ListUtils.newArrayList();
List<String> totalList = ListUtils.newArrayList();
totalListList.add(totalList);
totalList.add(null);
totalList.add(null);
totalList.add(null);
// 第四列
totalList.add("统计:1000");
// 这里是write 别和fill 搞错了
excelWriter.write(totalListList, writeSheet);
// 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以
// 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案
} finally {
// 千万别忘记close 会帮忙关闭流
if (excelWriter != null) {
excelWriter.close();
}
}
}
/**
@ -255,7 +181,7 @@ public class FillTest {
TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "horizontal.xlsx";
String fileName = TestFileUtil.getPath() + "horizontalFill" + System.currentTimeMillis() + ".xlsx";
// 方案1 : 使用 try-with-resources @since 3.1.0
// 方案1
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
@ -266,25 +192,6 @@ public class FillTest {
map.put("date", "2019年10月9日13:28:28");
excelWriter.fill(map, writeSheet);
}
// 方案2 : 不使用 try-with-resources
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
excelWriter.fill(data(), fillConfig, writeSheet);
excelWriter.fill(data(), fillConfig, writeSheet);
Map<String, Object> map = new HashMap<String, Object>();
map.put("date", "2019年10月9日13:28:28");
excelWriter.fill(map, writeSheet);
} finally {
// 千万别忘记close 会帮忙关闭流
if (excelWriter != null) {
excelWriter.close();
}
}
}
/**
@ -301,7 +208,7 @@ public class FillTest {
String fileName = TestFileUtil.getPath() + "compositeFill" + System.currentTimeMillis() + ".xlsx";
// 方案1 : 使用 try-with-resources @since 3.1.0
// 方案1
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
@ -319,32 +226,6 @@ public class FillTest {
excelWriter.fill(map, writeSheet);
}
// 方案2 : 不使用 try-with-resources
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
// 如果有多个list 模板上必须有{前缀.} 这里的前缀就是 data1,然后多个list必须用 FillWrapper包裹
excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet);
excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet);
excelWriter.fill(new FillWrapper("data2", data()), writeSheet);
excelWriter.fill(new FillWrapper("data2", data()), writeSheet);
excelWriter.fill(new FillWrapper("data3", data()), writeSheet);
excelWriter.fill(new FillWrapper("data3", data()), writeSheet);
Map<String, Object> map = new HashMap<String, Object>();
//map.put("date", "2019年10月9日13:28:28");
map.put("date", new Date());
excelWriter.fill(map, writeSheet);
} finally {
// 千万别忘记close 会帮忙关闭流
if (excelWriter != null) {
excelWriter.close();
}
}
}
private List<FillData> data() {

143
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/rare/WriteTest.java

@ -0,0 +1,143 @@
package com.alibaba.easyexcel.test.demo.rare;
import java.io.File;
import java.util.Date;
import java.util.List;
import com.alibaba.easyexcel.test.demo.write.DemoData;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.FileUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
import com.alibaba.excel.write.handler.context.RowWriteHandlerContext;
import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext;
import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.junit.Ignore;
import org.junit.Test;
/**
* 记录一些不太常见的案例
*
* @author Jiaju Zhuang
*/
@Ignore
@Slf4j
public class WriteTest {
/**
* 压缩临时文件
* 在导出Excel且格式为xlsx的时候会生成一个临时的xml文件会比较大再磁盘不太够的情况下可以压缩
* 当然压缩式耗费性能的
*/
@Test
public void compressedTemporaryFile() {
log.info("临时的xml存储在:{}", FileUtils.getPoiFilesPath());
File file = TestFileUtil.createNewFile("rare/compressedTemporaryFile" + System.currentTimeMillis()
+ ".xlsx");
// 这里 需要指定写用哪个class去写
try (ExcelWriter excelWriter = EasyExcel.write(file, DemoData.class).registerWriteHandler(
new WorkbookWriteHandler() {
/**
* 拦截Workbook创建完成事件
* @param context
*/
@Override
public void afterWorkbookCreate(WorkbookWriteHandlerContext context) {
// 获取到Workbook对象
Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();
// 只有SXSSFWorkbook模式才会生成临时文件
if (workbook instanceof SXSSFWorkbook) {
SXSSFWorkbook sxssfWorkbook = (SXSSFWorkbook)workbook;
// 设置临时文件压缩,当然这个会浪费cpu性能 但是临时文件会变小
sxssfWorkbook.setCompressTempFiles(true);
}
}
}).build()) {
// 这里注意 如果同一个sheet只要创建一次
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
// 10万数据 确保有足够的空间
for (int i = 0; i < 10000; i++) {
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
log.info("写入完毕,开始准备迁移压缩文件。");
}
}
/**
* 在指定单元格写入数据
*/
@Test
public void specifiedCellWrite() {
File file = TestFileUtil.createNewFile("rare/specifiedCellWrite" + System.currentTimeMillis()
+ ".xlsx");
// 需要区分是在 最后一行之前 还是之后
// 区分的原因是:excel只能一直向前,而且内存里面只存储100条,而afterRowDispose是在每一行写入完成的时候调用,所以修改一行需要拦截这个事件
// 如果是在最后一行之后,由于后面不会再有数据了,所以只要拦截afterWorkbookDispose,在整个excel快写完的时候调用,继续写入数据即可
EasyExcel.write(file, DemoData.class)
// 写入的值在最后一行之前
.registerWriteHandler(new RowWriteHandler() {
@Override
public void afterRowDispose(RowWriteHandlerContext context) {
if (context.getRow().getRowNum() == 2) {
Cell cell = context.getRow().getCell(2);
if (cell == null) {
cell = context.getRow().createCell(2);
}
cell.setCellValue("测试的第二行数据呀");
}
}
})
// 写入的值 在最后一一行之后
.registerWriteHandler(new WorkbookWriteHandler() {
@Override
public void afterWorkbookDispose(WorkbookWriteHandlerContext context) {
Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();
Sheet sheet = workbook.getSheetAt(0);
Row row = sheet.getRow(99);
if (row == null) {
row = sheet.createRow(99);
}
Cell cell = row.getCell(2);
if (cell == null) {
cell = row.createCell(2);
}
cell.setCellValue("测试地99行数据呀");
}
})
.sheet("模板")
.doWrite(data());
log.info("写入到文件完成:{}", file);
}
private List<DemoData> data() {
List<DemoData> list = ListUtils.newArrayList();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
}

42
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java

@ -47,7 +47,7 @@ public class ReadTest {
// since: 3.0.0-beta1
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
// 这里每次会读取3000条数据 然后返回过来 直接调用使用数据就行
// 这里每次会读取100条数据 然后返回过来 直接调用使用数据就行
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
for (DemoData demoData : dataList) {
log.info("读取到一条数据{}", JSON.toJSONString(demoData));
@ -98,7 +98,7 @@ public class ReadTest {
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
// 写法4: 使用 try-with-resources @since 3.1.0
// 写法4
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 一个文件一个reader
try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) {
@ -107,23 +107,6 @@ public class ReadTest {
// 读取一个sheet
excelReader.read(readSheet);
}
// 写法5: 不使用 try-with-resources
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 一个文件一个reader
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
// 构建一个sheet 这里可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 读取一个sheet
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.close();
}
}
}
/**
@ -162,7 +145,7 @@ public class ReadTest {
// 读取部分sheet
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 写法1: 使用 try-with-resources @since 3.1.0
// 写法1
try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {
// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet1 =
@ -172,25 +155,6 @@ public class ReadTest {
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
}
// 写法2: 不使用 try-with-resources
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName).build();
// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.close();
}
}
}
/**

1
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/DemoData.java

@ -24,6 +24,7 @@ public class DemoData {
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/

109
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java

@ -86,28 +86,13 @@ public class WriteTest {
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
// 写法3:使用 try-with-resources @since 3.1.0
// 写法3
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
}
// 写法4: 不使用 try-with-resources
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
} finally {
// 千万别忘记close 会帮忙关闭流
if (excelWriter != null) {
excelWriter.close();
}
}
}
/**
@ -185,7 +170,7 @@ public class WriteTest {
*/
@Test
public void repeatedWrite() {
// 方法1.1: 如果写到同一个sheet 使用 try-with-resources @since 3.1.0
// 方法1: 如果写到同一个sheet
String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
@ -199,28 +184,7 @@ public class WriteTest {
}
}
// 方法1.2: 如果写到同一个sheet 不使用 try-with-resources
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
ExcelWriter writer = null;
try {
// 这里 需要指定写用哪个class去写
writer = EasyExcel.write(fileName, DemoData.class).build();
// 这里注意 如果同一个sheet只要创建一次
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
for (int i = 0; i < 5; i++) {
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
writer.write(data, writeSheet);
}
} finally {
// 千万别忘记close 会帮忙关闭流
if (writer != null) {
writer.close();
}
}
// 方法2.1: 如果写到不同的sheet 同一个对象 使用 try-with-resources @since 3.1.0
// 方法2: 如果写到不同的sheet 同一个对象
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
@ -234,27 +198,7 @@ public class WriteTest {
}
}
// 方法2.2: 如果写到不同的sheet 同一个对象 不使用 try-with-resources
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
try {
// 这里 指定文件
writer = EasyExcel.write(fileName, DemoData.class).build();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
writer.write(data, writeSheet);
}
} finally {
// 千万别忘记close 会帮忙关闭流
if (writer != null) {
writer.close();
}
}
// 方法3.1 如果写到不同的sheet 不同的对象 使用 try-with-resources @since 3.1.0
// 方法3 如果写到不同的sheet 不同的对象
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
@ -269,26 +213,6 @@ public class WriteTest {
}
}
// 方法3.2 如果写到不同的sheet 不同的对象 不使用 try-with-resources
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
try {
// 这里 指定文件
writer = EasyExcel.write(fileName).build();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class
// 实际上可以一直变
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
writer.write(data, writeSheet);
}
} finally {
// 千万别忘记close 会帮忙关闭流
if (writer != null) {
writer.close();
}
}
}
/**
@ -649,7 +573,7 @@ public class WriteTest {
@Test
public void tableWrite() {
String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx";
// 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案, 使用 try-with-resources @since 3.1.0
// 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案
// 这里 需要指定写用哪个class去写
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
// 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
@ -662,27 +586,6 @@ public class WriteTest {
// 第二次写如也会创建头,然后在第一次的后面写入数据
excelWriter.write(data(), writeSheet, writeTable1);
}
// 方法2 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案, 不使用 try-with-resources
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
// 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();
// 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要
WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();
WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();
// 第一次写入会创建头
excelWriter.write(data(), writeSheet, writeTable0);
// 第二次写如也会创建头,然后在第一次的后面写入数据
excelWriter.write(data(), writeSheet, writeTable1);
} finally {
// 千万别忘记close 会帮忙关闭流
if (excelWriter != null) {
excelWriter.close();
}
}
}
/**
@ -839,8 +742,8 @@ public class WriteTest {
for (int i = 0; i < 10; i++) {
List<Object> data = ListUtils.newArrayList();
data.add("字符串" + i);
data.add(new Date());
data.add(0.56);
data.add(new Date());
list.add(data);
}
return list;

35
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData2.java

@ -0,0 +1,35 @@
package com.alibaba.easyexcel.test.temp;
import java.math.BigDecimal;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
/**
* 基础数据类
*
* @author Jiaju Zhuang
**/
@Getter
@Setter
@EqualsAndHashCode
public class DemoData2 {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
@ExcelProperty("数字标题2")
private BigDecimal bigDecimal;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}

24
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/DemoData3.java

@ -0,0 +1,24 @@
package com.alibaba.easyexcel.test.temp;
import java.time.LocalDateTime;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
/**
* 基础数据类
*
* @author Jiaju Zhuang
**/
@Getter
@Setter
@EqualsAndHashCode
public class DemoData3 {
@ExcelProperty("日期时间标题")
private LocalDateTime localDateTime;
}

314
easyexcel-test/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java

@ -1,32 +1,49 @@
package com.alibaba.easyexcel.test.temp;
import java.io.File;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.alibaba.easyexcel.test.demo.write.DemoData;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.util.NumberDataFormatterUtils;
import com.alibaba.excel.util.NumberUtils;
import com.alibaba.excel.util.PositionUtils;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* 临时测试
*
* @author Jiaju Zhuang
**/
@Slf4j
public class Lock2Test {
private static final Logger LOGGER = LoggerFactory.getLogger(Lock2Test.class);
@ -37,7 +54,11 @@ public class Lock2Test {
// File file = TestFileUtil.readUserHomeFile("test/test6.xls");
File file = new File("/Users/zhuangjiaju/IdeaProjects/easyexcel/src/test/resources/converter/converter07.xlsx");
List<Object> list = EasyExcel.read("/Users/zhuangjiaju/Downloads/测试格式.xlsx").sheet(0).headRowNumber(0).doReadSync();
List<Object> list = EasyExcel.read(
"/Users/zhuangjiaju/IdeaProjects/easyexcel/easyexcel-test/target/test-classes/simpleWrite1674051907397.xlsx")
//.useDefaultListener(false)
.sheet(0)
.headRowNumber(0).doReadSync();
LOGGER.info("数据:{}", list.size());
for (Object data : list) {
LOGGER.info("返回数据:{}", CollectionUtils.size(data));
@ -109,18 +130,6 @@ public class Lock2Test {
return list;
}
private List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
@Test
public void testc() throws Exception {
LOGGER.info("reslut:{}", JSON.toJSONString(new CellReference("B3")));
@ -151,10 +160,9 @@ public class Lock2Test {
@Test
public void test335() throws Exception {
LOGGER.info("reslut:{}", PositionUtils.getCol("A10",null));
LOGGER.info("reslut:{}", PositionUtils.getCol("A10", null));
LOGGER.info("reslut:{}", PositionUtils.getRow("A10"));
LOGGER.info("reslut:{}", PositionUtils.getCol("AB10",null));
LOGGER.info("reslut:{}", PositionUtils.getCol("AB10", null));
LOGGER.info("reslut:{}", PositionUtils.getRow("AB10"));
//LOGGER.info("reslut:{}", PositionUtils2.getCol("A10",null));
@ -163,5 +171,279 @@ public class Lock2Test {
//LOGGER.info("reslut:{}", PositionUtils2.getRow("AB10"));
}
@Test
public void numberforamt() throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
//LOGGER.info("date:{}",
// NumberDataFormatterUtils.format(BigDecimal.valueOf(44727.99998842592), (short)200, "yyyy-MM-dd HH:mm:ss",
// null,
// null, null));
//
//LOGGER.info("date:{}",
// NumberDataFormatterUtils.format(BigDecimal.valueOf(44728.99998842592), (short)200, "yyyy-MM-dd HH:mm:ss",
// null,
// null, null));
//
//LOGGER.info("date:{}",
// NumberDataFormatterUtils.format(BigDecimal.valueOf(44729.99998836806), (short)200, "yyyy-MM-dd HH:mm:ss",
// null,
// null, null));
//
//LOGGER.info("date:{}",
// NumberDataFormatterUtils.format(BigDecimal.valueOf(44727.99998842592).setScale(10, RoundingMode
// .HALF_UP), (short)200, "yyyy-MM-dd HH:mm:ss",
// null,
// null, null));
//
//LOGGER.info("date:{}",
// NumberDataFormatterUtils.format(BigDecimal.valueOf(44728.99998842592).setScale(10, RoundingMode
// .HALF_UP), (short)200, "yyyy-MM-dd HH:mm:ss",
// null,
// null, null));
//44729.9999883681
//44729.999988368058
//LOGGER.info("date:{}",
// NumberDataFormatterUtils.format(BigDecimal.valueOf(44729.999988368058).setScale(10, RoundingMode
// .HALF_UP), (short)200, "yyyy-MM-dd HH:mm:ss",
// null,
// null, null));
//LOGGER.info("date:{}",BigDecimal.valueOf(44729.999988368058).setScale(10, RoundingMode.HALF_UP).doubleValue
// ());
// 2022/6/17 23:59:59
// 期望 44729.99998842592
//LOGGER.info("data:{}", DateUtil.getJavaDate(44729.9999883681, true));
LOGGER.info("data4:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058)
.setScale(4, RoundingMode.HALF_UP).doubleValue(), false));
LOGGER.info("data5:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058)
.setScale(5, RoundingMode.HALF_UP).doubleValue(), false));
LOGGER.info("data6:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058)
.setScale(6, RoundingMode.HALF_UP).doubleValue(), false));
LOGGER.info("data7:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058)
.setScale(7, RoundingMode.HALF_UP).doubleValue(), false));
LOGGER.info("data8:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999988368058)
.setScale(8, RoundingMode.HALF_UP).doubleValue(), false));
LOGGER.info("data:{}", format.format(DateUtil.getJavaDate(44729.999988368058, false)));
LOGGER.info("data:{}", format.format(DateUtil.getJavaDate(44729.9999883681, false)));
LOGGER.info("data:{}", DateUtil.getJavaDate(Double.parseDouble("44729.999988368058"), false));
LOGGER.info("data:{}", DateUtil.getJavaDate(Double.parseDouble("44729.9999883681"), false));
// 44729.999976851854
// 44729.999988368058
LOGGER.info("data:{}", DateUtil.getExcelDate(format.parse("2022-06-17 23:59:58")));
// 44729.99998842592
LOGGER.info("data:{}", DateUtil.getExcelDate(format.parse("2022-06-17 23:59:59")));
LOGGER.info("data:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999976851854)
.setScale(10, RoundingMode.HALF_UP).doubleValue(), false));
LOGGER.info("data:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.99998842592)
.setScale(10, RoundingMode.HALF_UP).doubleValue(), false));
LOGGER.info("data:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.999976851854)
.setScale(5, RoundingMode.HALF_UP).doubleValue(), false));
LOGGER.info("data:{}", DateUtil.getJavaDate(BigDecimal.valueOf(44729.99998842592)
.setScale(5, RoundingMode.HALF_UP).doubleValue(), false));
}
@Test
public void testDate() throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info("TT:{}", format.format(new Date(100L)));
log.info("TT:{}", new Date().getTime());
}
@Test
public void testDateAll() throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
long dateTime = 0L;
while (true) {
Date date = new Date(dateTime);
double excelDate = DateUtil.getExcelDate(date);
Assert.assertEquals("测试基本转换错误" + dateTime, format.format(date),
format.format(DateUtil.getJavaDate(excelDate, false)));
Assert.assertEquals("测试精度5转换错误" + dateTime, format.format(date),
format.format(DateUtil.getJavaDate(BigDecimal.valueOf(excelDate)
.setScale(10, RoundingMode.HALF_UP).doubleValue(), false)));
LOGGER.info("date:{}", format2.format(DateUtil.getJavaDate(BigDecimal.valueOf(excelDate)
.setScale(10, RoundingMode.HALF_UP).doubleValue())));
dateTime += 1000L;
// 30天输出
if (dateTime % (24 * 60 * 60 * 1000) == 0) {
log.info("{}成功", format.format(date));
}
if (dateTime > 1673957544750L) {
log.info("结束啦");
break;
}
}
log.info("结束啦");
}
@Test
public void numberforamt3() throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
List<Map<Integer, ReadCellData>> list = EasyExcel.read("/Users/zhuangjiaju/Downloads/date3.xlsx")
.useDefaultListener(false)
.sheet(0)
.headRowNumber(0).doReadSync();
LOGGER.info("数据:{}", list.size());
for (Map<Integer, ReadCellData> readCellDataMap : list) {
ReadCellData data = readCellDataMap.get(0);
LOGGER.info("data:{}", format.format(
DateUtil.getJavaDate(data.getNumberValue().setScale(10, RoundingMode.HALF_UP).doubleValue(), false)));
}
//
//LOGGER.info("data:{}", format.format(DateUtil.getJavaDate(44727.999988425923, false)));
//LOGGER.info("data:{}", format.format(DateUtil.getJavaDate(44729.999988368058, false)));
}
@Test
public void numberforamt4() throws Exception {
String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class)
.sheet("模板")
.doWrite(() -> {
// 分页查询数据
return data2();
});
}
@Test
public void numberforamt77() throws Exception {
String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData3.class)
.sheet("模板")
.doWrite(() -> {
List<DemoData3> list = new ArrayList<>();
DemoData3 demoData3 = new DemoData3();
demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 400000000));
list.add(demoData3);
demoData3 = new DemoData3();
demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 499000000));
list.add(demoData3);
demoData3 = new DemoData3();
demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 500000000));
list.add(demoData3);
demoData3 = new DemoData3();
demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 501000000));
list.add(demoData3);
demoData3 = new DemoData3();
demoData3.setLocalDateTime(LocalDateTime.of(2023, 1, 1, 0, 0, 0, 995000000));
list.add(demoData3);
return list;
});
}
@Test
public void numberforamt99() throws Exception {
LocalDateTime localDateTime=LocalDateTime.of(2023, 1, 1, 0, 0, 0, 995000000);
log.info("date:{}",localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
}
@Test
public void numberforamt5() throws Exception {
String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class)
.sheet("模板")
.doWrite(() -> {
// 分页查询数据
return data3();
});
}
@Test
public void numberforamt6() throws Exception {
DecimalFormat decimalFormat = new DecimalFormat("#.#");
BigDecimal bigDecimal = new BigDecimal(3101011021236149800L);
log.info("b:{}", bigDecimal);
log.info("b:{}", bigDecimal.setScale(-4, RoundingMode.HALF_UP));
log.info("b:{}", decimalFormat.format(bigDecimal.setScale(-4, RoundingMode.HALF_UP)));
}
@Test
public void numberforamt7() throws Exception {
DecimalFormat decimalFormat = new DecimalFormat("#.#");
BigDecimal bigDecimal = new BigDecimal(3.1010110212361498E+18).round(new MathContext(15, RoundingMode.HALF_UP));
//bigDecimal.
// bigDecimal
log.info("b:{}", bigDecimal);
log.info("b:{}", bigDecimal.setScale(-4, RoundingMode.HALF_UP));
log.info("b:{}", decimalFormat.format(bigDecimal.setScale(-4, RoundingMode.HALF_UP)));
log.info("b:{}", decimalFormat.format(bigDecimal));
}
private List<DemoData2> data3() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
List<DemoData2> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DemoData2 data = new DemoData2();
data.setString("字符串" + i);
data.setDoubleData(0.56);
data.setBigDecimal(BigDecimal.valueOf(3101011021236149800L));
list.add(data);
}
return list;
}
private List<DemoData> data() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
try {
data.setDate(format.parse("2032-01-18 09:00:01.995"));
} catch (ParseException e) {
throw new RuntimeException(e);
}
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
private List<DemoData> data2() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
try {
data.setDate(format.parse("2032-01-18 09:00:00."));
} catch (ParseException e) {
throw new RuntimeException(e);
}
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
}

BIN
easyexcel-test/src/test/resources/compatibility/t02.xlsx

Binary file not shown.

BIN
easyexcel-test/src/test/resources/compatibility/t03.xlsx

Binary file not shown.

BIN
easyexcel-test/src/test/resources/compatibility/t04.xlsx

Binary file not shown.

BIN
easyexcel-test/src/test/resources/compatibility/t05.xlsx

Binary file not shown.

BIN
easyexcel-test/src/test/resources/compatibility/t06.xlsx

Binary file not shown.

BIN
easyexcel-test/src/test/resources/compatibility/t07.xlsx

Binary file not shown.

BIN
easyexcel-test/src/test/resources/extra/extraRelationships.xlsx

Binary file not shown.

1
easyexcel/pom.xml

@ -13,6 +13,7 @@
<packaging>jar</packaging>
<artifactId>easyexcel</artifactId>
<name>easyexcel</name>
<dependencies>

26
pom.xml

@ -20,7 +20,7 @@
<properties>
<revision>3.1.0</revision>
<revision>3.2.0</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jdk.version>1.8</jdk.version>
<gpg.skip>true</gpg.skip>
@ -229,6 +229,11 @@
<artifactId>flatten-maven-plugin</artifactId>
<version>1.2.7</version>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
</plugin>
</plugins>
</pluginManagement>
@ -307,6 +312,13 @@
</goals>
</execution>
</executions>
<!-- Configuration GPG plug-ins don't tip password -->
<configuration>
<gpgArguments>
<argument>--pinentry-mode</argument>
<argument>loopback</argument>
</gpgArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -389,6 +401,18 @@
</execution>
</executions>
</plugin>
<!-- Publish package to the Maven Central Repository -->
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</project>

49
update.md

@ -1,4 +1,31 @@
# 3.2.0
* 修复部分xlsx读取日期可能相差1秒的bug [Issue #1956](https://github.com/alibaba/easyexcel/issues/1956)
* 修复部分数据精度和excel不匹配的bug [Issue #2805](https://github.com/alibaba/easyexcel/issues/2805)
* 不创建对象的读支持读取原始的数据类型
# 3.1.5
* 提高xlsx读取兼容性:兼用ns2开头的标签
# 3.1.4
* 提高xlsx读取兼容性:在存在第一行很多空列的情况下,忽略空列
# 3.1.3
* 提高xlsx兼容性:兼容`sharedStrings.xml` 存在 `x:t`标签的情况
# 3.1.2
* 修复长时间运行会产生大对象的bug [Issue #2725](https://github.com/alibaba/easyexcel/issues/2725)
# 3.1.1
* 修复部分xlsx无法读取超链接的bug
# 3.1.0
* 支持jdk17,去除cglib&asm依赖,改成重新拷贝一份 [Issue #2240](https://github.com/alibaba/easyexcel/issues/2240)
* 升级ehcache 到 3.9.9 ,为了兼容jdk17
* 在有样式没有数据的情况下也算空行 [Issue #2294](https://github.com/alibaba/easyexcel/issues/2294)
@ -14,24 +41,27 @@
* csv支持设置读写编码 [Issue #2404](https://github.com/alibaba/easyexcel/issues/2404)
* 读写支持`try-with-resources`语法糖
# 3.0.5
* 修复`ReadListener` 转换异常不抛出的问题
# 3.0.4
* 调整读写默认大小,防止大批量写的时候可能会full gc
* `fill`的情况新增 `afterRowDispose`事件
# 3.0.3
* 修复`HeadStyle`无效的bug
# 3.0.2
* 大幅提升读写性能
* 修复列宽注解没用的bug [Issue #2151](https://github.com/alibaba/easyexcel/issues/2151)
* 修复`CellData`接收失败的的bug [Issue #2147](https://github.com/alibaba/easyexcel/issues/2147)
# 3.0.1
* 升级到正式版
* 修复填充样式可能丢失的问题 [Issue #2124](https://github.com/alibaba/easyexcel/issues/2124)
* 修复填充数据为空 可能NPE的bug
@ -39,7 +69,6 @@
* 修复样式可能超过最大限制的bug
* 修复写入过慢的bug
# 3.0.0-beta3
* 修复导出浮点型数据可能精度异常的bug
@ -122,7 +151,8 @@
* 修复xls仅公式行 不读取的bug [Issue #1324](https://github.com/alibaba/easyexcel/issues/1324)
* 修复xls直接读取第2页 `NPE` 的bug [Issue #1280](https://github.com/alibaba/easyexcel/issues/1280)
* 修复填充的时候,最后一行中间有空行会创建失败的bug
* 修复`includeColumnIndexes`不包含第列 会无法导出数据的bug [Issue #1346](https://github.com/alibaba/easyexcel/issues/1346)
* 修复`includeColumnIndexes`不包含第列
会无法导出数据的bug [Issue #1346](https://github.com/alibaba/easyexcel/issues/1346)
* 修复`@NumberFormat`注解转换double时可能会丢失精度 [Issue #1306](https://github.com/alibaba/easyexcel/issues/1306)
# 2.2.3
@ -139,11 +169,16 @@
* 发布正式版
* 修复第一行为空不会调用`invokeHeadMap`的bug [Issue #993](https://github.com/alibaba/easyexcel/issues/993)
* 当类的属性没有按照ExcelProperty的属性index顺序排序的时候,写数据出现错乱 [Issue #1046](https://github.com/alibaba/easyexcel/issues/1046)
* 新增支持自定义转换器 入参可以为空 实现`NullableObjectConverter` 即可 [Issue #1084](https://github.com/alibaba/easyexcel/issues/1084)
*
当类的属性没有按照ExcelProperty的属性index顺序排序的时候,写数据出现错乱 [Issue #1046](https://github.com/alibaba/easyexcel/issues/1046)
* 新增支持自定义转换器 入参可以为空 实现`NullableObjectConverter`
即可 [Issue #1084](https://github.com/alibaba/easyexcel/issues/1084)
* 修复xls丢失结束标记的情况下 会漏读最后一行
* 修复填充的时候 多次`forceNewRow` 空指针的bug [Issue #1201](https://github.com/alibaba/easyexcel/issues/1201)
* 修复`table`、`sheet`中创建的拦截器不执行`workbook`事件的bug [Issue #1202](https://github.com/alibaba/easyexcel/issues/1202)
* 修复`table`、`sheet`中创建的拦截器不执行`workbook`
事件的bug [Issue #1202](https://github.com/alibaba/easyexcel/issues/1202)
# 2.2.0-beta2

Loading…
Cancel
Save