POI 3.17 在克隆的 sheet 中创建单元格注释会创建不一致的 xlsx
POI 3.17 creating Cell Comments in a cloned sheet creates inconsistent xlsx
我使用 cloneSheet
方法在已包含注释的同一工作簿中复制了一个 sheet。
之后将新评论添加到这个新 sheet 并保存 excel。
用Excel 365打开文件时,提示/xl/comments1。xml并恢复了文件。
新创建的评论可用。克隆的评论在恢复过程中被删除。
打开 zip 文件并查看 /xl/comments1。xml,它显示出差异。
这是 cloneSheet
方法的问题还是 Microsoft 使用了新方法?
尽管 apache poi
项目已经成熟,但它还远未完成。因此,需要使用它的人必须了解所用文件系统的内部结构。
那么评论是如何存储在 Excel 的 Office Open XML (*.xlsx
) 文件系统中的?
整个文件系统是一个 ZIP 压缩包。第一个 sheet 的 sheet 数据位于该 ZIP 中的 /xl/worksheets/sheet1.xml
中。 XML那里有
...
<legacyDrawing r:id="rId2"/>
...
指向遗留 VMLDrawing
在第一个 sheet.
的关系部分 XML 中有 rId2
第一个 sheet 的关系部分 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
包含 sheet 上评论形状的锚点,而 'comments1.xml` contains the comments' 内容。
现在 XSSFWorkbook
的方法 public XSSFSheet cloneSheet(int sheetNum, String newName) 在做什么?
首先,它复制所有sheet的关系。因此,与 VMLDrawing
和 Comments
部分的关系也被复制。因此,如果我们首先克隆 sheet,那么在克隆后 /xl/worksheets/_rels/sheet2.xml.rels
将具有与 /xl/worksheets/_rels/sheet1.xml.rels
相同的内容。
但随后它声明:"Cloning sheets with comments is not yet supported." 并从 sheet 的 XML 中删除了 <legacyDrawing r:id="rId2"/>
。但是之前复制的关系并没有删除。
因此,我们得到了一个克隆的 sheet,在 sheet 中没有链接评论,但与评论及其形状集有关系。
如果我们现在在克隆的 sheet 中创建新评论,那么也会创建新的 /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
中,这也是错误的,因为每个 sheet 都需要自己的评论部分。发生这种情况是因为在克隆 XSSFSheet
时,字段 private CommentsTable sheetComments
也被克隆并且现在包含源 sheet 的旧注释 table。
因此,为了能够在克隆的 sheet 中创建评论,我们需要摆脱错误的关系,并摆脱 sheetComments
字段中错误的 CommentsTable
XSSFSheet
.
示例:
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 复制评论。但是当然现在也可以在源 sheet 中使用 Sheet.getCellComments 然后在克隆的 sheet.
中使用 Drawing.createCellComment
我使用 cloneSheet
方法在已包含注释的同一工作簿中复制了一个 sheet。
之后将新评论添加到这个新 sheet 并保存 excel。
用Excel 365打开文件时,提示/xl/comments1。xml并恢复了文件。 新创建的评论可用。克隆的评论在恢复过程中被删除。
打开 zip 文件并查看 /xl/comments1。xml,它显示出差异。
这是 cloneSheet
方法的问题还是 Microsoft 使用了新方法?
尽管 apache poi
项目已经成熟,但它还远未完成。因此,需要使用它的人必须了解所用文件系统的内部结构。
那么评论是如何存储在 Excel 的 Office Open XML (*.xlsx
) 文件系统中的?
整个文件系统是一个 ZIP 压缩包。第一个 sheet 的 sheet 数据位于该 ZIP 中的 /xl/worksheets/sheet1.xml
中。 XML那里有
...
<legacyDrawing r:id="rId2"/>
...
指向遗留 VMLDrawing
在第一个 sheet.
rId2
第一个 sheet 的关系部分 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
包含 sheet 上评论形状的锚点,而 'comments1.xml` contains the comments' 内容。
现在 XSSFWorkbook
的方法 public XSSFSheet cloneSheet(int sheetNum, String newName) 在做什么?
首先,它复制所有sheet的关系。因此,与 VMLDrawing
和 Comments
部分的关系也被复制。因此,如果我们首先克隆 sheet,那么在克隆后 /xl/worksheets/_rels/sheet2.xml.rels
将具有与 /xl/worksheets/_rels/sheet1.xml.rels
相同的内容。
但随后它声明:"Cloning sheets with comments is not yet supported." 并从 sheet 的 XML 中删除了 <legacyDrawing r:id="rId2"/>
。但是之前复制的关系并没有删除。
因此,我们得到了一个克隆的 sheet,在 sheet 中没有链接评论,但与评论及其形状集有关系。
如果我们现在在克隆的 sheet 中创建新评论,那么也会创建新的 /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
中,这也是错误的,因为每个 sheet 都需要自己的评论部分。发生这种情况是因为在克隆 XSSFSheet
时,字段 private CommentsTable sheetComments
也被克隆并且现在包含源 sheet 的旧注释 table。
因此,为了能够在克隆的 sheet 中创建评论,我们需要摆脱错误的关系,并摆脱 sheetComments
字段中错误的 CommentsTable
XSSFSheet
.
示例:
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 复制评论。但是当然现在也可以在源 sheet 中使用 Sheet.getCellComments 然后在克隆的 sheet.
中使用Drawing.createCellComment