Apache POI 常见问题列表

什么样的文章?

尽管Apache POI非常实用,但也存在许多容易出错的地方。
本文记录了创建Excel文件比较工具”方眼Diff”时遇到的问题,希望能对他人有所帮助。

再次强调,Apache POI非常方便。由于这样的东西可以免费使用,所以我们对贡献者们非常感激。如果有抱怨的话,请自己贡献一下,至少提出一个问题。换句话说,这篇文章是记录了我个人无法做到这一点的力量不足。

请注意,由于本文可能包含基于我的误解或理解不足的内容,请谅解。如果您发现了这些问题,请给我留言,我将非常感激。

这篇文章假定的版本。

Apache POI 4.1.0~(下表に記載)
Excel for Office 365
OpenJDK 12.0.1

各种爱好和迷恋一览表

№ハマりポイント対象POI ver.記載日1シートの種類(ワークシート、グラフシート、マクロシート、ダイアログシート)を見分けられないSheet インタフェース4.1.02019/7/15 初稿2クラス名の大文字/小文字がタイポ?XSSFDialogsheet クラス4.1.02019/7/15 初稿3一般的な命名則と異なるメソッド名に注意HSSFSheet#getDialog() メソッド4.1.02019/7/15 初稿4HSSFSheet#getDialog() が機能しないHSSFSheet#getDialog() メソッド4.1.02019/7/15 初稿5.xlsx/.xlsm 形式のダイアログシート/マクロシートは無視されるWorkbookFactory#create(File) メソッド4.1.02019/7/15 初稿6.xls 形式のダイアログシートはイベントモデル API でも識別できないWSBoolRecord#getDialog() メソッド4.1.02019/7/15 初稿7ブック形式(.xls vs .xlsx/.xlsm)によって数式内の空白が再現されたりされなかったりするCell#getCellFormula() メソッド4.1.02019/8/12 追加8斜めの罫線を操作するためのAPIが提供されていないCellStyle インタフェース4.1.02019/10/5 追加9空コメントに関する挙動がファイル形式によって異なるComment#getString() メソッド4.1.22020/3/25 追加10XSSFComment#setVisible(boolean) が機能しないXSSFComment#setVisible(boolean) メソッド4.1.22020/3/25 追加11

一边回忆,我会逐步添加追述。

给予详细说明

无法区分表格的类型(工作表、图表表格、宏表格、对话框表格)。

解釋

Excel的工作表有以下几种类型。

    • ワークシート

 

    • グラフシート

 

    • ダイアログシート

 

    マクロシート

我认为有时候会有这样的情况,希望能够区分这些类型中的工作表,但出乎意料的是,Apache POI的用户模型API并没有提供这样的功能。

在使用Apache POI操作Excel表格时,最初的选择是使用称为用户模型API的功能组,该功能组包含了可以透明处理.xls/.xlsx/.xlsm格式文件的org.apache.poi.ss.usermodel.Sheet。该接口不提供像Sheet#isWorksheet()这样的API。

那该怎么办呢?
Sheet接口具有以下层次结构。

①Sheet:.xls/.xlsx/.xlsm 格式的sheet
├─ ②HSSFSheet:.xls 格式的sheet
├─ ③XSSFSheet:.xlsx/.xlsm 格式的sheet
│  ├─ ④XSSFChartSheet:.xlsx/.xlsm 格式的图表sheet
│  └─ ⑤XSSFDialogsheet:.xlsx/.xlsm 格式的对话框sheet
└─ ⑥SXSSFSheet:.xlsx 格式的 sheet(流式处理方式)

对于 .xlsx/.xlsm 格式的表格,可以通过检查 ④⑤ 实例来确认表格的类型3。
然而,对于 .xls 格式的表格,虽然提供了一个返回对话表格与否的方法4,但没有提供辨别图表表格与否的方法。
在这里我们遇到了障碍。

解决办法

通过使用更低层次的解决方案可以解决该问题。

    • .xls 形式のシートについては、Apache POI が提供するイベントモデル API を利用することでシートの種類を見分けることができます5。

 

    .xlsx/.xlsm 形式のシートについては、SAX (Simple API for XML) を利用することでシートの種類を見分けることができます。

请参阅此文章以获取更详细的信息。

第二个问题是班级名称的大小写有错字吗?

解释

以下是之前介绍过的Sheet接口的派生层次结构:

