This is an automated email from the ASF dual-hosted git repository.

delei pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fesod.git


The following commit(s) were added to refs/heads/main by this push:
     new 70e445a0 fix: read XLS literal error cells as errors instead of 
booleans (#1011)
70e445a0 is described below

commit 70e445a062408c766dd0094f3f2f57963145d327
Author: anchor <[email protected]>
AuthorDate: Mon Aug 17 09:04:01 2026 +0800

    fix: read XLS literal error cells as errors instead of booleans (#1011)
    
    A BIFF BOOLERR record stores either a boolean or an error code,
    distinguished by its fError flag, but BoolErrRecordHandler called
    getBooleanValue() unconditionally. POI returns `errorCode != 0` for
    error records, so a literal #DIV/0! or #N/A read back as true and
    #NULL! (code 0) read back as false, while the same content stored in
    .xlsx read back as the error text.
    
    Branch on isError() and emit a CellDataTypeEnum.ERROR cell holding the
    error text, so the existing StringErrorConverter yields the same
    user-visible value as the xlsx path. This also lines the handler up
    with FormulaRecordHandler, which already maps XLS formula errors to
    ERROR.
    
    Assisted-by: Cursor (Fable 5)
    
    Co-authored-by: codeAnqiang-ma 
<[email protected]>
    Co-authored-by: Cursor <[email protected]>
    Co-authored-by: ian zhang <[email protected]>
    Co-authored-by: DeleiGuo <[email protected]>
---
 .../v03/handlers/BoolErrRecordHandler.java         |  18 ++-
 .../v03/handlers/BoolErrRecordHandlerTest.java     | 121 +++++++++++++++++++++
 2 files changed, 134 insertions(+), 5 deletions(-)

diff --git 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v03/handlers/BoolErrRecordHandler.java
 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v03/handlers/BoolErrRecordHandler.java
index 4fe379ca..8a5d5895 100644
--- 
a/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v03/handlers/BoolErrRecordHandler.java
+++ 
b/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v03/handlers/BoolErrRecordHandler.java
@@ -28,10 +28,12 @@ package org.apache.fesod.sheet.analysis.v03.handlers;
 import java.util.List;
 import org.apache.fesod.sheet.analysis.v03.IgnorableXlsRecordHandler;
 import org.apache.fesod.sheet.context.xls.XlsReadContext;
+import org.apache.fesod.sheet.enums.CellDataTypeEnum;
 import org.apache.fesod.sheet.enums.RowTypeEnum;
 import org.apache.fesod.sheet.metadata.data.ReadCellData;
 import org.apache.poi.hssf.record.BoolErrRecord;
 import org.apache.poi.hssf.record.Record;
+import org.apache.poi.ss.formula.eval.ErrorEval;
 
 /**
  * Record handler
@@ -52,11 +54,17 @@ public class BoolErrRecordHandler extends 
AbstractXlsRecordHandler implements Ig
                 return;
             }
         }
-        xlsReadContext
-                .xlsReadSheetHolder()
-                .getCellMap()
-                .put(targetColumnIndex, 
ReadCellData.newInstance(ber.getBooleanValue(), ber.getRow(), (int)
-                        targetColumnIndex));
+        ReadCellData<?> cellData;
+        if (ber.isError()) {
+            // A BOOLERR record stores either a boolean or an error code; 
getBooleanValue() would
+            // report the error code as `code != 0`.
+            cellData = new ReadCellData<>(CellDataTypeEnum.ERROR, 
ErrorEval.getText(ber.getErrorValue()));
+            cellData.setRowIndex(ber.getRow());
+            cellData.setColumnIndex(targetColumnIndex);
+        } else {
+            cellData = ReadCellData.newInstance(ber.getBooleanValue(), 
ber.getRow(), targetColumnIndex);
+        }
+        
xlsReadContext.xlsReadSheetHolder().getCellMap().put(targetColumnIndex, 
cellData);
         xlsReadContext.xlsReadSheetHolder().setTempRowType(RowTypeEnum.DATA);
     }
 }
diff --git 
a/fesod-sheet/src/test/java/org/apache/fesod/sheet/analysis/v03/handlers/BoolErrRecordHandlerTest.java
 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/analysis/v03/handlers/BoolErrRecordHandlerTest.java
new file mode 100644
index 00000000..15dc6b5e
--- /dev/null
+++ 
b/fesod-sheet/src/test/java/org/apache/fesod/sheet/analysis/v03/handlers/BoolErrRecordHandlerTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.fesod.sheet.analysis.v03.handlers;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.fesod.sheet.FesodSheet;
+import org.apache.fesod.sheet.enums.ReadDefaultReturnEnum;
+import org.apache.fesod.sheet.testkit.Tags;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.FormulaError;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * A cell holding a literal error value (an error stored as the cell value 
rather than as a formula
+ * result, e.g. after "Paste Special &rarr; Values") is written to a BIFF 
{@code BOOLERR} record, which
+ * stores either a boolean or an error code. Reading such a cell must yield 
the error text, exactly as
+ * the xlsx path already does, instead of a boolean derived from the error 
code.
+ */
+@Tag(Tags.READ)
+class BoolErrRecordHandlerTest {
+
+    private static final String MARKER = "marker";
+
+    @Test
+    void read_literalErrorCells_returnErrorText_inStringMode(@TempDir Path 
dir) throws IOException {
+        Map<Integer, Object> xls = readFirstRow(writeXls(dir), 
ReadDefaultReturnEnum.STRING);
+        Map<Integer, Object> xlsx = readFirstRow(writeXlsx(dir), 
ReadDefaultReturnEnum.STRING);
+
+        Map<Integer, Object> expected = new LinkedHashMap<>();
+        expected.put(0, "true");
+        expected.put(1, "#DIV/0!");
+        expected.put(2, "#N/A");
+        expected.put(3, "#NULL!");
+        expected.put(4, MARKER);
+
+        // Before the fix the .xls row read back as {0=true, 1=true, 2=true, 
3=false, 4=marker}: the
+        // error code was taken as `code != 0`, so #NULL! (code 0) even came 
back as false.
+        Assertions.assertEquals(expected, xls);
+        Assertions.assertEquals(xlsx, xls, "the same content must read the 
same from .xls and .xlsx");
+    }
+
+    @Test
+    void read_literalErrorCells_returnErrorText_inActualDataMode(@TempDir Path 
dir) throws IOException {
+        Map<Integer, Object> xls = readFirstRow(writeXls(dir), 
ReadDefaultReturnEnum.ACTUAL_DATA);
+        Map<Integer, Object> xlsx = readFirstRow(writeXlsx(dir), 
ReadDefaultReturnEnum.ACTUAL_DATA);
+
+        // The boolean cell keeps its Boolean type; only the error cells 
change.
+        Assertions.assertEquals(Boolean.TRUE, xls.get(0));
+        Assertions.assertEquals("#DIV/0!", xls.get(1));
+        Assertions.assertEquals("#N/A", xls.get(2));
+        Assertions.assertEquals("#NULL!", xls.get(3));
+        Assertions.assertEquals(MARKER, xls.get(4));
+        Assertions.assertEquals(xlsx, xls, "the same content must read the 
same from .xls and .xlsx");
+    }
+
+    /** One row: a literal boolean, three literal error values, and a plain 
string. */
+    private static void fillRow(Workbook workbook) {
+        Row row = workbook.createSheet("sheet").createRow(0);
+        row.createCell(0).setCellValue(true);
+        row.createCell(1).setCellErrorValue(FormulaError.DIV0.getCode());
+        row.createCell(2).setCellErrorValue(FormulaError.NA.getCode());
+        row.createCell(3).setCellErrorValue(FormulaError.NULL.getCode());
+        row.createCell(4).setCellValue(MARKER);
+    }
+
+    private static File writeXls(Path dir) throws IOException {
+        return write(dir.resolve("literal-error.xls"), new HSSFWorkbook());
+    }
+
+    private static File writeXlsx(Path dir) throws IOException {
+        return write(dir.resolve("literal-error.xlsx"), new XSSFWorkbook());
+    }
+
+    private static File write(Path path, Workbook workbook) throws IOException 
{
+        try (Workbook closeable = workbook;
+                OutputStream out = Files.newOutputStream(path)) {
+            fillRow(closeable);
+            closeable.write(out);
+        }
+        return path.toFile();
+    }
+
+    private static Map<Integer, Object> readFirstRow(File file, 
ReadDefaultReturnEnum readDefaultReturn) {
+        List<Map<Integer, Object>> rows = FesodSheet.read(file)
+                .readDefaultReturn(readDefaultReturn)
+                .headRowNumber(0)
+                .sheet(0)
+                .doReadSync();
+        return rows.get(0);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to