Repository: wangguanquan/eec
Pull request #91 (2020-01-11 09:40:41)

Fix#51


Patch:
diff --git a/src/main/java/org/ttzero/excel/entity/Sheet.java b/src/main/java/org/ttzero/excel/entity/Sheet.java
index f47adbe9..5ca66eb9 100644
--- a/src/main/java/org/ttzero/excel/entity/Sheet.java
+++ b/src/main/java/org/ttzero/excel/entity/Sheet.java
@@ -1291,8 +1291,8 @@ public abstract class Sheet implements Cloneable, Storageable {
     }
 
     /**
-     * Int conversion to column string number
-     * The max column on sheet is 16_384
+     * Int conversion to column string number.
+     * The max column on sheet is 16_384 after office 2007 and 256 in office 2003
      * <blockquote><pre>
      * int    | column number
      * -------|---------
diff --git a/src/main/java/org/ttzero/excel/entity/e7/XMLWorksheetWriter.java b/src/main/java/org/ttzero/excel/entity/e7/XMLWorksheetWriter.java
index ce6479a3..284bf0e0 100644
--- a/src/main/java/org/ttzero/excel/entity/e7/XMLWorksheetWriter.java
+++ b/src/main/java/org/ttzero/excel/entity/e7/XMLWorksheetWriter.java
@@ -385,16 +385,16 @@ public class XMLWorksheetWriter implements IWorksheetWriter {
         bw.writeInt(columns.length);
         bw.write("\">");
 
-        int c = 1, defaultStyle = sheet.defaultHeadStyle();
-        for (Sheet.Column hc : columns) {
-            bw.write("<c r=\"");
-            bw.write(int2Col(c++));
-            bw.writeInt(row);
-            bw.write("\" t=\"s\" s=\"");
-            bw.writeInt(defaultStyle);
-            bw.write("\"><v>");
-            bw.writeInt(sst.get(isNotEmpty(hc.getName()) ? hc.getName() : hc.key));
-            bw.write("</v></c>");
+        int c = 0, defaultStyle = sheet.defaultHeadStyle();
+
+        if (sheet.isAutoSize()) {
+            for (Sheet.Column hc : columns) {
+                writeStringAutoSize(isNotEmpty(hc.getName()) ? hc.getName() : hc.key, row, c++, defaultStyle);
+            }
+        } else {
+            for (Sheet.Column hc : columns) {
+                writeString(isNotEmpty(hc.getName()) ? hc.getName() : hc.key, row, c++, defaultStyle);
+            }
         }
         bw.write("</row>");
     }
diff --git a/src/main/java/org/ttzero/excel/reader/Grid.java b/src/main/java/org/ttzero/excel/reader/Grid.java
new file mode 100644
index 00000000..44fa0a70
--- /dev/null
+++ b/src/main/java/org/ttzero/excel/reader/Grid.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2017-2020, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.ttzero.excel.reader;
+
+/**
+ * @author guanquan.wang at 2020-01-09 16:54
+ */
+interface Grid {
+
+    /**
+     * Mark `1` at the specified coordinates
+     *
+     * @param coordinate the excel coordinate string,
+     *                   it's a coordinate or range coordinates like `A1` or `A1:C4`
+     */
+    default void mark(String coordinate) {
+        mark(coordinate.toCharArray(), 0, coordinate.length());
+    }
+
+    /**
+     * Mark `1` at the specified coordinates
+     *
+     * @param chars the excel coordinate buffer,
+     *              it's a coordinate or range coordinates like `A1` or `A1:C4`
+     * @param from the begin index
+     * @param to the end index
+     */
+    void mark(char[] chars, int from, int to);
+}
+
+abstract class AbstractGrid implements Grid {
+    int r, c; // Start index of Row and Column(One base)
+
+}
+
+class FractureGrid implements Grid {
+
+    @Override
+    public void mark(char[] chars, int from, int to) {
+
+    }
+}
+
+class LongGrid extends AbstractGrid {
+
+    @Override
+    public void mark(char[] chars, int from, int to) {
+
+    }
+}
+
+class IntegerGrid extends AbstractGrid {
+
+    @Override
+    public void mark(char[] chars, int from, int to) {
+
+    }
+}
+
+class ShortGrid extends AbstractGrid {
+
+    @Override
+    public void mark(char[] chars, int from, int to) {
+
+    }
+}
diff --git a/src/main/java/org/ttzero/excel/reader/HeaderRow.java b/src/main/java/org/ttzero/excel/reader/HeaderRow.java
index afa47210..5c6ab96c 100644
--- a/src/main/java/org/ttzero/excel/reader/HeaderRow.java
+++ b/src/main/java/org/ttzero/excel/reader/HeaderRow.java
@@ -19,6 +19,7 @@ package org.ttzero.excel.reader;
 import org.ttzero.excel.annotation.DisplayName;
 import org.ttzero.excel.annotation.ExcelColumn;
 import org.ttzero.excel.annotation.IgnoreImport;
