2017-09-21 2 views
0

Wir versuchen, ein Diagramm aus einer PowerPoint-Folie mit Apache POI 3.16 zu entfernen, aber wir haben Schwierigkeiten.Entfernen von Diagramm aus PowerPoint Folie mit Apache POI

Unser Code führt die folgenden Schritte aus:

  1. Öffnen Sie eine vorhandene Powerpoint-Dokument (ein Vorlagendokument)
  2. Hinzufügen und Entfernen von Folien
  3. aktualisieren Charts in vorhandenen Folien

Dies funktioniert fein.

Irgendwann müssen wir ein Diagramm von einer bestimmten Folie entfernen. Hier ist unser Versuch:

OPCPackage pkg = ppt.getPackage(); 

String chartRelationId = slide.getRelationId(chart); 
pkg.removeRelationship(chartRelationId); 

pkg.removePart(chart.getPackagePart()); 

Der pkg.removePart() Aufruf scheint zu funktionieren, aber das endgültige Powerpoint-Dokument auf der Festplatte zu schreiben schlägt mit Ausnahme von selbst, dass die Teil-Datei kann nicht entfernt werden (vermutlich, weil wir es bereits gelöscht).

Der Aufruf pkg.removeRelationship() löst auch eine Ausnahme beim Schreiben des Dokuments auf die Festplatte, die besagt, dass core.xml bereits existiert.

Ist es möglich, ein Diagramm aus einer PowerPoint-Folie mit Apache POI zu entfernen? Wenn das so ist, wie?

Antwort

2

Da XSLFChart im @Beta-Zustand ist, gibt es bis jetzt keine explizite Shape für ein Diagramm. Mit apache poi können wir nur XSLFGraphicFrame s erhalten, die Diagramme enthalten. Wenn Sie jedoch eine XSLFGraphicFrame von der Folie entfernen, werden nicht alle zugehörigen Diagrammteile entfernt. Also das Entfernen der zugehörigen Chart Teile von oben nach unten, bedeutet von POIXMLDocumentPart Ebene bis PackagePart Ebene ist nicht implementiert, bis jetzt. Und da alle relevanten Methoden in POIXMLDocumentPart geschützt sind und die XSLFChart selbst endgültig ist, gibt es nicht wirklich eine einfache Möglichkeit zu umgehen.

Der folgende Code zeigt das Problem. Es ist als solches kommentiert.

Der Code entfernt alle Diagramme von der ersten Folie und entfernt alle Beziehungen und zugehörige Teile, die lauten würden: /ppt/embeddings/Microsoft_Excel_WorksheetN.xlsx, /ppt/charts/colorsN.xml und /ppt/charts/styleN.xml. Nur /ppt/charts/chartN.xml kann nicht entfernt werden, da es kommentiert wird.

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

Nach den PPTWithChartsNew.pptxPowerPoint mit Öffnen und dann zu speichern, die nicht benötigten /ppt/charts/styleN.xml Teile sind zu entfernen, da es keine weiteren Beziehungen zu ihnen sind.


bearbeiten 24. September 2017:

eine Lösung gefunden Reflexion verwendet wird. Wie gesagt, das Entfernen der zugehörigen Chart-Teile muss von oben nach unten erfolgen, dh von POIXMLDocumentPart Level bis PackagePart Level. Und da POIXMLDocumentPart.removeRelation geschützt ist, müssen wir dies mit Reflektion tun.

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(); 
} 
} 
+0

Es funktioniert wie ein Charme, danke. –