Apache Poi 无法读取表格名称。

3
我们正在经历一个关于通过Apache Poi读取Excel表格的奇怪错误。我们使用的是5.0版本。
这段代码之前是可以使用的,但现在在我们所有的生产环境中都停止工作了。当在本地测试时仍然可以正常工作,因此这证明其非常难以调试。
问题在于我们得到了返回空表名,因此无法正确加载所需的表格。
try (XSSFWorkbook wb = new XSSFWorkbook(new FileInputStream(venueListFile))) {

        LOGGER.info("Found {} sheets", wb.getNumberOfSheets());

        // First setup venues
        Sheet venueSetUpSheet = wb.getSheet("Store Set Up");
        List<String> sheetNames = new ArrayList<>();
        for (Iterator<Sheet> it = wb.sheetIterator(); it.hasNext(); ) {
            sheetNames.add(it.next().getSheetName());
        }

        if (venueSetUpSheet == null) {

            LOGGER.warn("Sheet 'Store Set Up' not found, available sheets: '" + String.join("','", sheetNames) + "'");
        } else {
            LOGGER.info("Found sheets: " + String.join("','", sheetNames) + "'");

本地运行此代码会返回:

Found 5 sheets
Found sheets: Store Set Up','Store Open Hours','Staff Setup','TV Configurations','Sheet3'

在生产环境中,对于同一个Excel文件,它返回:
Found 5 sheets
Sheet 'Store Set Up' not found, available sheets: 'null','null','null','null','null'

看起来文件已经被读取,并且我们已经测试了服务器上上传的文件没有损坏。有人知道Poi存在已知问题会导致表格名称为空吗?


1
工作表名称真的是“null”吗(我的意思是当您手动打开工作簿并使用人眼检查名称时,每个工作表都有预期的名称),还是Apache POI只是将它们解析为“null”,就像无法读取实际名称一样? - deHaar
手动检查Excel中的名称与我们“本地”运行的代码版本是否匹配。 - fraserh
1
这个什么时候停止工作的 - 你改了什么?你最近升级了Apache POI或者做了什么其他的事情吗?如果升级了Apache POI,看看降级是否有帮助,并且(如果有)报告一个bug。如果POI没有升级,那可能是其他问题。 - ewramner
测试环境和生产环境中的POI版本是否相同? - deHaar
不幸的是,目前还不清楚发生了什么变化。在生产和开发环境中,Java和POI版本都是相同的。但是,POI已经升级了,尽管这是几个月前的事情,但自那以后功能一直正常工作。我还应该指出,该功能也通过了我们的自动化测试。 - fraserh
1个回答

2

XSSFSheet基于底层的org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheet。而XSSFSheet.getSheetName仅仅返回org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheet.getName

如果它返回null而不是名称,则可能使用了错误的类org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheet。对于apache poi 5.0.0,它必须来自poi-ooxml-full-5.0.0.jarpoi-ooxml-lite-5.0.0.jar中的CTSheet。它不能来自ooxml-schemas-*.jar或任何poi-ooxml-schemas-*.jar,例如使用apache poi的较低版本。也许您的生产环境中的某个库会提供或拉取任何ooxml-schemas-*.jar?那么这就与apache poi 5.0.0不兼容。

您可以在运行时询问ClassLoader一个特定类(在您的情况下为org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheet)来自何处:

...
  ClassLoader classloader = org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheet.class.getClassLoader();
  java.net.URL res = classloader.getResource("org/openxmlformats/schemas/spreadsheetml/x2006/main/CTSheet.class");
  String path = res.getPath();
  System.out.println("CTSheet came from " + path);
...

如果不是 poi-ooxml-full-5.0.0.jar 或者 poi-ooxml-lite-5.0.0.jar,那么你可以大致了解哪个其他库也包含或调用了这个类。如果这不能帮助你,请检查 org.apache.xmlbeans.* 类的来源。XmlBeans 是为了使用 org.openxmlformats.schemas.* 类而需要的。
...
  classloader = org.apache.xmlbeans.XmlObject.class.getClassLoader();
  res = classloader.getResource("org/apache/xmlbeans/XmlObject.class");
  path = res.getPath();
  System.out.println("XmlObject came from " + path);     
...

使用apache poi 5.0.0,这个类必须来自于xmlbeans-4.0.0.jar,不能来自于其他任何版本的XmlBeans。如果以上方法都没有帮助,您还可以检查org.apache.poi.xssf.usermodel.XSSFSheet是否真的来自于apache poi 5.0.0 (poi-ooxml-5.0.0.jar)。也许在您的生产环境中有多个apache poi版本,这也是不支持的。
...
  classloader = org.apache.poi.xssf.usermodel.XSSFSheet.class.getClassLoader();
  res = classloader.getResource("org/apache/poi/xssf/usermodel/XSSFSheet.class");
  path = res.getPath();
  System.out.println("XSSFSheet came from " + path);     
...

最后,您可以尝试从底层的CTSheet对象中获取工作表名称,方法如下:
...
  try (Workbook wb = WorkbookFactory.create(new FileInputStream("./test.xlsx"));) {

   List<String> sheetNames = new ArrayList<>();
   for (Iterator<Sheet> it = wb.sheetIterator(); it.hasNext(); ) {
    Sheet sheet = it.next();
    if (sheet instanceof org.apache.poi.xssf.usermodel.XSSFSheet) {
     java.lang.reflect.Field _sheet = org.apache.poi.xssf.usermodel.XSSFSheet.class.getDeclaredField("sheet");
     _sheet.setAccessible(true);
     org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheet ctSheet = 
      (org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheet)_sheet.get((org.apache.poi.xssf.usermodel.XSSFSheet)sheet);
     org.apache.xmlbeans.impl.values.TypeStore store = ((org.apache.xmlbeans.impl.values.XmlObjectBase)ctSheet).get_store();
     System.out.println(store);
     org.apache.xmlbeans.SimpleValue target = (org.apache.xmlbeans.SimpleValue)store.find_attribute_user(new javax.xml.namespace.QName("", "name"));
     System.out.println(target.getStringValue());
    }
    sheetNames.add(sheet.getSheetName());
   }
  
   System.out.println(sheetNames);
  }
...

这个有用吗?还是会抛出异常?如果是的话,是哪个异常?

当然,要检查所有错误日志以查找错误。由于Office Open XML将数据存储在XML中,使用Java解析XML可能会出现问题。但是应该会抛出异常,需要在某个地方记录下来。


谢谢您的反馈。我们已经添加了上述代码以输出路径,并确认正在使用正确的类。然而,我们发现回滚到早期版本的Java和Ubuntu后,问题得到了解决。这个版本可以工作:openjdk-8-jre-headless/now 8u282-b08-0ubuntu1~18.04 amd64 [installed,upgradable to: 8u292-b10-0ubuntu1~18.04]这个版本不行:openjdk-8-jre-headless/bionic-updates,bionic-security,now 8u292-b10-0ubuntu1~18.04 amd64 [installed,automatic] - fraserh
我已经添加了你提到的额外日志记录。这是输出结果:CTSheet 来自文件:../lib/poi-ooxml-lite-5.0.0.jar!/org/openxmlformats/schemas/spreadsheetml/x2006/main/CTSheet.class XmlObject 来自文件../lib/xmlbeans-4.0.0.jar!/org/apache/xmlbeans/XmlObject.class XSSFSheet 来自文件:.../lib/poi-ooxml-5.0.0.jar!/org/apache/poi/xssf/usermodel/XSSFSheet.class使用你上面提供的代码示例,输出结果也包含了 null 值。 - fraserh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接