Bug 56170 - Exception when adding content to tables that have no headers
Summary: Exception when adding content to tables that have no headers
Status: RESOLVED FIXED
Alias: None
Product: POI
Classification: Unclassified
Component: XSSF (show other bugs)
Version: 3.10-FINAL
Hardware: PC All
: P2 normal (vote)
Target Milestone: ---
Assignee: POI Developers List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2014-02-20 10:39 UTC by Dirk Dittert
Modified: 2014-05-18 19:21 UTC (History)
0 users



Attachments
Template file to reproduce the issue (8.38 KB, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)
2014-02-20 10:39 UTC, Dirk Dittert
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Dirk Dittert 2014-02-20 10:39:52 UTC
Created attachment 31338 [details]
Template file to reproduce the issue

I'm trying to add rows to an Excel 2010 table that has no visible header rows. However, POI-3.10 throws an exception on safe (see below).

Given:
======
an Excel 2010 file that contains a table with no visible header rows

Description:
============
New rows are added to the table and the table is expanded before safe. POI tries to update the table headers in org.apache.poi.xssf.usermodel.XSSFTable#updateHeaders but fails to take into account that there are no visible table headers. 

If I use the debugger to set the value of row in XSSFTable line 295 to null, the exception can be avoided and POI produces a file that opens in Excel without errors. I guess adding a condition that checks for number of visible header rows should help. 

Please note:
This exception does not occur if the table has headers. It also does not occur if there is no other content in the file (thus the x in column C).

Demo program to reproduce (please adjust paths before running):
===============================================================
public class Demo
{
	public static void main(String[] args) throws IOException, InvalidFormatException
	{
		final Workbook workbook = WorkbookFactory.create(new File("Tabelle.xlsx"));
		final XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);

		// add some contents to table so that the table will need expansion
		Row row = sheet.getRow(0);
		Cell cell = row.createCell(0);
		cell.setCellValue("demo1");
		cell = row.createCell(1);
		cell.setCellValue("demo2");
		cell = row.createCell(2);
		cell.setCellValue("demo3");

		row = sheet.getRow(1);
		cell = row.createCell(0);
		cell.setCellValue("demo1");
		cell = row.createCell(1);
		cell.setCellValue("demo2");
		cell = row.createCell(2);
		cell.setCellValue("demo3");

		// expand table
		XSSFTable table = sheet.getTables().get(0);
		final CellReference startRef = table.getStartCellReference();
		final CellReference endRef = table.getEndCellReference();
		table.getCTTable().setRef(new CellRangeAddress(startRef.getRow(), 1, startRef.getCol(), endRef.getCol()).formatAsString());


		FileOutputStream stream = new FileOutputStream("e:\\output.xlsx");
		workbook.write(stream);
		stream.close();
	}

}

Excpetion:
==========
Exception in thread "main" org.apache.xmlbeans.impl.values.XmlValueDisconnectedException
	at org.apache.xmlbeans.impl.values.XmlObjectBase.check_orphaned(XmlObjectBase.java:1213)
	at org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTCellImpl.getF(Unknown Source)
	at org.apache.poi.xssf.usermodel.XSSFCell.getCellType(XSSFCell.java:529)
	at org.apache.poi.xssf.usermodel.XSSFCell.getRichStringCellValue(XSSFCell.java:264)
	at org.apache.poi.xssf.usermodel.XSSFCell.getStringCellValue(XSSFCell.java:251)
	at org.apache.poi.xssf.usermodel.XSSFTable.updateHeaders(XSSFTable.java:301)
	at org.apache.poi.xssf.usermodel.XSSFTable.writeTo(XSSFTable.java:84)
	at org.apache.poi.xssf.usermodel.XSSFTable.commit(XSSFTable.java:95)
	at org.apache.poi.POIXMLDocumentPart.onSave(POIXMLDocumentPart.java:322)
	at org.apache.poi.POIXMLDocumentPart.onSave(POIXMLDocumentPart.java:326)
	at org.apache.poi.POIXMLDocumentPart.onSave(POIXMLDocumentPart.java:326)
	at org.apache.poi.POIXMLDocument.write(POIXMLDocument.java:173)
	at Demo.main(PoiBug.java:49)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:601)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Comment 1 Yaniv Kunda 2014-03-19 09:31:07 UTC
I've found the problem in XSSFTable.updateHeaders() while fixing bug #56274, and found that adding row validation solves it, i.e. changing
if (row != null)
to
if (row != null && row.getCTRow().validate())

I've not included a patch, since I'm not sure it's ok to count on validate() to check this - can invalid rows still be appropriate for being a table header row?
In addition, validate() might be too heavy - but I didn't find any other way of identifying what's wrong.
Comment 2 Dominik Stadler 2014-05-16 09:03:41 UTC
The actual point where the Cell contents becomes invalid is here:

CTRowImpl(XmlComplexContentImpl).arraySetterHelper(XmlObject[], QName) line: 1149	
CTRowImpl.setCArray(CTCell[]) line: not available	
XSSFRow.onDocumentWrite() line: 466	
XSSFSheet.write(OutputStream) line: 2761	
XSSFSheet.commit() line: 2725	
XSSFSheet(POIXMLDocumentPart).onSave(Set<PackagePart>) line: 322	
XSSFWorkbook(POIXMLDocumentPart).onSave(Set<PackagePart>) line: 326	
XSSFWorkbook(POIXMLDocument).write(OutputStream) line: 173	
XSSFTestDataSamples.writeOutAndReadBack(R) line: 68	
TestXSSFCell.test56170() line: 299	

Unfortunatly the XMLBeans framework is very picky about objects being used in more than one place, sometimes setting objects to "invalid" if they are removed from some collection, while we are still using it in other places. 

Your change basically just checks if the row or any cell inside it became invalid by some prior action, so it is not a full fix, but just does not do anything any more with the cell as soon as it becomes invalid.
Comment 3 Dominik Stadler 2014-05-16 12:02:27 UTC
I did some more investigation, this is likely caused at XSSFRow.onDocumentWrite() when we call

   _row.setCArray(cArray);

We mostly pass in objects which were in this array before, but XMLBeans does not handle this case when the old array is larger than the new one.

The call gets to XmlComplexContentImpl.arraySetterHelper() where XMLBeans will release any objects that it removes from the previous array and thus objects will be disconnected if they are still referenced there afterwards, despite them still being in the new array that we set!

So it is mostly caused by how XMLBeans is handling setting these arrays, my first approach in fixing locally in POI is to keep the array of CTCells in XSSFRow._row in sync with _cells, so these cases do not appear at all.
Comment 4 Dominik Stadler 2014-05-18 19:21:27 UTC
I tried to fix this in r1595659, I could not find any other way than to copy the CTCell in this case, which is not nice and may cause a slight increase in save-time.