org.apache.poi.ss.usermodel.Sheet(工作表)
├─ org.apache.poi.hssf.usermodel.HSSFSheet(HSSF工作表)
├─ org.apache.poi.xssf.usermodel.XSSFSheet(XSSF工作表)
│  ├─ org.apache.poi.xssf.usermodel.XSSFChartSheet(XSSF图表工作表)
│  └─ org.apache.poi.xssf.usermodel.XSSFDialogsheet(XSSF对话框工作表)
└─ org.apache.poi.xssf.streaming.SXSSFSheet(SXSSF工作表)

你明白了吗?XSSFDialogsheet只有字母”s”是小写的。这是故意的吗?不,应该是打错了。

解决方法

如果实施者这样命名的话,那就是正确的类名。基于主观臆断的争论对任何问题都没有解决作用。让我们充分利用 IDE 的自动补全功能和提醒建议。

请注意与一般命名规则不同的方法名称。

请解释

在No.1的说明中,我写道HSSFSheet接口提供了一个返回对话框表格(true)或者非对话框表格(false)的方法。以下是具体的方法(摘自Apache POI提供的API文档)。

获取对话框
public boolean 获取对话框()
判断工作表是否为对话框工作表
返回值:
是否为对话框

进行此操作后会返回一个布尔值。这不错吧。
并不一定必须使用名为isDialogSheet()的名称。

解决方案 Cè)

在寻找某种方法时,不要凭空断定方法的名称。如果需要一个返回布尔值的方法,应该仔细查找返回值类型为布尔的方法。这才是真正的事实。应该摒弃主观臆断,从上到下仔细阅读 API 文档,寻找是否提供了目标功能。

第四个HSSFSheet#getDialog()方法无法正常运行。

解释

需要注意的是,在第3点介绍的HSSFSheet#getDialog()方法无法正常工作。具体来说,

    • グラフシートに対してこのメソッドを実行すると、NullPointerException が発生します。

 

    少なくとも Excel for Office 365 で作成したダイアログシートに対してこのメソッドを実行すると、true ではなく false が返されます。

我会进行现场演示。

1) 创建测试用的Excel文件(.xls格式)

如果右键单击工作表标签,然后选择”插入…”,您会发现可以创建以下类型的工作表。

    • ワークシート

 

    • 「グラフ」シート

 

    • 「Excel 4.0 マクロ」シート

 

    「MS Excel 5.0 ダイアログ」シート
No.4-a.png

创建每个工作表,并填入相应的内容。

No.4-b.png

将此保存为.xls格式的Excel工作簿。

2) 编写用于测试的代码

例如,创建以下测试代码。

package mypackage;

import java.io.File;

import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

public class Main {

    public static void main(String[] args) {

        try (Workbook wb = WorkbookFactory.create(
                new File("C:\\Users\\xxx\\No.4テスト.xls"))) {

            System.out.println(wb.getNumberOfSheets());

            wb.sheetIterator().forEachRemaining(s -> {
                System.out.print(String.format(
                        "%s : %s : ",
                        s.getSheetName(),
                        s.getClass().getName()));

                if (s instanceof HSSFSheet) {
                    HSSFSheet sheet = (HSSFSheet) s;
                    try {
                        System.out.println(sheet.getDialog());
                    } catch (NullPointerException e) {
                        System.out.println("★NPE発生★");
                    }
                } else {
                    System.out.println();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3) 进行测试

运行上述的测试代码将得到以下结果。

4
A_ワークシート : org.apache.poi.hssf.usermodel.HSSFSheet : false
B_グラフシート : org.apache.poi.hssf.usermodel.HSSFSheet : ★NPE発生★
C_マクロシート : org.apache.poi.hssf.usermodel.HSSFSheet : false
D_ダイアログシート : org.apache.poi.hssf.usermodel.HSSFSheet : false

在”B_图表表格”中发生了NullPointerException,并且在”D_对话框表格”中返回的不是true,而是false。

解决方案 (jiě jué cè)

我认为不应该使用HSSFSheet#getDialog()方法。

忽略掉以 .xlsx/.xlsm 格式的对话框工作表/宏工作表。

解释

在Office 365上使用Excel创建的.xlsx/.xlsm文件,似乎无法使用Apache POI的用户模型API加载对话框页和宏页。让我来演示一下。

1)创建用于测试的Excel文件(以.xlsm格式)

将在No.4中使用的测试文件以.xlsm格式重新保存。
※由于包含宏表格,将作为启用宏的工作簿进行保存。

2) 创建测试代码

使用与第四个相同的测试代码。
只需更改文件名指定部分。

3) 进行测验

在执行测试代码时,会得到以下结果。

2
A_ワークシート : org.apache.poi.xssf.usermodel.XSSFSheet : 
B_グラフシート : org.apache.poi.xssf.usermodel.XSSFChartSheet : 

可以看出「C_マクロシート」和「D_ダイアログシート」没有被加载。

解决方案

如果你想从 .xlsx/.xlsm 格式的Excel文件中读取宏表和对话框表,我认为使用POI的事件模型API或SAX(简单XML API)可能更好,但还未进行验证。

第6个.xls格式的对话框表无法在事件模型API中识别。

解释

正如在 No.1中介绍的那样,Apache POI的用户模型API无法识别.xls格式的工作表类型。使用称为事件模型API的功能,可以选择以下选项。

.xls格式的Excel文件是一种二进制交换文件格式(BIFF),其内容是一系列的BIFF记录集合。
BIFF的概述在这篇博客中解释得很清楚,并且详细规格由Microsoft公开发布。

我們可以使用 Apache POI 的事件模型 API 來讀取 BIFF 記錄的內容,但看起來 WSBoolRecord#getDialog() 方法似乎無法正確運作,因此我們仍然無法正確地識別對話框工作表。
讓我們以下進行示範。

1)准备测试用Excel文件(.xls格式)

使用与4号相同的文件。

2)测试用代码

