【问题标题】:Unexpected record type (org.apache.poi.hssf.record.HyperlinkRecord)意外的记录类型(org.apache.poi.hssf.record.HyperlinkRecord)
【发布时间】:2019-08-10 16:57:14
【问题描述】:

问题:

我只是尝试使用 Apache-poi 4.1.0 库打开它的 .xls 文件,它在 similar question 中给出了与 4 年前相同的错误。

我已经试过了

放版本 3.12-3.16。

3.13 as well

所有版本都可以打开空白.xls并由我自己填写,但不是这个。

此文档是自动生成的,我需要制作一个接受它的程序。

我已经制作了一个可用的 .Net 标准库 C#,我尝试使用 xamarin android,这太可怕了,由于各种可怕的 SDK 链接错误,该应用程序的重量为 50 mb vs 3 mb,但那是另一回事.所以我决定在 Kotlin 上做。

代码来自documentation 可以查看file on git

    val inputStream = FileInputStream("./test.xls")
    val wb = HSSFWorkbook(inputStream)

我希望在打开 xls 时不会出错。

实际输出为

Exception in thread "main" java.lang.RuntimeException: Unexpected record type (org.apache.poi.hssf.record.HyperlinkRecord)
    at org.apache.poi.hssf.record.aggregates.RowRecordsAggregate.<init>(RowRecordsAggregate.java:97)
    at org.apache.poi.hssf.model.InternalSheet.<init>(InternalSheet.java:183)
    at org.apache.poi.hssf.model.InternalSheet.createSheet(InternalSheet.java:122)
    at org.apache.poi.hssf.usermodel.HSSFWorkbook.<init>(HSSFWorkbook.java:354)
    at org.apache.poi.hssf.usermodel.HSSFWorkbook.<init>(HSSFWorkbook.java:400)
    at org.apache.poi.hssf.usermodel.HSSFWorkbook.<init>(HSSFWorkbook.java:381)
    at ru.plumber71.toolbox.ExcelParcerKt.main(ExcelParcer.kt:19)
    at ru.plumber71.toolbox.ExcelParcerKt.main(ExcelParcer.kt)

不会以任何方式修改文档。如果有任何其他库可以从 .xls 文件中读取数据集或字符串就可以了。

【问题讨论】:

  • 您的文件已损坏。正如错误所述,在不应该出现的地方有一个HyperlinkRecordExcel 可能有足够的容忍度来打开该文件。但是你不能指望apache poi 也试图容忍所有可能的文件格式违规。如果我使用Excel 打开文件然后重新保存它,apache poi 之后就可以创建Workbook
  • @AxelRichter 我很高兴收到您的评论。谢谢你,它确实有效。但是这个文件每天几次来自组织,我想自动化这个过程。 (重新保存excel文件/修复文件)。奇怪的是.net library可以打开它。我在另一个具有相同文件的项目中使用它可能没有错误,因为它不提供任何格式化功能。 Java有类似的东西吗?我只需要阅读它。

标签: java excel kotlin apache-poi xls


【解决方案1】:

经过一番调查,我发现您的 test.xls 文件存在问题。

根据文件格式规范,所有HyperlinkRecords 应该一起在Hyperlink Table 中。它包含在单元格记录之后的Sheet Substream 中。在您的情况下,HyperlinkRecords 在其他记录之间(在这种情况下,在NumberRecords 和LabelSSTRecords 之间)。所以我怀疑不是Excel 创建了test.xls 文件。

Excel 可能有足够的容忍度来打开该文件。但是你不能指望apache poi 也试图容忍所有可能的文件格式违规。如果您使用Excel 打开文件然后重新保存它,apache poi 可以在此之后创建Workbook

Apache poi 无法像Excel 那样修复此问题。但是可以以一种低级的方式读取POIFSFileSystem 并过滤掉其他记录之间的HyperlinkRecords。这样就可以使用apache poi 阅读内容,当然超链接除外。

例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.poifs.filesystem.DirectoryNode;

import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.NameCommentRecord;
import org.apache.poi.hssf.record.HyperlinkRecord;
import org.apache.poi.hssf.record.RecordFactoryInputStream;
import org.apache.poi.hssf.record.RecordFactory;
import org.apache.poi.hssf.model.RecordStream;
import org.apache.poi.hssf.model.InternalWorkbook;
import org.apache.poi.hssf.model.InternalSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFName;

import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Cell;

import org.apache.poi.ss.util.CellReference;

import java.util.List;
import java.util.ArrayList;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;

class ExcelOpenHSSF {

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

  String fileName = "test(2).xls";

