繁体   English   中英

使用Apache POI从PowerPoint幻灯片中删除图表

[英]Removing chart from PowerPoint slide with Apache POI

我们正在尝试使用Apache POI 3.16从PowerPoint幻灯片中删除图表,但遇到了困难。

我们的代码执行以下步骤:

  1. 打开现有的PowerPoint文档(模板文档)
  2. 添加和删​​除幻灯片
  3. 更新现有幻灯片中的图表

这很好。

在某些时候,我们需要从给定的幻灯片中删除图表。 这是我们的尝试:

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幻灯片中删除图表吗? 如果是这样,怎么办?

由于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();
 }
}

使用PowerPoint打开PPTWithChartsNew.pptx并将其保存后,由于不需要更多的/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();
 }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM