From 83d6428db84660eee6da8685d6001069f90a94db Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 24 Feb 2020 02:44:05 +0800 Subject: [PATCH] support mergeCell --- CHANGELOG.md | 2 + .../cn/hutool/db/dialect/DialectName.java | 4 +- .../java/cn/hutool/db/sql/SqlBuilder.java | 4 +- .../java/cn/hutool/poi/excel/RowUtil.java | 2 +- .../cn/hutool/poi/excel/cell/CellUtil.java | 88 ++++++++++++++++-- .../java/cn/hutool/poi/word/Word07Writer.java | 21 ++--- .../hutool/poi/excel/test/ExcelReadTest.java | 26 ++++-- hutool-poi/src/test/resources/merge_test.xlsx | Bin 0 -> 11395 bytes 8 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 hutool-poi/src/test/resources/merge_test.xlsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 17c8d2a66..68eb83149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ## 5.1.5 ### 新特性 +* 【poi 】 Excel合并单元格读取同一个值,不再为空 + ### Bug修复 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java index 63ecd1396..8c6c911f7 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java @@ -1,7 +1,9 @@ package cn.hutool.db.dialect; /** - * 方言名 + * 方言名
+ * 方言枚举列出了Hutool支持的所有数据库方言 + * * @author Looly * */ diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java index c32b50994..f5ca1de29 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java @@ -531,7 +531,7 @@ public class SqlBuilder implements Builder{ * @return 插入或更新的数据库字段列表 */ public String[] getFieldArray() { - return this.fields.toArray(new String[this.fields.size()]); + return this.fields.toArray(new String[0]); } /** @@ -558,7 +558,7 @@ public class SqlBuilder implements Builder{ * @return 占位符对应的值列表 */ public Object[] getParamValueArray() { - return this.paramValues.toArray(new Object[this.paramValues.size()]); + return this.paramValues.toArray(new Object[0]); } /** diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java index 39c7618de..1a5684ed7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java @@ -49,7 +49,7 @@ public class RowUtil { if (length < 0) { return new ArrayList<>(0); } - final List cellValues = new ArrayList<>((int) length); + final List cellValues = new ArrayList<>(length); Object cellValue; boolean isAllNull = true; for (short i = 0; i < length; i++) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java index 07092d5db..b369e5a1e 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java @@ -2,6 +2,7 @@ package cn.hutool.poi.excel.cell; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.StyleSet; import cn.hutool.poi.excel.editors.TrimEditor; import org.apache.poi.ss.usermodel.Cell; @@ -102,6 +103,12 @@ public class CellUtil { cellType = cell.getCellTypeEnum(); } + if(CellType.BLANK == cellType){ + // 空白单元格可能为合并单元格 + cell = getMergedRegionCell(cell); + cellType = cell.getCellType(); + } + Object value; switch (cellType) { case NUMERIC: @@ -239,17 +246,42 @@ public class CellUtil { /** * 判断指定的单元格是否是合并单元格 * - * @param sheet {@link Sheet} - * @param row 行号 - * @param column 列号 + * @param sheet {@link Sheet} + * @param locationRef 单元格地址标识符,例如A11,B5 + * @return 是否是合并单元格 + * @since 5.1.5 + */ + public static boolean isMergedRegion(Sheet sheet, String locationRef) { + final CellLocation cellLocation = ExcelUtil.toLocation(locationRef); + return isMergedRegion(sheet, cellLocation.getX(), cellLocation.getY()); + } + + /** + * 判断指定的单元格是否是合并单元格 + * + * @param cell {@link Cell} + * @return 是否是合并单元格 + * @since 5.1.5 + */ + public static boolean isMergedRegion(Cell cell) { + return isMergedRegion(cell.getSheet(), cell.getColumnIndex(), cell.getRowIndex()); + } + + /** + * 判断指定的单元格是否是合并单元格 + * + * @param sheet {@link Sheet} + * @param x 列号,从0开始 + * @param y 行号,从0开始 * @return 是否是合并单元格 */ - public static boolean isMergedRegion(Sheet sheet, int row, int column) { + public static boolean isMergedRegion(Sheet sheet, int x, int y) { final int sheetMergeCount = sheet.getNumMergedRegions(); CellRangeAddress ca; for (int i = 0; i < sheetMergeCount; i++) { ca = sheet.getMergedRegion(i); - if (row >= ca.getFirstRow() && row <= ca.getLastRow() && column >= ca.getFirstColumn() && column <= ca.getLastColumn()) { + if (y >= ca.getFirstRow() && y <= ca.getLastRow() + && x >= ca.getFirstColumn() && x <= ca.getLastColumn()) { return true; } } @@ -284,17 +316,57 @@ public class CellUtil { return sheet.addMergedRegion(cellRangeAddress); } + /** + * 获取合并单元格的值
+ * 传入的x,y坐标(列行数)可以是合并单元格范围内的任意一个单元格 + * + * @param sheet {@link Sheet} + * @param locationRef 单元格地址标识符,例如A11,B5 + * @return 合并单元格的值 + * @since 5.1.5 + */ + public static Object getMergedRegionValue(Sheet sheet, String locationRef) { + final CellLocation cellLocation = ExcelUtil.toLocation(locationRef); + return getMergedRegionValue(sheet, cellLocation.getX(), cellLocation.getY()); + } + /** * 获取合并单元格的值
* 传入的x,y坐标(列行数)可以是合并单元格范围内的任意一个单元格 * * @param sheet {@link Sheet} - * @param y 行号,从0开始,可以是合并单元格范围中的任意一行 * @param x 列号,从0开始,可以是合并单元格范围中的任意一列 + * @param y 行号,从0开始,可以是合并单元格范围中的任意一行 * @return 合并单元格的值 * @since 4.6.3 */ public static Object getMergedRegionValue(Sheet sheet, int x, int y) { + return getCellValue(getMergedRegionCell(sheet, x, y)); + } + + /** + * 获取合并单元格
+ * 传入的x,y坐标(列行数)可以是合并单元格范围内的任意一个单元格 + * + * @param cell {@link Cell} + * @return 合并单元格 + * @since 5.1.5 + */ + public static Cell getMergedRegionCell(Cell cell) { + return getMergedRegionCell(cell.getSheet(), cell.getColumnIndex(), cell.getRowIndex()); + } + + /** + * 获取合并单元格
+ * 传入的x,y坐标(列行数)可以是合并单元格范围内的任意一个单元格 + * + * @param sheet {@link Sheet} + * @param x 列号,从0开始,可以是合并单元格范围中的任意一列 + * @param y 行号,从0开始,可以是合并单元格范围中的任意一行 + * @return 合并单元格,如果非合并单元格,返回坐标对应的单元格 + * @since 5.1.5 + */ + public static Cell getMergedRegionCell(Sheet sheet, int x, int y) { final List addrs = sheet.getMergedRegions(); int firstColumn; @@ -309,12 +381,12 @@ public class CellUtil { if (y >= firstRow && y <= lastRow) { if (x >= firstColumn && x <= lastColumn) { - return getCellValue(SheetUtil.getCell(sheet, firstRow, firstColumn)); + return SheetUtil.getCell(sheet, firstRow, firstColumn); } } } - return null; + return SheetUtil.getCell(sheet, y, x); } // -------------------------------------------------------------------------------------------------------------- Private method start diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java b/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java index d224de536..d1693e332 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java @@ -1,21 +1,20 @@ package cn.hutool.poi.word; -import java.awt.Font; -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; - -import org.apache.poi.xwpf.usermodel.ParagraphAlignment; -import org.apache.poi.xwpf.usermodel.XWPFDocument; -import org.apache.poi.xwpf.usermodel.XWPFParagraph; -import org.apache.poi.xwpf.usermodel.XWPFRun; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; +import org.apache.poi.xwpf.usermodel.ParagraphAlignment; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; + +import java.awt.Font; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; /** * Word生成器 diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java index fb12b0afc..2f3816802 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java @@ -15,16 +15,15 @@ import cn.hutool.poi.excel.ExcelUtil; /** * Excel读取单元测试 - * - * @author Looly * + * @author Looly */ public class ExcelReadTest { - + @Test public void aliasTest() { ExcelReader reader = ExcelUtil.getReader(ResourceUtil.getStream("alias.xlsx")); - + //读取单个单元格内容测试 Object value = reader.readCellValue(1, 2); Assert.assertEquals("仓库", value); @@ -77,13 +76,13 @@ public class ExcelReadTest { Assert.assertEquals(11L, readAll.get(1).get(2)); Assert.assertEquals(41.5D, readAll.get(1).get(3)); } - + @Test public void excelReadAsTextTest() { ExcelReader reader = ExcelUtil.getReader(ResourceUtil.getStream("aaa.xlsx")); Assert.assertNotNull(reader.readAsText(false)); } - + @Test public void excel03ReadTest() { ExcelReader reader = ExcelUtil.getReader(ResourceUtil.getStream("aaa.xls")); @@ -141,7 +140,7 @@ public class ExcelReadTest { Assert.assertEquals("男", all.get(0).getGender()); Assert.assertEquals(Integer.valueOf(11), all.get(0).getAge()); } - + @Test @Ignore public void excelReadToBeanListTest2() { @@ -150,7 +149,7 @@ public class ExcelReadTest { reader.addHeaderAlias("年龄", "age"); reader.addHeaderAlias("性别", "gender"); - List all = reader.read(0,2, Person.class); + List all = reader.read(0, 2, Person.class); for (Person person : all) { Console.log(person); } @@ -193,11 +192,20 @@ public class ExcelReadTest { @Test @Ignore - public void readDoubleTest(){ + public void readDoubleTest() { ExcelReader reader = ExcelUtil.getReader("f:/test/doubleTest.xls"); final List> read = reader.read(); for (List list : read) { Console.log(list.get(8)); } } + + @Test + public void mergeReadTest() { + final ExcelReader reader = ExcelUtil.getReader("merge_test.xlsx"); + final List> read = reader.read(); + // 验证合并单元格在两行中都可以取到值 + Assert.assertEquals(11L, read.get(1).get(2)); + Assert.assertEquals(11L, read.get(2).get(2)); + } } diff --git a/hutool-poi/src/test/resources/merge_test.xlsx b/hutool-poi/src/test/resources/merge_test.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a6b335a5b5dcb5ebd354f1a8e49562e3e8fe429a GIT binary patch literal 11395 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

fW4mn15ZPLM!pvn}}gWl~66%v_KXbWB% zYE8%SRXfSWjxlzU+%39TQRxzXnNtj!Y+}p50c@?XBYfjI&m{jCkW#dvj-&Yue%F#Y zS3=a>6lDoWLVDA_nMd#+G^VlYL<`2QrXO^gmOjE6p;TvXn*3~Dz3(Os&HN1O^*a*ym zK$EX5?HN#tin37yKy4hPns61Wd;Fe&-uaq%gi@f;*1SxV58Kf9m}leVWws7yMsW44 ziVeq18~$3M{4Z2>9uDIDNd+AXI!0ffjfKhZ0gZ+7U-At~9-al6itc+|y|eR2g)VmZ zGJJl8@TZ+QRxcA)_y3Crezl1H=>c%38C~Ky{4R9Pmr5W~Ihxu?mVB1x9w2E5%D8RL!cl}goJ%5u$uC=|u_{+v5G6n+47YM{ylq78 zIw~erca6_db6U*a1b4`(-#N|YvRqB7%v|2$Qq?S7{%RGZWbLU?$Fo@lwV16&42!hM zR-daltDlN#rm#uz$TZjsq2<{apCKmXMUdAE^uV$9hgDR+D@$hh z|5^pd-&Vo72tn8-oEaSak5$0E?i)q#>V+?a!vtgg;olu2v2}`oSE2$=HW=Umi@B4T zva6G`8>^XMDUt4?cp1a zjNCRa^Oqhz*|bfgMgUV6-HyDJScX#IG^B-%4W@U9Cc<*z>#fa;y!c+RYMnwwXKm}A zwN3yFD0%Cd9Gy5!(yW<2F7yXo;xy9%>&?11tdBpL;0x5i^%-Ci@xKQ3GaU6yA62ie z#T*0PhkdDm9HYJ_^6H$6m2el6Wv3Wvs_+fM#!ryt?+8)GhW91(U%vnpg9M*fBYMxxGgwCZ%4naU#2pTZ7RA!n@+9ClJ*CZhj6>`a`U{~L8bsruKI zl{^OS#e=7LP`9WNM{!x^bP*z&?1jv)=BfcQzO&~1xD0Y3y@W&WEK-SK4^TW8K=z#^ zX7zEcqYwI?l;1H(1E{E7e5~zf#N_+iLPo6khC6MH2%A{vmX5aWaE37%v>5h@u@B&* z3_jQ4nPT||y@pkl!ezlJmqKP7O|HySv5~5!EIO-Ed1muANBHpC(X-^*1vry$&MGD*XpKxf{l|@Aa-u_a8goet zx(dF)r0*hjdesM?WXD&r%QYtG zBEzN|P-I9!K(^1MM#6!QSEt+WSwoVArf!`V_~S<8S`MDk@j4tJ!=hp;B6z_v7kW)g zN!}ryxFRlcA~!>gHo|s(f8#`TSk%{16ztBg(LuZRBhLCTGj}mDjQy3_qIfHkKwuI2 zJ4a_-Y>=)b-LCz5Tz^P&(WNvfz1(i|N?GXgj^3ShE8qs#HGq9MmG4D_jKaw_8d)GX ze*dZ>5RlAZE%xVQf&YAx|GfXGHQTOHU1-p4|9tItxCc z_~abNQ{#Ufwf{B+0L;LO;Xe=FpW-|nxc){e0uKfLv50>gzdl8II;{GQQib|QlwZ2! zDazA%!*3K0j6b6Mnm#;5dD_7KjY13VXM=yy({}b#fTtVp-vHHwzW{!3!=IWymHWR< zJxKmCeJcK+B0SZ$zY#u@KJ~#LmF`o(r&94Z;1{z01K@Ar_!RJ|@cIonM*a)%iR}8L z40{UtR7d>=)d0^Szz^;J0{Tl;Jq7(&we%Yj0O$pS{+o7sYW}ZP@9*XU)PFPoM-5b# Vg8{SlXMIEmXoF|AICMWh{SR^5Y}5b% literal 0 HcmV?d00001