我們可以創建如下的測試代碼。

package mypackage;

import java.io.FileInputStream;

import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
import org.apache.poi.hssf.eventusermodel.HSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFRequest;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

public class Main {

    private static class TestListener implements HSSFListener {

        @Override
        public void processRecord(Record record) {
            System.out.println(record);
        }
    }

    public static void main(String[] args) throws Exception {

        try (FileInputStream fin = new FileInputStream(
                "C:\\Users\\xxx\\No.6テスト.xls");
                POIFSFileSystem poifs = new POIFSFileSystem(fin)) {

            HSSFRequest req = new HSSFRequest();
            TestListener listener = new TestListener();
            req.addListenerForAllRecords(listener);
            HSSFEventFactory factory = new HSSFEventFactory();
            factory.abortableProcessWorkbookEvents(req, poifs);
        }
    }
}

3) 进行测试

运行测试代码后,会输出BIFF记录的内容。
※左侧的行号只是为了说明而添加,实际上不会输出。

由于具体的内容较长,我将简要解释。以下BOF记录是指示对话框表单的定义开始。尽管我们很难理解为什么会这样,但是对话框表单的BOF记录的.type属性被定义为与工作表相同的0x0010。(相反,图表表单被定义为0x0020,宏表单被定义为0x0040,可以区分它们。)

...
5201: 
5202: [BOF RECORD]
5203:     .version  = 0x0600
5204:     .type     = 0x0010 (worksheet)
5205:     .build    = 0x4F5A
5206:     .buildyear= 1997
5207:     .history  = 0x000200C9
5208:     .reqver   = 0x00000806
5209: [/BOF RECORD]
5210: 
...

工作表和对话框工作表在后面的WSBOOL记录的.dialog属性中可以区分,但由于某种原因,实际输出中即使是对话框工作表也会显示.dialog = false。

...
5263: 
5264: [WSBOOL]
5265:     .wsbool1        = 4
5266:         .autobreaks = false
5267:         .dialog     = false
5268:         .rowsumsbelw= false
5269:         .rowsumsrigt= false
5270:     .wsbool2        = ffffffd1
5271:         .fittopage  = true
5272:         .displayguts= false
5273:         .alternateex= true
5274:         .alternatefo= true
5275: [/WSBOOL]
5276: 
...

解決方案

我认为WSBoolRecord类的实现可能存在问题。如果仔细对比以下BIFF规范和Apache公开的源代码,可能能找到原因并采取相应的解决方案。

或许,有一种稍显偏激的方法,可以将没有ROW记录的工作表视为对话框工作表(未经验证)。

根据No.7的书籍格式(.xls vs .xlsx/.xlsm),数学公式内的空白可能会有或没有被再现。

解释

在Apache POI的用户模型API中,不会保留公式中的空白。这个事实在Apache POI的文档中明确说明了。

如果不保留,只要一直保持在去除了空格的状态就好了,但根据原始Excel文件的格式,行为可能会有所不同。

当具体在一个单元格中被写成 = 1 + 2,并且当使用 Cell#getCellFormula() 方法读取时,

    • 元のExcelファイルが .xls 形式の場合、1+2 という文字列が取得されます。つまり、空白が削除されます。

 

    元のExcelファイルが .xlsx/.xlsm 形式の場合、1 + 2 という文字列が取得されます。つまり、数式内の空白が再現されます。

如果想要比较不同格式的Excel文件中的公式,就会遇到问题。

