我们正在尝试使用Apache POI 3.16从PowerPoint幻灯片中删除图表,但我们遇到了困难。
我们的代码执行以下步骤:
这很好用。
在某些时候,我们需要从给定幻灯片中删除图表。这是我们的尝试:
OPCPackage pkg = ppt.getPackage();
String chartRelationId = slide.getRelationId(chart);
pkg.removeRelationship(chartRelationId);
pkg.removePart(chart.getPackagePart());
pkg.removePart()
调用似乎有效但将最终的PowerPoint文档写入磁盘失败,但有一个例外,即无法删除部分文件(可能是因为我们已将其删除)。
pkg.removeRelationship()
调用还会在将文档写入磁盘时触发异常,该异常表示core.xml
已存在。
是否可以使用Apache POI从PowerPoint幻灯片中删除图表?如果是这样,怎么样?
答案 0 :(得分:2)
由于XSLFChart
处于@Beta状态,因此到目前为止,图表没有明确的Shape
。因此,使用apache poi
我们只能得到包含图表的XSLFGraphicFrame
个。但是从幻灯片中删除XSLFGraphicFrame
也不会删除所有相关的图表部分。因此,自上而下删除相关图表部分意味着从POIXMLDocumentPart
级别降至PackagePart
级别直到现在才实现。由于POIXMLDocumentPart
中的所有相关方法都受到保护且XSLFChart
本身是最终的,因此无法轻松解决问题。
下面的代码显示了问题。它被评论为。
该代码会删除第一张幻灯片中的所有图表,并删除所有关系和相关部分:/ppt/embeddings/Microsoft_Excel_WorksheetN.xlsx
,/ppt/charts/colorsN.xml
和/ppt/charts/styleN.xml
。只有/ppt/charts/chartN.xml
无法删除,因为它已被评论。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.sl.usermodel.*;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.xmlbeans.XmlObject;
import java.util.Map;
import java.util.HashMap;
import java.util.regex.Pattern;
public class ReadPPTRemoveChart {
public static void main(String[] args) throws Exception {
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("PPTWithCharts.pptx"));
XSLFSlide slide = slideShow.getSlides().get(0);
Map<String, XSLFGraphicFrame> chartFramesToRemove = new HashMap<>();
for (XSLFShape shape : slide.getShapes()) {
if (shape instanceof XSLFGraphicFrame) {
XSLFGraphicFrame graphicframe = (XSLFGraphicFrame)shape;
XmlObject xmlobject = graphicframe.getXmlObject();
XmlObject[] graphics = xmlobject.selectPath(
"declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' " +
".//a:graphic");
if (graphics.length > 0) { //we have a XSLFGraphicFrame containing a:graphic
XmlObject graphic = graphics[0];
XmlObject[] charts = graphic.selectPath(
"declare namespace c='http://schemas.openxmlformats.org/drawingml/2006/chart' " +
".//c:chart");
if (charts.length > 0) { //we have a XSLFGraphicFrame containing c:chart
XmlObject chart = charts[0];
String rid = chart.selectAttribute(
"http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id")
.newCursor().getTextValue();
chartFramesToRemove.put(rid, graphicframe);
}
}
}
}
PackagePart slidepart = slide.getPackagePart();
OPCPackage opcpackage = slideShow.getPackage();
for (String rid : chartFramesToRemove.keySet()) {
//at frist remove the XSLFGraphicFrame
XSLFGraphicFrame chartFrame = chartFramesToRemove.get(rid);
slide.removeShape(chartFrame);
//Here is the problem in my opinion. This **should** remove all related parts too.
//But since XSLFChart is @Beta, it does not.
//So we try doing removing the related parts manually.
//we get the PackagePart of the chart
PackageRelationship relship = slidepart.getRelationships().getRelationshipByID(rid);
PackagePart chartpart = slidepart.getRelatedPart(relship);
//now we get and remove all the relations and related PackageParts from this chartpart
//this are /ppt/embeddings/Microsoft_Excel_WorksheetN.xlsx, /ppt/charts/colorsN.xml
//and /ppt/charts/styleN.xml
for (PackageRelationship chartrelship : chartpart.getRelationships()) {
String partname = chartrelship.getTargetURI().toString();
PackagePart part = opcpackage.getPartsByName(Pattern.compile(partname)).get(0);
opcpackage.removePart(part);
chartpart.removeRelationship(chartrelship.getId());
}
//this works
//now we **should** be able removing the relationship to the chartpart from the slide too
//but this seems not to be possible
//doing this on PackagePart level works:
slidepart.removeRelationship(rid);
for (PackageRelationship sliderelship : slidepart.getRelationships()) {
System.out.println("rel PP level: " + sliderelship.getTargetURI().toString());
}
//all relationships to /ppt/charts/chartN.xml are removed
//but on POIXMLDocumentPart level this has no effect
for (POIXMLDocumentPart sliderelpart : slide.getRelations()) {
System.out.println("rel POIXML level: " + sliderelpart.getPackagePart().getPartName());
}
//relationships to /ppt/charts/chartN.xml are **not** removed
//So we cannot remove the chartpart.
//If we would do this, then while slideShow.write the
//org.apache.poi.xslf.usermodel.XSLFChart.commit in XSLFChart.java fails
//because after removing the PackagePart is absent but the relation is still there.
//opcpackage.removePart(chartpart);
}
slideShow.write(new FileOutputStream("PPTWithChartsNew.pptx"));
slideShow.close();
}
}
使用PPTWithChartsNew.pptx
打开PowerPoint
并保存之后,由于没有更多关系,所以删除了不必要的/ppt/charts/styleN.xml
部分。
2017年9月24日编辑:
使用反射找到解决方案。如上所述,删除相关图表部分需要自上而下,意味着从POIXMLDocumentPart
级别到PackagePart
级别。由于POIXMLDocumentPart.removeRelation
受到保护,我们需要使用反射来做到这一点。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.sl.usermodel.*;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.xmlbeans.XmlObject;
import java.util.Map;
import java.util.HashMap;
import java.util.regex.Pattern;
import java.lang.reflect.Method;
public class ReadPPTRemoveChart {
public static void main(String[] args) throws Exception {
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("PPTWithCharts.pptx"));
XSLFSlide slide = slideShow.getSlides().get(0);
Map<String, XSLFGraphicFrame> chartFramesToRemove = new HashMap<>();
for (XSLFShape shape : slide.getShapes()) {
if (shape instanceof XSLFGraphicFrame) {
XSLFGraphicFrame graphicframe = (XSLFGraphicFrame)shape;
XmlObject xmlobject = graphicframe.getXmlObject();
XmlObject[] graphics = xmlobject.selectPath(
"declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' " +
".//a:graphic");
if (graphics.length > 0) { //we have a XSLFGraphicFrame containing a:graphic
XmlObject graphic = graphics[0];
XmlObject[] charts = graphic.selectPath(
"declare namespace c='http://schemas.openxmlformats.org/drawingml/2006/chart' " +
".//c:chart");
if (charts.length > 0) { //we have a XSLFGraphicFrame containing c:chart
XmlObject chart = charts[0];
String rid = chart.selectAttribute(
"http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id")
.newCursor().getTextValue();
chartFramesToRemove.put(rid, graphicframe);
}
}
}
}
PackagePart slidepart = slide.getPackagePart();
OPCPackage opcpackage = slideShow.getPackage();
for (String rid : chartFramesToRemove.keySet()) {
//at frist remove the XSLFGraphicFrame
XSLFGraphicFrame chartFrame = chartFramesToRemove.get(rid);
slide.removeShape(chartFrame);
//Here is the problem in my opinion. This **should** remove all related parts too.
//But since XSLFChart is @Beta, it does not.
//So we try doing removing the related parts manually.
//we get the PackagePart of the chart
PackageRelationship relship = slidepart.getRelationships().getRelationshipByID(rid);
PackagePart chartpart = slidepart.getRelatedPart(relship);
//now we get and remove all the relations and related PackageParts from this chartpart
//this are /ppt/embeddings/Microsoft_Excel_WorksheetN.xlsx, /ppt/charts/colorsN.xml
//and /ppt/charts/styleN.xml
for (PackageRelationship chartrelship : chartpart.getRelationships()) {
String partname = chartrelship.getTargetURI().toString();
PackagePart part = opcpackage.getPartsByName(Pattern.compile(partname)).get(0);
opcpackage.removePart(part);
chartpart.removeRelationship(chartrelship.getId());
}
//now we remove the chart part from the slide part
//We need doing this on POIXMLDocumentPart level.
//Since POIXMLDocumentPart.removeRelation is protected, we need doing this using reflection
XSLFChart chart = (XSLFChart)slide.getRelationById(rid);
Method removeRelation = POIXMLDocumentPart.class.getDeclaredMethod("removeRelation", POIXMLDocumentPart.class);
removeRelation.setAccessible(true);
removeRelation.invoke(slide, chart);
}
slideShow.write(new FileOutputStream("PPTWithChartsNew.pptx"));
slideShow.close();
}
}