Repository: wangguanquan/eec
Pull request #125 (2020-04-15 02:02:32)

Feature/e3 support
1. 优化SharedStringTable
2. 支持读取Excel97~2003文件（需要引eec-e3-support依赖）


Patch:
diff --git a/CHANGELOG b/CHANGELOG
index 497d3cdd..01491408 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+Version 0.4.4
+-------------
+1. 优化SharedStringTable
+2. 支持读取Excel97~2003文件
+
 Version 0.4.3 (2020-03-19)
 -------------
 1. 修复读取科学计数转数字类型时抛NumberFormatException异常
diff --git a/pom.xml b/pom.xml
index b90320c6..4b5f51dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>org.ttzero</groupId>
     <artifactId>eec</artifactId>
-    <version>0.4.3</version>
+    <version>0.4.4-SNAPSHOT</version>
     <name>Excel Write/Read Framework</name>
     <packaging>jar</packaging>
     <url>https://www.ttzero.org</url>
@@ -36,6 +36,8 @@
             <artifactId>slf4j-api</artifactId>
             <version>${slf4j.version}</version>
         </dependency>
+
+        <!-- TEST -->
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
diff --git a/src/main/java/org/ttzero/excel/entity/IWorkbookWriter.java b/src/main/java/org/ttzero/excel/entity/IWorkbookWriter.java
index 3860e273..c5699e97 100644
--- a/src/main/java/org/ttzero/excel/entity/IWorkbookWriter.java
+++ b/src/main/java/org/ttzero/excel/entity/IWorkbookWriter.java
@@ -16,12 +16,23 @@
 
 package org.ttzero.excel.entity;
 
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.ttzero.excel.util.FileUtil;
+import org.ttzero.excel.util.StringUtil;
+
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
+import java.util.Properties;
 
 import static org.ttzero.excel.util.FileUtil.exists;
 import static org.ttzero.excel.util.StringUtil.indexOf;
@@ -112,4 +123,55 @@ public interface IWorkbookWriter extends Storable {
         Files.move(src, o);
         return o;
     }
+
+    /**
+     * @return general pom properties
+     */
+    static Properties pom() {
+        Properties pom = new Properties();
+        try {
+            InputStream is = IWorkbookWriter.class.getClassLoader()
+                .getResourceAsStream("META-INF/maven/org.ttzero/eec/pom.properties");
+            if (is == null) {
+                // Read from target/maven-archiver/pom.properties
+                URL url = IWorkbookWriter.class.getClassLoader().getResource(".");
+                if (url != null) {
+                    Path targetPath = (FileUtil.isWindows()
+                        ? Paths.get(url.getFile().substring(1))
+                        : Paths.get(url.getFile())).getParent();
+                    // On Mac or Linux
+                    Path pomPath = targetPath.resolve("maven-archiver/pom.properties");
+                    if (exists(pomPath)) {
+                        is = Files.newInputStream(pomPath);
+                        // On windows
+                    } else {
+                        pomPath = targetPath.getParent().resolve("pom.xml");
+                        // load workbook.xml
+                        SAXReader reader = new SAXReader();
+                        Document document;
+                        try {
+                            document = reader.read(Files.newInputStream(pomPath));
+                            Element pomRoot = document.getRootElement();
+                            pom.setProperty("groupId", pomRoot.elementText("groupId"));
+                            pom.setProperty("artifactId", pomRoot.elementText("artifactId"));
+                            pom.setProperty("version", pomRoot.elementText("version"));
+                        } catch (DocumentException | IOException e) {
+                            // Nothing
+                        }
+                    }
+                }
+            }
+            if (is != null) {
+                pom.load(is);
+            }
+        } catch (IOException e) {
+            // Nothing
+        }
+        if (StringUtil.isEmpty(pom.getProperty("version"))) {
+            pom.setProperty("groupId", "org.ttzero");
+            pom.setProperty("artifactId", "eec");
+            pom.setProperty("version", "1.0.0");
+        }
+        return pom;
+    }
 }
diff --git a/src/main/java/org/ttzero/excel/entity/SharedStringTable.java b/src/main/java/org/ttzero/excel/entity/SharedStringTable.java
index 2fceca67..375f84d8 100644
--- a/src/main/java/org/ttzero/excel/entity/SharedStringTable.java
+++ b/src/main/java/org/ttzero/excel/entity/SharedStringTable.java
@@ -18,6 +18,7 @@ package org.ttzero.excel.entity;
 
 import org.ttzero.excel.util.FileUtil;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.ByteBuffer;
@@ -35,7 +36,7 @@ import static org.ttzero.excel.util.FileUtil.exists;
 /**
  * @author guanquan.wang at 2019-05-10 20:04
  */