  try (InputStream is = new FileInputStream(fileName);
       POIFSFileSystem fileSystem = new POIFSFileSystem(is)) {

   //find workbook directory entry
   DirectoryNode directory = fileSystem.getRoot();
   String workbookName = "";
   for(String wbName : InternalWorkbook.WORKBOOK_DIR_ENTRY_NAMES) {
    if(directory.hasEntry(wbName)) {
     workbookName = wbName;
     break;
    }
   }
   InputStream stream = directory.createDocumentInputStream(workbookName);

   //loop over all records and manipulate if needed
   List<Record> records = new ArrayList<Record>();
   RecordFactoryInputStream recStream = new RecordFactoryInputStream(stream, true);

   //here we filter out the HyperlinkRecords that are between other records (NumberRecords and LabelSSTRecords in that case)
   //System.out.println prints the problematic records
   Record record1 = null;
   Record record2 = null;
   while ((record1 = recStream.nextRecord()) != null) {
    record2 = recStream.nextRecord();
    if (!(record1 instanceof HyperlinkRecord) && (record2 instanceof HyperlinkRecord)) {
     System.out.println(record1);
     System.out.println(record2);
     records.add(record1);  
    } else if ((record1 instanceof HyperlinkRecord) && !(record2 instanceof HyperlinkRecord)) {
     System.out.println(record1);
     System.out.println(record2);
     records.add(record2);  
    } else {
     records.add(record1);
     if (record2 != null) records.add(record2);
    }
   }

   //now create the HSSFWorkbook
   //see https://svn.apache.org/viewvc/poi/tags/REL_4_1_0/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java?view=markup#l322

   InternalWorkbook internalWorkbook = InternalWorkbook.createWorkbook(records);
   HSSFWorkbook wb = HSSFWorkbook.create(internalWorkbook);
   int recOffset = internalWorkbook.getNumRecords();

   Method convertLabelRecords = HSSFWorkbook.class.getDeclaredMethod("convertLabelRecords", List.class, int.class); 
   convertLabelRecords.setAccessible(true);
   convertLabelRecords.invoke(wb, records, recOffset);

   RecordStream rs = new RecordStream(records, recOffset);
   while (rs.hasNext()) {
    InternalSheet internelSheet = InternalSheet.createSheet(rs);
    Constructor constructor = HSSFSheet.class.getDeclaredConstructor(HSSFWorkbook.class, InternalSheet.class);
    constructor.setAccessible(true);
    HSSFSheet hssfSheet = (HSSFSheet)constructor.newInstance(wb, internelSheet);
    Field _sheets = HSSFWorkbook.class.getDeclaredField("_sheets"); 
    _sheets.setAccessible(true);
    @SuppressWarnings("unchecked")
    List<HSSFSheet> sheets = (ArrayList<HSSFSheet>)_sheets.get(wb);
    sheets.add(hssfSheet);
   }

   for (int i = 0 ; i < internalWorkbook.getNumNames() ; ++i){
    NameRecord nameRecord = internalWorkbook.getNameRecord(i);
    Constructor constructor = HSSFName.class.getDeclaredConstructor(HSSFWorkbook.class, NameRecord.class, NameCommentRecord.class);
    constructor.setAccessible(true);
    HSSFName name = (HSSFName)constructor.newInstance(wb, nameRecord, internalWorkbook.getNameCommentRecord(nameRecord));
    Field _names = HSSFWorkbook.class.getDeclaredField("names"); 
    _names.setAccessible(true);
    @SuppressWarnings("unchecked")
    List<HSSFName> names = (ArrayList<HSSFName>)_names.get(wb);
    names.add(name);
   }

   //now the workbook is created properly
   System.out.println(wb);

/*
   //getting the data
   DataFormatter formatter = new DataFormatter();
   Sheet sheet = wb.getSheetAt(0);
   for (Row row : sheet) {
    for (Cell cell : row) {
     CellReference cellRef = new CellReference(row.getRowNum(), cell.getColumnIndex());
     System.out.print(cellRef.formatAsString());
     System.out.print(" - ");
     String text = formatter.formatCellValue(cell);
     System.out.println(text);
    }
   }
*/

  }
 }
}

【讨论】:

  • 是的,谢谢,我也试过过滤,但是用了半天还是不行,决定换个库试试。但现在你可以回去了。特别感谢已解决的问题!
【解决方案2】:

我能够使用JExcel API打开这种“损坏”类型的文件

但如果使用 excel 应用程序手动重新保存文件,使用 poi.apache.org 也会打开文件。 (可能不适合某人)

抱歉,它问了一些奇怪的问题。谢谢大家,希望有人会觉得有用。

val inputStream = FileInputStream("./testCorrupted.xls")
val workbook = Workbook.getWorkbook(inputStream)
val sheet = workbook.getSheet(0)
val cell1 = sheet.getCell(0, 0)
print(cell1.contents + ":")   

【讨论】:

    猜你喜欢
    • 2015-08-27
    • 2019-06-29
    • 2019-12-04
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-17
    相关资源
    最近更新 更多