解决方案

从Cell#getCellFormula()方法获得的公式字符串中删除不必要的空格,然后通过自定义函数对公式字符串进行标准化是一种方法。在这种情况下,必须要注意的一点是。

首先,不应该在字符串文字中删除空格。这是因为在表达式 “= “Hello, World !!” 和 “= “Hello,World!!” 中,字符串的含义会发生改变。但是,对于这种程度的问题,处理起来并不是太难。

在Excel中,第二个值得注意的是,空格(半角空格)也起着“引用运算符”的作用。
例如,当一个单元格中输入了公式“= MAX(A1:C3 B2:E5)”时,它表示“单元格区域A1:C3和单元格区域B2:E5的交集,即单元格区域B2:C3中的最大值”。
Excel可以给单元格区域命名。因此,“= 区域A 区域B”这样的字符串也可以成为有效的公式。(如果“区域A”和“区域B”的交集是多个单元格,则会出现“#VALUE!”错误,如果是单个单元格,则该单元格的值将成为计算结果。)
当然,这样的空格作为“引用运算符”是不能删除的。

考虑到这些因素,创建标准化函数是一种选择。

或者,您可以参考此PDF文件等了解Excel文件的格式,并尝试自己编写解析器。

无论如何,这都是一项费力的工作。

没有提供操作斜线框的API。

解释它

CellStyle 接口及其子接口 HSSFCellStyle、XSSFCellStyle 接口中提供了一些用于操作边框的API。

    • CellStyle

罫線スタイルの取得

getBorderBottom(): BorderStyle
getBorderLeft(): BorderStyle
getBorderRight(): BorderStyle
getBorderTop(): BorderStyle

罫線色の取得

getBottomBorderColor(): short
getLeftBorderColor(): short
getRightBorderColor(): short
getTopBorderColor(): short

罫線スタイルの設定

setBorderBottom(BorderStyle): void
setBorderLeft(BorderStyle): void
setBorderRight(BorderStyle): void
setBorderTop(BorderStyle): void

罫線色の設定

setBottomBorderColor(short): void
setLeftBorderColor(short): void
setRightBorderColor(short): void
setTopBorderColor(short): void

HSSFCellStyle

(略)

XSSFCellStyle

(略)

正如您所见,API只提供了与纵向(左、右)和横向(顶部、底部)线条有关的功能,暂时没有提供与斜线有关的API。

解决方案

通过参考这篇博客(程序备忘录等 – Java Apache POI 获取斜线边框仅限于xls文件)中公开的信息,需要自己实现与斜线边框相关的处理。
在这个过程中,需要理解POI内部的类结构等。

没有9号: 关于空注释行为的差异取决于文件格式。

解释

对于空注释(即单元格中有注释但注释中没有任何内容),使用 xls 文件格式的工作簿返回空字符串 “”,而使用 xlsx/xlsm 文件格式的工作簿返回 null。

Cell cell = (何らかの方法でセルオブジェクトを取得)
Comment comment = cell.getCellComment();  // コメントが無い場合はnullが返される
RichTextString richText = comment.getString();  // 空コメントの場合も非null値が返される
String str = richText.getString();  // ★ブック形式によって空コメントの場合の挙動が異なる★

可以尝试的处理方式

也许可以考虑通过以下方式将空字符串””进行规范化。

String str = Optional.ofNullable(richText.getString).orElse("");

第10号XSSFComment#setVisible(boolean)无法正常运作。

解释

执行setVisible(false)对于显示的单元格注释不起作用,执行setVisible(true)对于隐藏的单元格注释也不起作用。尽管我考虑过true和false可能是相反的,但我尝试了一下,但仍然没有效果。

解法、处理方法

无法使用XSSFComment#setVisible(boolean)方法。
也没有找到替代方法。

中締め的概念是在日本餐桌禮儀中指當餐點即將結束時,主人或店家會準備一個標誌性的菜色,表示餐點結束。

我有点累了,所以我先到这里吧。

非常便利,但正如我之前提到的那样,如果想要充分利用它,可能会让你渐渐变得有点疯狂。

有一天,我希望能够亲自做出贡献,但为了保持自己的心理健康,我打算暂时离开。

非常方便,所以请务必使用。我们在这里发布了并撰写了文章。

作为一个业余程序员,将来贡献给开源软件是我的下一个目标。

但实际上,存在着在第5点介绍的陷阱。

但实际上,如第4点所介绍的,这个方法是无效的。

但实际上,这也存在着第6点介绍的陷阱。

广告
将在 10 秒后关闭
bannerAds