-public class SharedStringTable implements AutoCloseable, Iterable<String> {
+public class SharedStringTable implements Closeable, Iterable<String> {
     /**
      * The temp path
      */
diff --git a/src/main/java/org/ttzero/excel/entity/SharedStrings.java b/src/main/java/org/ttzero/excel/entity/SharedStrings.java
index 318f02bd..bce617ac 100644
--- a/src/main/java/org/ttzero/excel/entity/SharedStrings.java
+++ b/src/main/java/org/ttzero/excel/entity/SharedStrings.java
@@ -28,6 +28,7 @@ import org.ttzero.excel.util.ExtBufferedWriter;
 import org.ttzero.excel.util.FileUtil;
 import org.ttzero.excel.util.StringUtil;
 
+import java.io.Closeable;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -66,7 +67,7 @@ import static org.ttzero.excel.util.FileUtil.exists;
  * @author guanquan.wang on 2017/10/10.
  */
 @TopNS(prefix = "", value = "sst", uri = Const.SCHEMA_MAIN)
-public class SharedStrings implements Storable, AutoCloseable {
+public class SharedStrings implements Storable, Closeable {
     private final Logger LOGGER = LoggerFactory.getLogger(getClass());
 
     /**
diff --git a/src/main/java/org/ttzero/excel/entity/e7/XMLWorkbookWriter.java b/src/main/java/org/ttzero/excel/entity/e7/XMLWorkbookWriter.java
index ca44e2b3..0643036c 100644
--- a/src/main/java/org/ttzero/excel/entity/e7/XMLWorkbookWriter.java
+++ b/src/main/java/org/ttzero/excel/entity/e7/XMLWorkbookWriter.java
@@ -17,12 +17,10 @@
 package org.ttzero.excel.entity.e7;
 
 import org.dom4j.Document;
-import org.dom4j.DocumentException;
 import org.dom4j.DocumentFactory;
 import org.dom4j.Element;
 import org.dom4j.Namespace;
 import org.dom4j.QName;
-import org.dom4j.io.SAXReader;
 import org.ttzero.excel.annotation.TopNS;
 import org.ttzero.excel.entity.ExcelWriteException;
 import org.ttzero.excel.entity.IWorkbookWriter;
@@ -48,10 +46,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.reflect.InvocationTargetException;
-import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -223,53 +219,10 @@ public class XMLWorkbookWriter implements IWorkbookWriter {
         }
 
         // Read app and version from pom
-        try {
-            InputStream is = getClass().getClassLoader()
-                .getResourceAsStream("META-INF/maven/org.ttzero/eec/pom.properties");
-            Properties pom = new Properties();
-            if (is == null) {
-                // Read from target/maven-archiver/pom.properties
-                URL url = getClass().getClassLoader().getResource(".");
-                if (url != null) {
-                    Path targetPath = (FileUtil.isWindows()
-                        ? Paths.get(url.getFile().substring(1))
-                        : Paths.get(url.getFile())).getParent();
-                    // On Mac or Linux
-                    Path pomPath = targetPath.resolve("maven-archiver/pom.properties");
-                    if (exists(pomPath)) {
-                        is = Files.newInputStream(pomPath);
-                        // On windows
-                    } else {
-                        pomPath = targetPath.getParent().resolve("pom.xml");
-                        // load workbook.xml
-                        SAXReader reader = new SAXReader();
-                        Document document;
-                        try {
-                            document = reader.read(Files.newInputStream(pomPath));
-                            Element pomRoot = document.getRootElement();
-                            String application = pomRoot.elementText("groupId") + "." + pomRoot.elementText("artifactId");
-                            app.setApplication(application);
-                            String appVersion = pomRoot.elementText("version");
-                            app.setAppVersion(appVersion);
-                        } catch (DocumentException | IOException e) {
-                            // Nothing
-                        }
-                    }
-                }
-            }
-            if (is != null) {
-                pom.load(is);
-                app.setApplication(pom.getProperty("groupId") + '.' + pom.getProperty("artifactId"));
-                app.setAppVersion(pom.getProperty("version"));
-                // Can't read pom.xml if running as dev on window
-            }
-        } catch (IOException e) {
-            // Nothing
-        }
-        if (StringUtil.isEmpty(app.getAppVersion())) {
-            app.setApplication("org.ttzero.eec");
-            app.setAppVersion("1.0.0");
-        }
+        Properties pom = IWorkbookWriter.pom();
+
+        app.setApplication(pom.getProperty("groupId") + "." + pom.getProperty("artifactId"));
+        app.setAppVersion(pom.getProperty("version"));
 
         int size = workbook.getSize();
 
diff --git a/src/main/java/org/ttzero/excel/entity/style/NumFmt.java b/src/main/java/org/ttzero/excel/entity/style/NumFmt.java
index 20682a47..d06b2e8a 100644
--- a/src/main/java/org/ttzero/excel/entity/style/NumFmt.java
+++ b/src/main/java/org/ttzero/excel/entity/style/NumFmt.java
@@ -38,10 +38,11 @@ import org.ttzero.excel.util.StringUtil;
  * If you specify only one code section, it is used for all numbers.
  * If you want to skip a code section and include a code section that follows it,
  * you must include the ending semicolon for the section that you skip.
- * <p>
- * <a href="https://support.office.com/en-us/article/create-or-delete-a-custom-number-format-78f2a361-936b-4c03-8772-09fab54be7f4">https://support.office.com/en-us/article/create-or-delete-a-custom-number-format-78f2a361-936b-4c03-8772-09fab54be7f4</a>
- * <a href="https://support.office.com/en-us/article/Number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68?ui=en-US&rs=en-US&ad=US">https://support.office.com/en-us/article/Number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68</a>
- * <a href="https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/ee857658(v=office.14)">https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/ee857658(v=office.14)</a>
+ * <ul>
+ * <li><a href="https://support.office.com/en-us/article/create-or-delete-a-custom-number-format-78f2a361-936b-4c03-8772-09fab54be7f4">Create a custom number format</a></li>
+ * <li><a href="https://support.office.com/en-us/article/Number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68?ui=en-US&rs=en-US&ad=US">Number format codes</a></li>
+ * <li><a href="https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/ee857658(v=office.14)">NumberingFormat Class</a></li>
+ * </ul>
  *
  * @author guanquan.wang at 2018-02-06 08:51
  */
diff --git a/src/main/java/org/ttzero/excel/entity/style/Styles.java b/src/main/java/org/ttzero/excel/entity/style/Styles.java
index b363f1e9..374d28c1 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Styles.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Styles.java
@@ -49,11 +49,17 @@ import static org.ttzero.excel.util.StringUtil.isNotEmpty;
  * {@link Fill}, {@link Border}, {@link Verticals} and
  * {@link Horizontals}, each Worksheet introduced by subscript.
  * <p>
- * EEC uses an Integer value to represent a complete pattern,
- * 0-8 bits are stored in NumFmt, 8-14 bits are stored in Font,
- * 14-20 bits are stored in Fill, 20-26 bits are stored in Border,
- * 26-29 bits are stored in Vertical and 29-32 bits are stored in
- * Horizontal. The Build-In number format does not write into styles.
+ * EEC uses an Integer value to store Extended Format:
+ * <blockquote><pre>
+ *  Bit  | Contents
+ * ------+---------
+ *  0, 8 | NumFmt
+ *  8, 6 | Font
+ * 14, 6 | Fill
+ * 20, 6 | Border
+ * 26, 3 | Vertical
+ * 29, 3 | Horizontal</pre></blockquote>
+ * The Build-In number format does not write into styles.
  *
  * @author guanquan.wang on 2017/10/13.
  */
@@ -103,12 +109,12 @@ public class Styles implements Storable {
         return map.size();
     }
 
-    static final int INDEX_NUMBER_FORMAT = 24;
-    static final int INDEX_FONT = 18;
-    static final int INDEX_FILL = 12;
-    static final int INDEX_BORDER = 6;
-    static final int INDEX_VERTICAL = 3;
-    static final int INDEX_HORIZONTAL = 0;
+    public static final int INDEX_NUMBER_FORMAT = 24;
+    public static final int INDEX_FONT = 18;
+    public static final int INDEX_FILL = 12;
+    public static final int INDEX_BORDER = 6;
+    public static final int INDEX_VERTICAL = 3;
+    public static final int INDEX_HORIZONTAL = 0;
 
     /**
      * Create a general style
@@ -192,6 +198,15 @@ public class Styles implements Storable {
         return self;
     }
 
+    /**
+     * Create a general style
+     *
+     * @return Styles
+     */
+    public static Styles forReader() {
+        return new Styles();
+    }
+
     /**
      * Load the style file from disk
      *
@@ -604,18 +619,16 @@ public class Styles implements Storable {
         if (style == null) return false;
         int nf = style >> INDEX_NUMBER_FORMAT & 0xFF;
 
-        boolean isDate = false;
+        // No number format
+        if (nf == 0) return false;
+
+        boolean isDate;
         NumFmt numFmt;
-        // All indexes from 0 to 163 are reserved for built-in formats.
-        // The first user-defined format starts at 164.
-        if (nf < 164) {
-            isDate = (nf >= 14 && nf <= 22
-                || nf >= 27 && nf <= 36
-                || nf >= 45 && nf <= 47
-                || nf >= 50 && nf <= 58
-                || nf == 81);
-            // Test by numFmt code
-        } else if ((numFmt = findFmtById(nf)) != null && (isNotEmpty(numFmt.getCode()))) {
+
+        // Test by numFmt code
+        if (!(isDate = isBuildInDateFormat(nf))
+            && (numFmt = findFmtById(nf)) != null
+            && (isNotEmpty(numFmt.getCode()))) {
             isDate = testCodeIsDate(numFmt.getCode());
         }
 
@@ -628,6 +641,16 @@ public class Styles implements Storable {
         return isDate;
     }
 
+    // All indexes from 0 to 163 are reserved for built-in formats.
+    // The first user-defined format starts at 164.
+    private static boolean isBuildInDateFormat(int nf) {
+        return nf < 164 && (nf >= 14 && nf <= 22
+            || nf >= 27 && nf <= 36
+            || nf >= 45 && nf <= 47
+            || nf >= 50 && nf <= 58
+            || nf == 81);
+    }
+
     public static boolean testCodeIsDate(String code) {
         char[] chars = code.toCharArray();
 
@@ -794,4 +817,13 @@ public class Styles implements Storable {
         return dateFmtCache != null && dateFmtCache.contains(styleIndex);
     }
 
+    /**
+     * Append a date format back into cache
+     *
+     * @param xf the XFRecord id
+     */
+    public void addDateFmtCache(int xf) {
+        if (dateFmtCache == null) dateFmtCache = new HashSet<>();
+        dateFmtCache.add(xf);
+    }
 }
diff --git a/src/main/java/org/ttzero/excel/reader/AppInfo.java b/src/main/java/org/ttzero/excel/reader/AppInfo.java
index c8bc0ff4..81de8ede 100644
--- a/src/main/java/org/ttzero/excel/reader/AppInfo.java
+++ b/src/main/java/org/ttzero/excel/reader/AppInfo.java
@@ -19,9 +19,11 @@ package org.ttzero.excel.reader;
 import org.ttzero.excel.manager.docProps.App;
 import org.ttzero.excel.manager.docProps.Core;
 import org.ttzero.excel.util.DateUtil;
-import org.ttzero.excel.util.StringUtil;
+import org.ttzero.excel.util.ReflectUtil;
 
+import java.lang.reflect.Method;
 import java.util.Date;
+import java.util.StringJoiner;
 
 /**
  * The file basic information
@@ -165,20 +167,22 @@ public class AppInfo {
 
     @Override
     public String toString() {
-        return "Application: " + getApplication()
-            + System.lineSeparator() + "Company: " + getCompany()
-            + System.lineSeparator() + "AppVersion: " + getAppVersion()
-            + System.lineSeparator() + "Title: " + getTitle()
-            + System.lineSeparator() + "Subject: " + getSubject()
-            + System.lineSeparator() + "Creator: " + getCreator()
-            + System.lineSeparator() + "Description: " + getDescription()
-            + System.lineSeparator() + "Keywords: " + getKeywords()
-            + System.lineSeparator() + "LastModifiedBy: " + getLastModifiedBy()
-            + System.lineSeparator() + "Version: " + getVersion()
-            + System.lineSeparator() + "Revision: " + getRevision()
-            + System.lineSeparator() + "Category: " + getCategory()
-            + System.lineSeparator() + "Created: " + (getCreated() != null ? DateUtil.toString(getCreated()) : StringUtil.EMPTY)
-            + System.lineSeparator() + "Modified: " + (getModified() != null ? DateUtil.toString(getModified()) : StringUtil.EMPTY)
-            ;
+        StringJoiner joiner = new StringJoiner(System.lineSeparator());
+        try {
+            Method[] methods = ReflectUtil.listReadMethods(getClass(),
+                method -> method.getName().startsWith("get")
+                    || method.getReturnType() == boolean.class && method.getName().startsWith("is"));
+            for (Method method : methods) {
+                Object o = method.invoke(this);
+                if (o != null) {
+                    joiner.add(method.getReturnType() == boolean.class ? method.getName().substring(2)
+                        : method.getName().substring(3) + ": "
+                        + (o instanceof Date ? DateUtil.toString((Date)o) : o.toString()));
+                }
+            }
+        } catch (Exception e) {
+            // Ignore
+        }
+        return joiner.toString();
     }
 }
diff --git a/src/main/java/org/ttzero/excel/reader/Dimension.java b/src/main/java/org/ttzero/excel/reader/Dimension.java
index 8934a308..c89a4f81 100644
--- a/src/main/java/org/ttzero/excel/reader/Dimension.java
+++ b/src/main/java/org/ttzero/excel/reader/Dimension.java
@@ -27,13 +27,13 @@ import static org.ttzero.excel.reader.ExcelReader.cellRangeToLong;
  * @author guanquan.wang at 2019-12-20 10:07
  */
 public class Dimension {
-    // Index to first used row
+    // Index to first used row (one base)
     public final int firstRow;
-    // Index to last used row, increased by 1
+    // Index to last used row (one base)
     public final int lastRow;
-    // Index to first used column
+    // Index to first used column (one base)
     public final short firstColumn;
-    // Index to last used column, increased by 1
+    // Index to last used column (one base)
     public final short lastColumn;
 
     public Dimension(int firstRow, short firstColumn, int lastRow, short lastColumn) {
@@ -100,7 +100,8 @@ public class Dimension {
     @Override
     public String toString() {
         return new String(int2Col(firstColumn)) + this.firstRow
-            + ":" + new String(int2Col(lastColumn)) + this.lastRow;
+            + (lastRow > 0 ? ":" + new String(int2Col(lastColumn)) + this.lastRow
+            : "");
     }
 
     /**
diff --git a/src/main/java/org/ttzero/excel/reader/ExcelReader.java b/src/main/java/org/ttzero/excel/reader/ExcelReader.java
index 08e2a8e4..70de8662 100644
--- a/src/main/java/org/ttzero/excel/reader/ExcelReader.java
+++ b/src/main/java/org/ttzero/excel/reader/ExcelReader.java
@@ -26,6 +26,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.ttzero.excel.annotation.NS;
 import org.ttzero.excel.annotation.TopNS;
+import org.ttzero.excel.entity.IWorkbookWriter;
 import org.ttzero.excel.entity.Relationship;
 import org.ttzero.excel.entity.style.Styles;
 import org.ttzero.excel.manager.Const;
@@ -37,11 +38,14 @@ import org.ttzero.excel.util.FileUtil;
 import org.ttzero.excel.util.StringUtil;
 import org.ttzero.excel.util.ZipUtil;
 
+import java.io.Closeable;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.text.ParseException;
@@ -52,6 +56,7 @@ import java.util.Comparator;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Properties;
 import java.util.Spliterator;
 import java.util.Spliterators;
 import java.util.stream.Stream;
@@ -59,11 +64,12 @@ import java.util.stream.StreamSupport;
 
 import static org.ttzero.excel.reader.SharedStrings.toInt;
 import static org.ttzero.excel.util.FileUtil.exists;
+import static org.ttzero.excel.util.StringUtil.isEmpty;
 import static org.ttzero.excel.util.StringUtil.isNotEmpty;
 
 /**
  * Excel Reader tools
- *
+ * <p>
  * A streaming operation chain, using cursor control, the cursor
  * will only move forward, so you cannot repeatedly operate the
  * same Sheet stream. If you need to read the data of a worksheet
@@ -76,11 +82,11 @@ import static org.ttzero.excel.util.StringUtil.isNotEmpty;
  * <blockquote><pre>
  * try (ExcelReader reader = ExcelReader.read(path)) {
  *     reader.sheets().flatMap(Sheet::rows).forEach(System.out::println);
- * } catch (IOException e) {}</pre></blockquote>
+ * } catch (IOException e) { }</pre></blockquote>
  *
  * @author guanquan.wang on 2018-09-22
  */
-public class ExcelReader implements AutoCloseable {
+public class ExcelReader implements Closeable {
     private static final Logger LOGGER = LoggerFactory.getLogger(ExcelReader.class);
 
     /**
@@ -111,19 +117,21 @@ public class ExcelReader implements AutoCloseable {
 
     /**
      * Reader Option
-     * 0: only parse cell value (default)
-     * 2: parse cell value and calc
-     * 4: copy value on merge cells
+     * <ul>
+     * <li>0: only parse cell value (default)</li>
+     * <li>2: parse cell value and calc</li>
+     * <li>4: copy value on merge cells</li>
+     * </ul>
      *
      * These attributes can be combined via `|`,
      * like: VALUE_ONLY|COPY_ON_MERGED
      */
-    int option;
+    protected int option;
 
     /**
      * A formula flag
      */
-    boolean hasFormula;
+    protected boolean hasFormula;
 
     /**
      * Constructor Excel Reader
@@ -305,7 +313,7 @@ public class ExcelReader implements AutoCloseable {
     }
 
     /**
-     * get all sheets
+     * Returns all sheets
      *
      * @return Sheet Array
      */
@@ -314,7 +322,7 @@ public class ExcelReader implements AutoCloseable {
     }
 
     /**
-     * size of sheets
+     * Size of sheets
      *
      * @return int
      */
@@ -323,7 +331,7 @@ public class ExcelReader implements AutoCloseable {
     }
 
     /**
-     * close stream and delete temp files
+     * Close stream and delete temp files
      *
      * @throws IOException when fail close readers
      */
@@ -338,7 +346,9 @@ public class ExcelReader implements AutoCloseable {
             sst.close();
 
         // Delete temp files
-        FileUtil.rm_rf(self.toFile(), true);
+        if (self != null) {
+            FileUtil.rm_rf(self.toFile(), true);
+        }
         if (temp != null) {
             FileUtil.rm(temp);
         }
@@ -350,7 +360,7 @@ public class ExcelReader implements AutoCloseable {
      * @return the information
      */
     public AppInfo getAppInfo() {
-        return appInfo;
+        return appInfo != null ? appInfo : (appInfo = getGeneralInfo(self));
     }
 
     /**
@@ -535,9 +545,13 @@ public class ExcelReader implements AutoCloseable {
      * @param rmSource   remove the source files
      * @param option the reader option.
      * @return the {@link ExcelReader}
-     * @throws IOException if path not exists or I/O error occur
+     * @throws FileNotFoundException if the path not exists or no permission to read
+     * @throws IOException if I/O error occur
      */
     private static ExcelReader read(Path path, int bufferSize, int cacheSize, boolean rmSource, int option) throws IOException {
+        if (!exists(path)) {
+            throw new FileNotFoundException(path.toString());
+        }
         // Check document type
         ExcelType type = getType(path);
         LOGGER.debug("File type: {}", type);
@@ -549,10 +563,20 @@ public class ExcelReader implements AutoCloseable {
             case XLS:
                 try {
                     Class<?> clazz = Class.forName("org.ttzero.excel.reader.BIFF8Reader");
-                    Constructor<?> constructor = clazz.getDeclaredConstructor(Path.class, int.class, int.class);
-                    er = (ExcelReader) constructor.newInstance(path, bufferSize, cacheSize);
-                } catch (Exception e) {
-                    throw new ExcelReadException("Only support read Office Open XML file.", e);
+                    Constructor<?> constructor = clazz.getDeclaredConstructor(Path.class, int.class, int.class, int.class);
+                    er = (ExcelReader) constructor.newInstance(path, bufferSize, cacheSize, option);
+                } catch (ClassNotFoundException e) {
+                    Properties pom = IWorkbookWriter.pom();
+                    throw new ExcelReadException("Can not load 'org.ttzero.excel.reader.BIFF8Reader'."
+                            + " Please add dependency [" + pom.getProperty("groupId") + ":eec-e3-support"
+                            + ":" + pom.getProperty("version") + "] to parse excel 97~2003.", e);
+                } catch (NoSuchMethodException | InstantiationException e) {
+                    Properties pom = IWorkbookWriter.pom();
+                    throw new ExcelReadException("It may be an exception caused by eec-e3-support version error."
+                            + " Please add dependency [" + pom.getProperty("groupId") + ":eec-e3-support"
+                            + ":" + pom.getProperty("version") + "]", e);
+                } catch (IllegalAccessException | InvocationTargetException e) {
+                    throw new ExcelReadException("Read excel failed.", e);
                 }
                 break;
             default:
@@ -667,6 +691,7 @@ public class ExcelReader implements AutoCloseable {
             Namespace namespace = new Namespace(ns.value(), urls[nsIndex]);
             Class<?> type = f.getType();
             String v = root.elementText(new QName(f.getName(), namespace));
+            if (isEmpty(v)) continue;
             if (type == String.class) {
                 try {
                     f.set(core, v);
diff --git a/src/main/java/org/ttzero/excel/reader/FixSizeLRUCache.java b/src/main/java/org/ttzero/excel/reader/FixSizeLRUCache.java
index f56dad9f..30c5a5b0 100644
--- a/src/main/java/org/ttzero/excel/reader/FixSizeLRUCache.java
+++ b/src/main/java/org/ttzero/excel/reader/FixSizeLRUCache.java
@@ -37,10 +37,11 @@ public class FixSizeLRUCache<K, V> implements Cache<K, V> {
             this.v = v;
         }
 
+        @Override
         public K getKey() {
             return k;
         }
-
+        @Override
         public V getValue() {
             return v;
         }
@@ -91,6 +92,7 @@ public class FixSizeLRUCache<K, V> implements Cache<K, V> {
 
     private FixSizeLRUCache(int limit) {
         this.limit = limit;
+        // Create double limit size
         table = new HashMap<>(Math.round(limit * 1.25f));
     }
 
diff --git a/src/main/java/org/ttzero/excel/reader/Grid.java b/src/main/java/org/ttzero/excel/reader/Grid.java
index 1b84f083..7f049330 100644
--- a/src/main/java/org/ttzero/excel/reader/Grid.java
+++ b/src/main/java/org/ttzero/excel/reader/Grid.java
@@ -16,13 +16,18 @@
 
 package org.ttzero.excel.reader;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.StringJoiner;
 
 import static java.lang.Integer.numberOfTrailingZeros;
+import static org.ttzero.excel.reader.Cell.BLANK;
 import static org.ttzero.excel.reader.Cell.EMPTY_TAG;
 
 /**
@@ -146,7 +151,7 @@ interface Grid {
             if (!test(r, cell.i)) return;
 
             Scanner.Entry e = scanner.get(r, cell.i);
-            if (cell.t == EMPTY_TAG) {
+            if (cell.t == EMPTY_TAG || cell.t == BLANK) {
                 // Copy value from the first merged cell
                 cell.from(e.getCell());
             }
@@ -228,7 +233,7 @@ interface Grid {
             if (!range(r, cell.i)) return;
             Cell c = index.get(((long) r) << 16 | cell.i);
 
-            if (cell.t == EMPTY_TAG) {
+            if (cell.t == EMPTY_TAG || cell.t == BLANK) {
                 // Copy value from the first merged cell
                 cell.from(c);
             }
@@ -277,7 +282,7 @@ interface Grid {
             Scanner.Entry e = scanner.get(r, cell.i);
             if (e == null) return;
 
-            if (cell.t == EMPTY_TAG) {
+            if (cell.t == EMPTY_TAG || cell.t == BLANK) {
                 // Copy value from the first merged cell
                 cell.from(e.getCell());
             }
@@ -378,6 +383,7 @@ interface Grid {
             size++;
         }
 
+        @Override
         public Entry get(int r, int c) {
             Node val = null;
             if (head == null) return null;
@@ -453,19 +459,35 @@ interface Grid {
 }
 
 final class GridFactory {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GridFactory.class);
     private GridFactory() { }
-    static Grid create(Dimension dim) {
-        int r = dim.lastRow - dim.firstRow + 1
-            , c = dim.lastColumn - dim.firstColumn + 1;
-
-        return create(dim, r * c);
-    }
-
-    static Grid create(Dimension dim, int n) {
-        int r = dim.lastRow - dim.firstRow + 1
-            , c = dim.lastColumn - dim.firstColumn + 1;
-
-        return c <= 64 && r < 1 << 14 ? new Grid.FastGrid(dim)
-            : n > 1 << 10 ? new Grid.FractureGrid(dim) : new Grid.IndexGrid(dim, n);
+    static Grid create(List<Dimension> mergeCells) {
+        Dimension dim = mergeCells.get(0);
+        int fr = dim.firstRow, lr = dim.lastRow;
+        short fc = dim.firstColumn, lc = dim.lastColumn;
+        int n = (lr - fr + 1) * (lc - fc + 1);
+        for (int j = 1, len = mergeCells.size(); j < len; j++) {
+            dim = mergeCells.get(j);
+            n += (dim.lastRow - dim.firstRow + 1) * (dim.lastColumn - dim.firstColumn + 1);
+            if (fr > dim.firstRow)    fr = dim.firstRow;
+            if (lr < dim.lastRow)     lr = dim.lastRow;
+            if (fc > dim.firstColumn) fc = dim.firstColumn;
+            if (lc < dim.lastColumn)  lc = dim.lastColumn;
+        }
+
+        Dimension range = new Dimension(fr, fc, lr, lc);
+        int r = lr - fr + 1
+            , c = lc - fc + 1;
+
+        n = r * c;
+
+        Grid grid = c <= 64 && r < 1 << 14 ? new Grid.FastGrid(range)
+            : n > 1 << 10 ? new Grid.FractureGrid(range) : new Grid.IndexGrid(range, n);
+
+        for (Dimension d : mergeCells) {
+            grid.mark(d);
+            LOGGER.debug("merged cells range {}", d);
+        }
+        return grid;
     }
 }
\ No newline at end of file
diff --git a/src/main/java/org/ttzero/excel/reader/IndexSharedStringTable.java b/src/main/java/org/ttzero/excel/reader/IndexSharedStringTable.java
index 064b6e08..1871e898 100644
--- a/src/main/java/org/ttzero/excel/reader/IndexSharedStringTable.java
+++ b/src/main/java/org/ttzero/excel/reader/IndexSharedStringTable.java
@@ -345,7 +345,7 @@ public class IndexSharedStringTable extends SharedStringTable {
             int _pos = buffer.position();
             buffer.flip();
             if (buffer.hasRemaining()) {
-                buffer.position((int) (keyIndex - index_size) >> ssst << 3);
+                buffer.position(((int)((keyIndex >> ssst) - (index_size >> 3))) << 3);
                 position = buffer.getLong();
             }
             // Mark status WRITE
diff --git a/src/main/java/org/ttzero/excel/reader/SharedStrings.java b/src/main/java/org/ttzero/excel/reader/SharedStrings.java
index b7bea582..23abeb4a 100644
--- a/src/main/java/org/ttzero/excel/reader/SharedStrings.java
+++ b/src/main/java/org/ttzero/excel/reader/SharedStrings.java
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
 import org.ttzero.excel.entity.ExcelWriteException;
 
 import java.io.BufferedReader;
+import java.io.Closeable;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -45,7 +46,7 @@ import static org.ttzero.excel.util.StringUtil.EMPTY;
  *
  * @author guanquan.wang at 2018-09-27 14:28
  */
-public class SharedStrings implements AutoCloseable {
+public class SharedStrings implements Closeable {
     private final Logger LOGGER = LoggerFactory.getLogger(getClass());
     private Path sstPath;
 
@@ -58,6 +59,7 @@ public class SharedStrings implements AutoCloseable {
     SharedStrings(String[] data) {
         max = data.length;
         offset_forward = 0;
+        status = 1;
         if (max <= page) {
             forward = new String[max];
             System.arraycopy(data, offset_forward, forward, 0, max);
@@ -66,6 +68,7 @@ public class SharedStrings implements AutoCloseable {
             if (max > page << 1) {
                 page = max >> 1;
             }
+            status <<= 1;
             forward = new String[page];
             limit_forward = page;
             System.arraycopy(data, offset_forward, forward, 0, limit_forward);
@@ -92,6 +95,25 @@ public class SharedStrings implements AutoCloseable {
         this.hotSize = hotSize;
     }
 
+    /**
+     * Constructs a SharedStrings with a {@link IndexSharedStringTable}
+     *
+     * @param sst {@link IndexSharedStringTable}
+     * @param cacheSize the number of word per load
+     * @param hotSize   the number of high frequency word
+     */
+    SharedStrings(IndexSharedStringTable sst, int cacheSize, int hotSize) throws IOException {
+        this.sst = sst;
+        max = sst.size();
+        if (cacheSize > 0) {
+            this.page = cacheSize;
+        }
+        this.hotSize = hotSize;
+        init();
+        // Load forward
+        limit_forward = sst.get(offset_forward = 0, forward);
+    }
+
     /**
      * Storage the new load data
      */
@@ -187,10 +209,18 @@ public class SharedStrings implements AutoCloseable {
             return this;
         }
 
-        status = 1;
         // Get unique count
         max = uniqueCount();
         LOGGER.debug("Size of SharedString: {}", max);
+        //
+        init();
+        escapeBuf = new StringBuilder();
+        return this;
+    }
+
+    /* */
+    private void init() throws IOException {
+        status = 1;
         // Unknown size or greater than 512
         if (max < 0 || max > page << 1) {
             status <<= 2;
@@ -203,8 +233,10 @@ public class SharedStrings implements AutoCloseable {
             if (hotSize > 0) hot = FixSizeLRUCache.create(hotSize);
             else hot = FixSizeLRUCache.create();
             // Instance the SharedStringTable
-            sst = new IndexSharedStringTable();
-            sst.setShortSectorSize(9);
+            if (sst == null) {
+                sst = new IndexSharedStringTable();
+                sst.setShortSectorSize(9);
+            }
         }
         else if (max > page) {
             status <<= 1;
@@ -213,8 +245,6 @@ public class SharedStrings implements AutoCloseable {
         } else {
             forward = new String[max];
         }
-        escapeBuf = new StringBuilder();
-        return this;
     }
 
     /**
@@ -593,6 +623,11 @@ public class SharedStrings implements AutoCloseable {
         }
     }
 
+    @Override
+    public String toString() {
+        return "Count: " + (total <= 0 ? max : total) + "，UniqueCount: " + max;
+    }
+
 }
 
 interface Tester {
diff --git a/src/main/java/org/ttzero/excel/reader/Sheet.java b/src/main/java/org/ttzero/excel/reader/Sheet.java
index 4351ad78..499dbec1 100644
--- a/src/main/java/org/ttzero/excel/reader/Sheet.java
+++ b/src/main/java/org/ttzero/excel/reader/Sheet.java
@@ -21,6 +21,7 @@ import org.ttzero.excel.util.CSVUtil;
 import org.ttzero.excel.util.FileUtil;
 import org.ttzero.excel.util.StringUtil;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.file.Files;
@@ -45,7 +46,7 @@ import static org.ttzero.excel.util.FileUtil.exists;
 /**
  * @author guanquan.wang at 2019-04-17 11:36
  */
-public interface Sheet extends AutoCloseable {
+public interface Sheet extends Closeable {
 
     /**
      * The worksheet name
diff --git a/src/main/java/org/ttzero/excel/reader/XMLRow.java b/src/main/java/org/ttzero/excel/reader/XMLRow.java
index 6a322370..c6e3ae52 100644
--- a/src/main/java/org/ttzero/excel/reader/XMLRow.java
+++ b/src/main/java/org/ttzero/excel/reader/XMLRow.java
@@ -452,6 +452,7 @@ class XMLCalcRow extends XMLRow {
     /**
      * Loop parse cell
      */
+    @Override
     void parseCells() {
         int index = 0;
         cursor = searchSpan();
diff --git a/src/main/java/org/ttzero/excel/reader/XMLSheet.java b/src/main/java/org/ttzero/excel/reader/XMLSheet.java
index 3ac05bdd..7b439c3d 100644
--- a/src/main/java/org/ttzero/excel/reader/XMLSheet.java
+++ b/src/main/java/org/ttzero/excel/reader/XMLSheet.java
@@ -291,6 +291,10 @@ class XMLSheet implements Sheet {
         if (!eof && dimension == null) {
             parseDimension();
         }
+        // Create a empty worksheet dimension
+        if (dimension == null) {
+            dimension = new Dimension(1, (short) 1, 0, (short) 0);
+        }
 
         return this;
     }
@@ -634,10 +638,12 @@ class XMLSheet implements Sheet {
         return new XMLRow(sst, styles, this.startRow > 0 ? this.startRow : 1).with(cb, start, n);
     }
 
+    @Override
     public XMLCalcSheet asCalcSheet() {
         return !(this instanceof XMLCalcSheet) ? new XMLCalcSheet(this) : (XMLCalcSheet) this;
     }
 
+    @Override
     public XMLMergeSheet asMergeSheet() {
         return !(this instanceof XMLMergeSheet) ? new XMLMergeSheet(this) : (XMLMergeSheet) this;
     }
@@ -692,6 +698,7 @@ class XMLCalcSheet extends XMLSheet implements CalcSheet {
         return this;
     }
 
+    @Override
     Row createHeader(char[] cb, int start, int n) {
         return new XMLCalcRow(sst, styles, this.startRow > 0 ? this.startRow : 1, this::findCalc).with(cb, start, n);
     }
@@ -743,6 +750,7 @@ class XMLMergeSheet extends XMLSheet implements MergeSheet {
      * @return Sheet
      * @throws IOException if io error occur
      */
+    @Override
     public XMLMergeSheet load() throws IOException {
         super.load();
 
@@ -760,6 +768,7 @@ class XMLMergeSheet extends XMLSheet implements MergeSheet {
     /*
     Parse `mergeCells` tag
      */
+    @Override
     void parseMerge(SeekableByteChannel channel, CharBuffer charBuffer, ByteBuffer buffer, int block) throws IOException {
         // Find mergeCells tag
         int limit = charBuffer.limit();
@@ -807,27 +816,8 @@ class XMLMergeSheet extends XMLSheet implements MergeSheet {
         }
 
         if (mergeCells != null) {
-            Dimension dim = mergeCells.get(0);
-            int fr = dim.firstRow, lr = dim.lastRow;
-            short fc = dim.firstColumn, lc = dim.lastColumn;
-            int n = (lr - fr + 1) * (lc - fc + 1);
-            for (int j = 1, len = mergeCells.size(); j < len; j++) {
-                dim = mergeCells.get(j);
-                n += (dim.lastRow - dim.firstRow + 1) * (dim.lastColumn - dim.firstColumn + 1);
-                if (fr > dim.firstRow)    fr = dim.firstRow;
-                if (lr < dim.lastRow)     lr = dim.lastRow;
-                if (fc > dim.firstColumn) fc = dim.firstColumn;
-                if (lc < dim.lastColumn)  lc = dim.lastColumn;
-            }
-
-            Grid grid = GridFactory.create(new Dimension(fr, fc, lr, lc), n);
-            for (Dimension d : mergeCells) {
-                grid.mark(d);
-                LOGGER.debug("merged cells range {}", d);
-            }
-
-            LOGGER.debug("Grid: {}", grid);
-            this.mergeCells = grid;
+            this.mergeCells = GridFactory.create(mergeCells);
+            LOGGER.debug("Grid: {}", this.mergeCells);
         }
     }
 
diff --git a/src/main/java/org/ttzero/excel/util/CSVUtil.java b/src/main/java/org/ttzero/excel/util/CSVUtil.java
index 59e6e82a..3c846991 100644
--- a/src/main/java/org/ttzero/excel/util/CSVUtil.java
+++ b/src/main/java/org/ttzero/excel/util/CSVUtil.java
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -61,16 +62,16 @@ import static org.ttzero.excel.util.FileUtil.mkdir;
  * <p>
  * RFC 4180 formalized CSV. It defines the MIME type "text/csv", and CSV files that follow its
  * rules should be very widely portable. Among its requirements:
- * <p>
- * 1. MS-DOS-style lines that end with (CR/LF) characters (optional for the last line).
- * 2. An optional header record (there is no sure way to detect whether it is present,
- * so care is required when importing).
- * 3. Each record "should" contain the same number of comma-separated fields.
- * 4. Any field may be quoted (with double quotes).
- * 5. Fields containing a line-break, double-quote or commas should be quoted. (If they are not,
- * the file will likely be impossible to process correctly).
- * 6. A (double) quote character in a field must be represented by two (double) quote characters.
- *
+ * <ul>
+ * <li>MS-DOS-style lines that end with (CR/LF) characters (optional for the last line).</li>
+ * <li>An optional header record (there is no sure way to detect whether it is present,
+ * so care is required when importing).</li>
+ * <li>Each record "should" contain the same number of comma-separated fields.</li>
+ * <li>Any field may be quoted (with double quotes).</li>
+ * <li>Fields containing a line-break, double-quote or commas should be quoted. (If they are not,
+ * the file will likely be impossible to process correctly).</li>
+ * <li>A (double) quote character in a field must be represented by two (double) quote characters.</li>
+ * </ul>
  * @author guanquan.wang at 2019-02-12 17:27
  */
 public class CSVUtil {
@@ -332,7 +333,7 @@ public class CSVUtil {
      *
      * @since 1.8
      */
-    public static class Reader implements AutoCloseable {
+    public static class Reader implements Closeable {
 
         private RowsIterator iterator;
         private Path path;
@@ -502,7 +503,7 @@ public class CSVUtil {
     /**
      * Rows iterator
      */
-    public static class RowsIterator implements AutoCloseable, Iterator<String[]> {
+    public static class RowsIterator implements Closeable, Iterator<String[]> {
         private int column;
         private final char comma;
         private BufferedReader reader;
@@ -948,7 +949,7 @@ public class CSVUtil {
      *
      * @since 1.8
      */
-    public static class Writer implements AutoCloseable {
+    public static class Writer implements Closeable {
 
         private BufferedWriter writer;
         // Comma separator character, default ','
diff --git a/src/main/java/org/ttzero/excel/util/StringUtil.java b/src/main/java/org/ttzero/excel/util/StringUtil.java
index b9a7c4fa..048b65a8 100644
--- a/src/main/java/org/ttzero/excel/util/StringUtil.java
+++ b/src/main/java/org/ttzero/excel/util/StringUtil.java
@@ -138,24 +138,6 @@ public class StringUtil {
         return new String(oldValues, 0, idx);
     }
 
-//    public static String append(String src, String a, int n, int origin) {
-//        StringBuilder buf = new StringBuilder();
-//        // Insert header
-//        if (origin == -1) {
-//            for (; n-- > 0; ) {
-//                buf.append(a);
-//            }
-//            buf.append(src);
-//        } else {
-//            buf.append(src);
-//            for (; n-- > 0; ) {
-//                buf.append(a);
-//            }
-//        }
-//
-//        return buf.toString();
-//    }
-
     /**
      * Wrap value in string array
      *


Test patch:
diff --git a/src/test/java/org/ttzero/excel/reader/GridTest.java b/src/test/java/org/ttzero/excel/reader/GridTest.java
index 8ea5770f..1a64afc0 100644
--- a/src/test/java/org/ttzero/excel/reader/GridTest.java
+++ b/src/test/java/org/ttzero/excel/reader/GridTest.java
@@ -18,6 +18,11 @@ package org.ttzero.excel.reader;
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
 import static org.ttzero.excel.Print.println;
 import static org.ttzero.excel.reader.Grid.FastGrid.isPowerOfTwo;
 
@@ -26,35 +31,20 @@ import static org.ttzero.excel.reader.Grid.FastGrid.isPowerOfTwo;
  */
 public class GridTest {
     @Test public void testGridType() {
-        Grid grid = GridFactory.create(Dimension.of("A1:BM10"));
+        Grid grid = GridFactory.create(Collections.singletonList(Dimension.of("A1:BM10")));
 
         assert grid instanceof Grid.IndexGrid;
 
-        grid = GridFactory.create(Dimension.of("A1:B16383"));
+        grid = GridFactory.create(Collections.singletonList(Dimension.of("A1:B16383")));
         assert grid instanceof Grid.FastGrid;
 
-        grid = GridFactory.create(Dimension.of("A1:B16384"));
+        grid = GridFactory.create(Collections.singletonList(Dimension.of("A1:B16384")));
         assert grid instanceof Grid.FractureGrid;
 
     }
 
     @Test public void testGrid1() {
-        Grid grid = GridFactory.create(new Dimension(1, (short) 1, 10, (short) 1));
-
-        grid.mark(new Dimension(3, (short) 1, 7, (short) 1));
-
-        println(grid);
-
-        assert !grid.test(2, 1);
-        assert grid.test(3, 1);
-        assert grid.test(7, 1);
-        assert !grid.test(8, 1);
-    }
-
-    @Test public void testGrid2() {
-        Grid grid = GridFactory.create(new Dimension(1, (short) 1, 10, (short) 2));
-
-        grid.mark(new Dimension(3, (short) 1, 7, (short) 1));
+        Grid grid = GridFactory.create(Collections.singletonList(new Dimension(3, (short) 1, 7, (short) 1)));
 
         println(grid);
 
@@ -65,9 +55,7 @@ public class GridTest {
     }
 
     @Test public void testGrid4() {
-        Grid grid = GridFactory.create(new Dimension(1, (short) 1, 10, (short) 3));
-
-        grid.mark(new Dimension(3, (short) 1, 7, (short) 3));
+        Grid grid = GridFactory.create(Collections.singletonList(new Dimension(3, (short) 1, 7, (short) 3)));
 
         println(grid);
 
@@ -80,38 +68,37 @@ public class GridTest {
     }
 
     @Test public void testGrid8() {
-        Grid grid = GridFactory.create(new Dimension(1, (short) 1, 10, (short) 8));
-
-        grid.mark(new Dimension(3, (short) 4, 7, (short) 4));
+        Grid grid = GridFactory.create(Collections.singletonList(new Dimension(3, (short) 4, 7, (short) 4)));
 
         println(grid);
     }
 
     @Test public void testGrid8_2() {
-        Grid grid = GridFactory.create(Dimension.of("A1:H71"));
-
-        grid.mark(Dimension.of("C10:D10"));
-        grid.mark(Dimension.of("C5:D5"));
-        grid.mark(Dimension.of("C6:D6"));
-        grid.mark(Dimension.of("C7:D7"));
-        grid.mark(Dimension.of("C8:D8"));
-        grid.mark(Dimension.of("C9:D9"));
-        grid.mark(Dimension.of("A39:A71"));
-        grid.mark(Dimension.of("D1:E1"));
-        grid.mark(Dimension.of("A1:A26"));
-        grid.mark(Dimension.of("A27:A38"));
-        grid.mark(Dimension.of("E20:H20"));
-        grid.mark(Dimension.of("E21:H21"));
-        grid.mark(Dimension.of("E22:H22"));
-        grid.mark(Dimension.of("E23:H23"));
-        grid.mark(Dimension.of("E24:H24"));
-        grid.mark(Dimension.of("E25:H25"));
-        grid.mark(Dimension.of("C11:D11"));
-        grid.mark(Dimension.of("C12:D12"));
-        grid.mark(Dimension.of("C13:D13"));
-        grid.mark(Dimension.of("C14:D14"));
-        grid.mark(Dimension.of("C15:D15"));
-        grid.mark(Dimension.of("C16:D16"));
+        List<Dimension> list = new ArrayList<>();
+        list.add(Dimension.of("C10:D10"));
+        list.add(Dimension.of("C5:D5"));
+        list.add(Dimension.of("C6:D6"));
+        list.add(Dimension.of("C7:D7"));
+        list.add(Dimension.of("C8:D8"));
+        list.add(Dimension.of("C9:D9"));
+        list.add(Dimension.of("A39:A71"));
+        list.add(Dimension.of("D1:E1"));
+        list.add(Dimension.of("A1:A26"));
+        list.add(Dimension.of("A27:A38"));
+        list.add(Dimension.of("E20:H20"));
+        list.add(Dimension.of("E21:H21"));
+        list.add(Dimension.of("E22:H22"));
+        list.add(Dimension.of("E23:H23"));
+        list.add(Dimension.of("E24:H24"));
+        list.add(Dimension.of("E25:H25"));
+        list.add(Dimension.of("C11:D11"));
+        list.add(Dimension.of("C12:D12"));
+        list.add(Dimension.of("C13:D13"));
+        list.add(Dimension.of("C14:D14"));
+        list.add(Dimension.of("C15:D15"));
+        list.add(Dimension.of("C16:D16"));
+
+        Grid grid = GridFactory.create(list);
 
         assert grid.toString().equals("FastGrid\n" +
             "00011001\n00000001\n00000001\n00000001\n" +
@@ -135,19 +122,17 @@ public class GridTest {
     }
 
     @Test public void testGrid16() {
-        Grid grid = GridFactory.create(new Dimension(1, (short) 1, 10, (short) 10));
-
-        grid.mark(new Dimension(4, (short) 5, 9, (short) 7));
+        Grid grid = GridFactory.create(Collections.singletonList(new Dimension(4, (short) 5, 9, (short) 7)));
 
         println(grid);
     }
 
     @Test public void testGrid162() {
-        Grid grid = GridFactory.create(new Dimension(1, (short) 1, 10, (short) 10));
+        List<Dimension> list = Arrays.asList(new Dimension(2, (short) 2, 4, (short) 6)
+            , new Dimension(3, (short) 7, 5, (short) 9)
+            , new Dimension(7, (short) 10, 10, (short) 10));
+        Grid grid = GridFactory.create(list);
 
-        grid.mark(new Dimension(2, (short) 2, 4, (short) 6));
-        grid.mark(new Dimension(3, (short) 7, 5, (short) 9));
-        grid.mark(new Dimension(7, (short) 10, 10, (short) 10));
 
         println(grid);
 
@@ -158,17 +143,13 @@ public class GridTest {
     }
 
     @Test public void testGrid32() {
-        Grid grid = GridFactory.create(Dimension.of("A1:AF10"));
-
-        grid.mark(Dimension.of("G3:AA9"));
+        Grid grid = GridFactory.create(Collections.singletonList(Dimension.of("G3:AA9")));
 
         println(grid);
     }
 
     @Test public void testGrid64() {
-        Grid grid = GridFactory.create(new Dimension(1, (short) 1, 10, (short) 54));
-
-        grid.mark(new Dimension(2, (short) 2, 4, (short) 6));
+        Grid grid = GridFactory.create(Collections.singletonList(new Dimension(2, (short) 2, 4, (short) 6)));
 
         println(grid);
     }
@@ -210,9 +191,7 @@ public class GridTest {
     }
 
     @Test public void testIndexGrid() {
-        Grid grid = GridFactory.create(Dimension.of("A1:BM10"));
-
-        grid.mark(Dimension.of("B2:E2"));
+        Grid grid = GridFactory.create(Collections.singletonList(Dimension.of("B2:E2")));
 
         assert !grid.test(1, 1);
         assert grid.test(2, 2);
@@ -222,9 +201,7 @@ public class GridTest {
     }
 
     @Test public void testFractureGrid() {
-        Grid grid = GridFactory.create(Dimension.of("A1:E16384"));
-
-        grid.mark(Dimension.of("B1:C3"));
+        Grid grid = GridFactory.create(Collections.singletonList(Dimension.of("B1:C3")));
 
         assert !grid.test(1, 1);
         assert grid.test(1, 2);