更新时间:2023-02-14 09:14:56
由于XSLFChart
处于@Beta状态,因此到目前为止,图表没有明确的Shape
.因此,使用apache poi
只能获取包含图表的XSLFGraphicFrame
.但是从幻灯片中删除XSLFGraphicFrame
不会同时删除所有相关的图表部分.因此,自上而下删除相关图表部分意味着从POIXMLDocumentPart
级别降低到PackagePart
级别直到现在都没有实现.而且,由于POIXMLDocumentPart
中的所有相关方法都受到保护,并且XSLFChart
本身是最终的,因此解决此问题的可能性并不大.
Since XSLFChart
is in @Beta state, there is not a explicit Shape
for a chart until now. So using apache poi
we can only get XSLFGraphicFrame
s which are containing charts. But removing a XSLFGraphicFrame
from the slide will not removing all related chart parts too. So the removing the related chart parts top-down, means from POIXMLDocumentPart
level down to PackagePart
level is not implemented until now. And since all relevant methods in POIXMLDocumentPart
are protected and the XSLFChart
itself is final there is not really a easy possibility to work around.
以下代码显示了问题.这样评论.
The folowing code shows the problem. It is commented as such.
该代码从第一张幻灯片中删除了所有图表,并删除了所有的关系和相关部分,例如:/ppt/embeddings/Microsoft_Excel_WorksheetN.xlsx
,/ppt/charts/colorsN.xml
和/ppt/charts/styleN.xml
.如注释所示,仅/ppt/charts/chartN.xml
无法删除.
The code removes all charts from the first slide and removes all relationships and related parts that would be: /ppt/embeddings/Microsoft_Excel_WorksheetN.xlsx
, /ppt/charts/colorsN.xml
and /ppt/charts/styleN.xml
. Only /ppt/charts/chartN.xml
cannot be removed, as it is commented.
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();
}
}
使用PowerPoint
打开PPTWithChartsNew.pptx
并保存后,由于不需要的/ppt/charts/styleN.xml
部分也不再存在,因此也将其删除.
After opening the PPTWithChartsNew.pptx
using PowerPoint
and saving it then, the unnecessary /ppt/charts/styleN.xml
parts are removed too since there are no more relations to them.
编辑2017年9月24日:
Edit Sep 24 2017:
找到了使用反射的解决方案.如前所述,删除相关图表部分需要自上而下,即从POIXMLDocumentPart
级别降至PackagePart
级别.并且由于POIXMLDocumentPart.removeRelation
受保护,我们需要使用反射来完成此操作.
Found a solution using reflection. As said, the removing the related chart parts needs to be top-down, means from POIXMLDocumentPart
level down to PackagePart
level. And since POIXMLDocumentPart.removeRelation
is protected, we need doing this using reflection.
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();
}
}