+import org.ttzero.excel.manager.Const;
 import org.ttzero.excel.util.ReflectUtil;
 import org.ttzero.excel.util.StringUtil;
 
@@ -34,6 +35,13 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.StringJoiner;
 
+import static org.ttzero.excel.entity.IWorksheetWriter.isBool;
+import static org.ttzero.excel.entity.IWorksheetWriter.isChar;
+import static org.ttzero.excel.entity.IWorksheetWriter.isDate;
+import static org.ttzero.excel.entity.IWorksheetWriter.isInt;
+import static org.ttzero.excel.entity.IWorksheetWriter.isLocalDate;
+import static org.ttzero.excel.entity.IWorksheetWriter.isLocalDateTime;
+import static org.ttzero.excel.entity.IWorksheetWriter.isLocalTime;
 import static org.ttzero.excel.util.ReflectUtil.listDeclaredFields;
 import static org.ttzero.excel.util.ReflectUtil.listWriteMethods;
 import static org.ttzero.excel.util.ReflectUtil.mapping;
@@ -296,12 +304,53 @@ class HeaderRow extends Row {
     @Override
     public String toString() {
         StringJoiner joiner = new StringJoiner(" | ");
+        StringBuilder buf = new StringBuilder();
         int i = 0;
-        for (; names[i++] == null; ) ;
-        for (; i < names.length; i++) {
+        for (; names[i] == null; i++) ;
+        char[] chars = new char[10];
+        Arrays.fill(chars, 0, chars.length, '-');
+        for (int j = i; i < names.length; i++) {
             joiner.add(names[i]);
+            int n = simpleTestLength(names[i]) + (j == i || i == names.length - 1 ? 1 : 2);
+            if (n > chars.length) {
+                chars = new char[n];
+                Arrays.fill(chars, 0, n, '-');
+            } else {
+                Arrays.fill(chars, 0, n, '-');
+            }
+
+            if (fieldClazz != null) {
+                Class<?> c = fieldClazz[i];
+
+                // Align Center
+                if (isDate(c) || isLocalDate(c) || isLocalDateTime(c) || isLocalTime(c) || isChar(c) || isBool(c)) {
+                    chars[0] = chars[n - 1] = ':';
+                }
+                // Align Right
+                else if (isInt(c)) {
+                    chars[n - 1] = ':';
+                }
+                // Align Left
+//            else;
+            }
+            buf.append(chars, 0, n).append('|');
+
+        }
+
+        buf.insert(0, joiner.toString() + Const.lineSeparator);
+
+        return buf.toString();
+    }
+
+    int simpleTestLength(String name) {
+        if (name == null) return 4;
+        char[] chars = name.toCharArray();
+        double d = 0.0;
+        for (char c : chars) {
+            if (c < 0x80) d++;
+            else d += 1.75;
         }
-        return joiner.toString();
+        return (int) d;
     }
 
     void put(Row row, Object t) throws IllegalAccessException, InvocationTargetException {
diff --git a/src/main/java/org/ttzero/excel/reader/PreCalc.java b/src/main/java/org/ttzero/excel/reader/PreCalc.java
index 2508c7a9..7c51bc6c 100644
--- a/src/main/java/org/ttzero/excel/reader/PreCalc.java
+++ b/src/main/java/org/ttzero/excel/reader/PreCalc.java
@@ -54,7 +54,6 @@ class PreCalc {
         int len = cb.length;
         int f = 0, m = f, n;
         for (; f < len; ) {
-            // FIXME maybe include quote and double quotes
             for (; f < len && (cb[f] < 'A' || cb[f] > 'Z') && cb[f] != '"'; f++) ;
             // EOF
             if (f >= len) break;
diff --git a/src/main/java/org/ttzero/excel/reader/Sheet.java b/src/main/java/org/ttzero/excel/reader/Sheet.java
index 0e9c4fe5..cf15da1d 100644
--- a/src/main/java/org/ttzero/excel/reader/Sheet.java
+++ b/src/main/java/org/ttzero/excel/reader/Sheet.java
@@ -174,7 +174,7 @@ public interface Sheet extends AutoCloseable {
      * @param col column mark
      * @return int value
      */
-    default int col2Int(String col) {
+    static int col2Int(String col) {
         if (StringUtil.isEmpty(col)) return 1;
         char[] values = col.toCharArray();
         int n = 0;
diff --git a/src/main/java/org/ttzero/excel/reader/XMLRow.java b/src/main/java/org/ttzero/excel/reader/XMLRow.java
index b1a9da4a..77188294 100644
--- a/src/main/java/org/ttzero/excel/reader/XMLRow.java
+++ b/src/main/java/org/ttzero/excel/reader/XMLRow.java
@@ -323,11 +323,12 @@ class XMLRow extends Row {
     private boolean isDouble(int a, int b) {
         if (a == b) return false;
         if (cb[a] == '-') a++;
-        for (char i = 0; a < b; ) {
+        for (char i = 0, e = 0; a < b; ) {
             char c = cb[a++];
-            if (i > 1) return false;
-            if (c >= '0' && c <= '9') continue;
+            if (i > 1 || e > 1) return false;
             if (c == '.') i++;
+            else if (c == 'e' || c == 'E') e++;
+            else if (c < '0' || c > '9') return false;
         }
         return true;
     }
diff --git a/src/main/java/org/ttzero/excel/reader/XMLSheet.java b/src/main/java/org/ttzero/excel/reader/XMLSheet.java
index 40938be0..01a609cc 100644
--- a/src/main/java/org/ttzero/excel/reader/XMLSheet.java
+++ b/src/main/java/org/ttzero/excel/reader/XMLSheet.java
@@ -18,9 +18,7 @@ package org.ttzero.excel.reader;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.ttzero.excel.entity.TooManyColumnsException;
 import org.ttzero.excel.entity.style.Styles;
-import org.ttzero.excel.manager.Const;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -31,8 +29,10 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -61,20 +61,11 @@ class XMLSheet implements Sheet {
     private int startRow = -1; // row index of data
     private HeaderRow header;
     private boolean hidden; // state hidden
-    /*
-     The range address of the used area in the current sheet
-     Offset | Size | Contents
-     -------|------|----------
-        0   |   4  | Index to first used row
-        4   |   4  | Index to last used row, increased by 1
-        8   |   2  | Index to first used column
-       10   |   2  | Index to last used column, increased by 1
-     */
-    private long dimension;
-    private int rc; // Range of column
     private long[] calc; // Array of formula
     private boolean hasCalc;
     private boolean parseFormula;
+    // Range address of the used area in the current sheet
+    private Dimension dimension;
 
     /**
      * Setting the worksheet name
@@ -156,8 +147,7 @@ class XMLSheet implements Sheet {
     @Deprecated
     @Override
     public int getSize() {
-        if (dimension == 0) return -1;
-        return (int) ((dimension >>> 32) - (dimension & 0x7FFFFFFF));
+        return dimension != null ? dimension.lastRow - dimension.firstRow + 1 : -1;
     }
 
     /**
@@ -170,14 +160,7 @@ class XMLSheet implements Sheet {
      */
     @Override
     public Dimension getDimension() {
-        // Read last rows info
-        if (dimension == 0 || ((dimension >> 32) & 0x7FFFFFFF) == 0) {
-            parseDimension();
-        }
-        return new Dimension((int)(dimension & 0x7FFFFFFF)
-            , (short) (rc & 0x7FFF)
-            , (int) (dimension >>> 32)
-            , (short) (rc >> 16));
+        return dimension;
     }
 
     /**
@@ -276,30 +259,17 @@ class XMLSheet implements Sheet {
                 int index = line.indexOf(size), end = index > 0 ? line.indexOf('"', index += size.length()) : -1;
                 if (end > 0) {
                     String l_ = line.substring(index, end);
+                    logger.debug("Dimension-Range: {}", l_);
                     Pattern pat = Pattern.compile("([A-Z]+)(\\d+):([A-Z]+)(\\d+)");
                     Matcher mat = pat.matcher(l_);
                     if (mat.matches()) {
-                        int from = Integer.parseInt(mat.group(2)), to = Integer.parseInt(mat.group(4));
-                        dimension = ((long) to << 32) | from;
-                        this.startRow = from;
-//                        this.size = to - from + 1;
-                        int c1 = col2Int(mat.group(1)), c2 = col2Int(mat.group(3)), columnSize = c2 - c1 + 1;
-                        if (columnSize > Const.Limit.MAX_COLUMNS_ON_SHEET) {
-                            throw new TooManyColumnsException(columnSize, Const.Limit.MAX_COLUMNS_ON_SHEET);
-                        }
-                        rc = (c2 << 16) | c1 & 0x7FFF;
-                        logger.debug("Dimension-Range: {\"first-row\": {}, \"last-row\": {}" +
-                            ", \"first-column\": {}, \"last-column\": {}}", from, to, c1, c2);
-//                        OPTIONS = columnSize > 128; // size more than DX
+                        dimension = Dimension.from(l_);
+                        this.startRow = dimension.firstRow;
                     } else {
                         pat = Pattern.compile("([A-Z]+)(\\d+)");
                         mat = pat.matcher(l_);
                         if (mat.matches()) {
                             this.startRow = Integer.parseInt(mat.group(2));
-                            dimension = this.startRow;
-                            rc = col2Int(mat.group(1));
-                            logger.debug("Dimension-Range: {\"first-row\": {}" +
-                                ", \"first-column\": {}}", this.startRow, rc);
                         }
                     }
                     nChar += end;
@@ -327,6 +297,10 @@ class XMLSheet implements Sheet {
         }
 
         mark = nChar;
+        if (!eof) {
+            parseDimension();
+        }
+
         return this;
     }
 
@@ -530,11 +504,15 @@ class XMLSheet implements Sheet {
             int block = (int) Math.min(1 << 11, fileSize);
             ByteBuffer buffer = ByteBuffer.allocate(block);
             byte[] left = null;
-            int left_size = 0;
-            for (; ;) {
+            int left_size = 0, i = 0;
+
+            CharBuffer charBuffer = null;
+            boolean eof = false;
+            for (; !eof;) {
                 channel.position(fileSize - block + left_size);
                 channel.read(buffer);
                 fileSize -= buffer.limit();
+                eof = buffer.limit() < block;
                 if (left_size > 0) {
                     buffer.limit(block);
                     buffer.put(left, 0, left_size);
@@ -542,17 +520,19 @@ class XMLSheet implements Sheet {
                 }
                 buffer.flip();
 
-                CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer);
-                int limit = charBuffer.limit(), i = limit - 1;
+                charBuffer = StandardCharsets.UTF_8.decode(buffer);
+                int limit = charBuffer.limit();
+                i = limit - 1;
+
                 // <row r="
                 for (; i >= 7 && (charBuffer.get(i) != '"' || charBuffer.get(i - 1) != '='
                     || charBuffer.get(i - 2) != 'r' || charBuffer.get(i - 3) > ' '
                     || charBuffer.get(i - 4) != 'w' || charBuffer.get(i - 5) != 'o'
-                    || charBuffer.get(i - 6) != 'r' || charBuffer.get(i - 7) != '<'); i--);
+                    || charBuffer.get(i - 6) != 'r' || charBuffer.get(i - 7) != '<'); i--) ;
 
                 // Not Found
                 if (i < 7) {
-                    for (; i < limit && charBuffer.get(i) != '>'; i++);
+                    for (; i < limit && charBuffer.get(i) != '>'; i++) ;
                     i++;
                     if (i < limit - 1) {
                         charBuffer.position(i);
@@ -568,39 +548,99 @@ class XMLSheet implements Sheet {
                 }
                 // Found the last row
                 else {
-                    int[] info = innerParse(charBuffer, ++i);
-                    rc = (info[2] << 16) | (info[1] & 0x7FFF);
-                    dimension = ((long) info[0]) << 32;
                     break;
                 }
                 buffer.position(0);
                 buffer.limit(buffer.capacity() - left_size);
             }
-            // Skip if the first row is known
-            if ((dimension & 0x7FFFFFFF) > 0L) return;
-            if (mark > 0) {
-                channel.position(mark);
-                buffer.clear();
-                channel.read(buffer);
-                buffer.flip();
 
-                CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer);
-                int i = 0;
-                for (; charBuffer.get(i) != '<' || charBuffer.get(i + 1) != 'r'
-                    || charBuffer.get(i + 2) != 'o' || charBuffer.get(i + 3) != 'w'
-                    || charBuffer.get(i + 4) > ' ' || charBuffer.get(i + 5) != 'r'
-                    || charBuffer.get(i + 6) != '=' || charBuffer.get(i + 7) != '"'; i++);
-                i += 8;
-
-                int[] info = innerParse(charBuffer, i);
-                dimension |= info[0];
-                if (info[1] > (rc & 0x7FFF)) {
-                    rc |= info[1] & 0x7FFF;
+            if (eof) return;
+
+            // Dimension
+            if (dimension == null) {
+                long rr = 0L;
+                int rc;
+
+                int[] info = innerParse(charBuffer, ++i);
+                rc = (info[2] << 16) | (info[1] & 0x7FFF);
+                rr |= ((long) info[0]) << 32;
+
+                // Skip if the first row is known
+                if (mark > 0) {
+                    channel.position(mark);
+                    buffer.clear();
+                    channel.read(buffer);
+                    buffer.flip();
+
+                    charBuffer = StandardCharsets.UTF_8.decode(buffer);
+                    i = 0;
+                    for (; charBuffer.get(i) != '<' || charBuffer.get(i + 1) != 'r'
+                        || charBuffer.get(i + 2) != 'o' || charBuffer.get(i + 3) != 'w'
+                        || charBuffer.get(i + 4) > ' ' || charBuffer.get(i + 5) != 'r'
+                        || charBuffer.get(i + 6) != '=' || charBuffer.get(i + 7) != '"'; i++) ;
+                    i += 8;
+
+                    info = innerParse(charBuffer, i);
+                    rr |= info[0];
+                    if (info[1] > (rc & 0x7FFF)) {
+                        rc |= info[1] & 0x7FFF;
+                    }
+                    if (info[2] < (rc >>> 16)) {
+                        rc |= info[2] << 16;
+                    }
+                } else rr |= 1;
+
+                dimension = new Dimension((int) rr, (short) rc, (int) (rr >>> 32), (short) (rc >>> 16));
+            }
+
+            // Find mergeCells tag
+            int limit = charBuffer.limit();
+            List<Dimension> mergeCells = null;
+            for (; ;) {
+                for (; i < limit - 11 && (charBuffer.get(i) != '<' || charBuffer.get(i + 1) != 'm'
+                    || charBuffer.get(i + 2) != 'e' || charBuffer.get(i + 3) != 'r'
+                    || charBuffer.get(i + 4) != 'g' || charBuffer.get(i + 5) != 'e'
+                    || charBuffer.get(i + 6) != 'C' || charBuffer.get(i + 7) != 'e'
+                    || charBuffer.get(i + 8) != 'l' || charBuffer.get(i + 9) != 'l'
+                    || charBuffer.get(i + 10) > ' '); i++) ;
+
+                if (i >= limit - 11) {
+                    if (eof) break;
+                    for (; i >= 0 && charBuffer.get(i) != '<'; i--) ;
+                    if (i > 0) channel.position(channel.position() + i);
+
+                    buffer.clear();
+                    channel.read(buffer);
+                    buffer.flip();
+                    if (buffer.limit() <= 0) break;
+
+                    charBuffer = StandardCharsets.UTF_8.decode(buffer);
+                    limit = charBuffer.limit();
+                    i = 0;
+                    eof = limit < block;
+                } else {
+                    i += 11;
+                    for (; i < limit - 5 && (charBuffer.get(i) != 'r' || charBuffer.get(i + 1) != 'e'
+                        || charBuffer.get(i + 2) != 'f' || charBuffer.get(i + 3) != '='
+                        || charBuffer.get(i + 4) != '"'); i++) ;
+                    if (i > limit - 5) continue;
+                    int f = i += 5;
+                    for (; charBuffer.get(i) != '"'; i++) ;
+                    if (i > limit) continue;
+                    char[] chars = new char[i - f];
+                    charBuffer.position(f);
+                    charBuffer.get(chars, 0, i - f);
+                    if (mergeCells == null) mergeCells = new ArrayList<>();
+                    mergeCells.add(Dimension.from(new String(chars)));
+                    charBuffer.position(++i);
                 }
-                if (info[2] < (rc >>> 16)) {
-                    rc |= info[2] << 16;
+            }
+            // TODO merge
+            if (mergeCells != null) {
+                for (Dimension dimension : mergeCells) {
+                    logger.info(dimension);
                 }
-            } else dimension |= 1;
+            }
         } catch (IOException e) {
             // Ignore error
             logger.debug("", e);
@@ -616,7 +656,7 @@ class XMLSheet implements Sheet {
         for (; charBuffer.get(i) != 's' || charBuffer.get(i + 1) != 'p'
             || charBuffer.get(i + 2) != 'a' || charBuffer.get(i + 3) != 'n'
             || charBuffer.get(i + 4) != 's' || charBuffer.get(i + 5) != '='
-            || charBuffer.get(i + 6) != '"'; i++);
+            || charBuffer.get(i + 6) != '"'; i++) ;
         i += 7;
         int cs = 0, ls = 0;
         for (; charBuffer.get(i) != ':'; i++)
diff --git a/src/main/java/org/ttzero/excel/util/ZipUtil.java b/src/main/java/org/ttzero/excel/util/ZipUtil.java
index d0abe0cf..54e0eb35 100644
--- a/src/main/java/org/ttzero/excel/util/ZipUtil.java
+++ b/src/main/java/org/ttzero/excel/util/ZipUtil.java
@@ -113,7 +113,9 @@ public class ZipUtil {
             if (j < array[index]) {
                 String name;
                 if (paths.get(j).equals(srcPath[index])) {
-                    name = paths.get(j).toString().substring(paths.get(j).getParent().toString().length() + 1);
+                    name = paths.get(j).getNameCount() > 1
+                        ? paths.get(j).toString().substring(paths.get(j).getParent().toString().length() + 1)
+                        : paths.get(j).toString();
                 } else {
                     name = paths.get(j).toString().substring(len + 1);
                 }


Test patch:
diff --git a/src/test/java/org/ttzero/excel/reader/ExcelReaderTest.java b/src/test/java/org/ttzero/excel/reader/ExcelReaderTest.java
index dd4b63bb..b434e3f6 100644
--- a/src/test/java/org/ttzero/excel/reader/ExcelReaderTest.java
+++ b/src/test/java/org/ttzero/excel/reader/ExcelReaderTest.java
@@ -303,6 +303,31 @@ public class ExcelReaderTest {
         }
     }
 
+    @Test public void testHeaderString() {
+        try (ExcelReader reader = ExcelReader.read(testResourceRoot().resolve("1.xlsx"))) {
+            reader.sheets().flatMap(sheet -> {
+                println("----------------" + sheet.getName() + "----------------");
+                println(sheet.getHeader());
+                return sheet.dataRows();
+            }).forEach(Print::println);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test public void testHeaderString2() {
+        try (ExcelReader reader = ExcelReader.read(testResourceRoot().resolve("1.xlsx"))) {
+            reader.sheets().flatMap(sheet -> {
+                println("----------------" + sheet.getName() + "----------------");
+                sheet.bind(Entry.class);
+                println(sheet.getHeader());
+                return sheet.dataRows();
+            }).forEach(Print::println);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
     @Test public void testVersionFilter() {
         char[] chars = "..0...3...7.SNAPSHOT.".toCharArray();
         int i = 0;
@@ -338,6 +363,17 @@ public class ExcelReaderTest {
         assert "B2:B8".equals(values[5]);
     }
 
+    @Test public void testMerge() {
+        try (ExcelReader reader = ExcelReader.read(testResourceRoot().resolve("merge.xlsx"))) {
+            reader.sheets().flatMap(s -> {
+                println("----------------" + s.getName() + "----------------");
+                println("dimension: " + s.getDimension());
+                return s.rows();
+            }).forEach(Print::println);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
 
 
     public static class Customer {
diff --git a/src/test/java/org/ttzero/excel/reader/GridTest.java b/src/test/java/org/ttzero/excel/reader/GridTest.java
new file mode 100644
index 00000000..dacc28e3
--- /dev/null
+++ b/src/test/java/org/ttzero/excel/reader/GridTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2017-2020, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.ttzero.excel.reader;
+
+import org.junit.Test;
+
+/**
+ * @author guanquan.wang at 2020-01-09 17:19
+ */
+public class GridTest {
+    @Test public void testLongGrid() {
+        LongGrid grid = new LongGrid();
+    }
+}