POI 3.17在克隆的工作表中创建单元格注释会创建不一致的xlsx

时间:2018-07-27 13:33:52

标签: apache-poi comments

我使用了cloneSheet方法在同一工作簿中复制已经包含注释的工作表。 之后,将新注释添加到此新工作表中,并保存excel。

使用Excel 365打开文件时,它抱怨 /xl/comments1.xml 并恢复了文件。 新创建的注释可用。恢复过程中将删除克隆中的注释。

打开zip文件并查看 /xl/comments1.xml ,它显示出不同之处。

Last entry is the newly created comment

这是cloneSheet方法的问题,还是Microsoft使用新方法?

1 个答案:

答案 0 :(得分:3)

apache poi项目已经成熟,但尚未完成。因此,仍然需要使用它的人必须了解所使用文件系统的内部。

那么注释如何存储在Excel的Office Open XML(*.xlsx)文件系统中?

整个文件系统是一个ZIP存档。第一张工作表的工作表数据位于该ZIP中的/xl/worksheets/sheet1.xml中。那里的XML

...
<legacyDrawing r:id="rId2"/>
...

指向第一张工作表的XML部分中具有VMLDrawing的旧式rId2

第一张纸的关系部分XML为/xl/worksheets/_rels/sheet1.xml.rels,看起来像

<Relationships>
 <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="../comments1.xml"/>
 <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="../drawings/vmlDrawing1.vml"/>
...
</Relationships>

所以rId2指向/xl/drawings/vmlDrawing1.vml,而rId3指向/xl/comments1.xml

因此vmlDrawing1.vml包含表单上注释形状的锚点,而'comments1.xml`包含注释内容。

现在XSSFWorkbook的方法public XSSFSheet cloneSheet(int sheetNum, String newName)在做什么?

首先,它会复制所有工作表的关系。因此,也复制了与VMLDrawingComments部分的关系。因此,如果我们要克隆第一张表,那么克隆后的/xl/worksheets/_rels/sheet2.xml.rels将具有与/xl/worksheets/_rels/sheet1.xml.rels相同的内容。

但是随后它指出:“尚不支持带有注释的克隆工作表。”并从工作表的XML中删除<legacyDrawing r:id="rId2"/>。但是以前的复制关系不会被删除。

因此,我们得到了一个克隆的工作表,该工作表中没有链接注释,但与注释及其形状相关。

如果我们现在在该克隆工作表中创建新注释,那么还将在/xl/drawings/vmlDrawing2.vml中创建新的/xl/worksheets/_rels/sheet2.xml.rels,包括其关系。因此,在此之后,我们有一个/xl/worksheets/_rels/sheet2.xml.rels指向/xl/drawings/vmlDrawing1.vml/xl/drawings/vmlDrawing2.vml。但这是不允许的,因此Excel在打开时会引发错误并建议修复。

此外,新创建的注释存储在/xl/comments1.xml中,这也是错误的,因为每个工作表都需要自己的注释部分。发生这种情况是因为在克隆XSSFSheet时,private CommentsTable sheetComments字段也被克隆了,并且现在包含源工作表的旧注释表。

因此,为了能够在克隆的表单中创建注释,我们需要消除错误的关系,还需要消除CommentsTable字段sheetComments中的错误XSSFSheet。 / p>

示例:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;

import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.POIXMLDocumentPart.RelationPart;

import java.io.FileInputStream;
import java.io.FileOutputStream;

import java.lang.reflect.Field;

class ExcelCloneSheetHavingComments {

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

  Workbook workbook = WorkbookFactory.create(new FileInputStream("ExcelHavingComments.xlsx"));

  Sheet sheetClone = workbook.cloneSheet(0);
  workbook.setSheetName(workbook.getSheetIndex(sheetClone), "Cloned first Sheet");

  if (sheetClone instanceof XSSFSheet) {
   XSSFSheet xssfSheet = (XSSFSheet)sheetClone;

   // get rid of the wrong relations
   for (POIXMLDocumentPart.RelationPart relationPart : xssfSheet.getRelationParts()) {
    if (relationPart.getDocumentPart() instanceof org.apache.poi.xssf.usermodel.XSSFVMLDrawing
     || relationPart.getDocumentPart() instanceof org.apache.poi.xssf.model.CommentsTable) {
     relationPart.getRelationship().getSource().removeRelationship(relationPart.getRelationship().getId());
    } 
   }

   // get rid of the wrong org.apache.poi.xssf.model.CommentsTable
   Field sheetComments = XSSFSheet.class.getDeclaredField("sheetComments"); 
   sheetComments.setAccessible(true); 
   sheetComments.set(xssfSheet, null);
  }  


  Drawing drawing = sheetClone.createDrawingPatriarch();
  Comment comment = drawing.createCellComment(drawing.createAnchor(0, 0, 0, 0, 2, 1, 4, 4));
  comment.setString(new XSSFRichTextString("Comment in Cell C2 in cloned sheet"));


  workbook.write(new FileOutputStream("CopyOfExcelHavingComments.xlsx"));
  workbook.close();

 }
}

这不会复制源工作表中的注释。但是,当然现在也可以在源工作表中使用Sheet.getCellComments,然后在克隆工作表中使用Drawing.createCellComment