2017-09-21 2 views
0

Nous essayons de supprimer un graphique d'une diapositive PowerPoint à l'aide d'Apache POI 3.16, mais nous rencontrons des difficultés.Suppression d'un graphique de la diapositive PowerPoint avec POI Apache

Notre code effectue les étapes suivantes:

  1. Ouvrir un document PowerPoint existant (un modèle de document)
  2. Ajouter et supprimer des diapositives
  3. des cartes de mise à jour dans les diapositives existantes

Ce works bien.

À un certain point, nous devons supprimer un graphique d'une diapositive donnée. Voici notre tentative:

OPCPackage pkg = ppt.getPackage(); 

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

pkg.removePart(chart.getPackagePart()); 

L'appel pkg.removePart() semble fonctionner, mais la rédaction du document PowerPoint finale sur le disque échoue avec une exception en disant que ne pouvait pas être supprimé le fichier partiel (sans doute parce que nous avons déjà supprimé).

L'appel pkg.removeRelationship() déclenche également une exception lors de l'écriture du document sur le disque indiquant que core.xml existe déjà.

Est-il possible de supprimer un graphique d'une diapositive PowerPoint avec Apache POI? Si c'est le cas, comment?

Répondre

2

Puisque XSLFChart est dans l'état @Beta, il n'y a pas de Shape explicite pour un graphique jusqu'à maintenant. Donc, en utilisant apache poi nous pouvons seulement obtenir XSLFGraphicFrame qui contiennent des graphiques. Mais supprimer un XSLFGraphicFrame de la diapositive ne supprimera pas toutes les parties de graphique connexes aussi. Ainsi, la suppression des parties de graphique connexes de haut en bas, signifie POIXMLDocumentPart niveau jusqu'à PackagePart niveau n'est pas mis en œuvre jusqu'à maintenant. Et puisque toutes les méthodes pertinentes dans POIXMLDocumentPart sont protégées et le XSLFChart lui-même est final il n'y a pas vraiment une possibilité facile de contourner.

Le code suivant indique le problème. Il est commenté en tant que tel.

Le code supprime tous les graphiques de la première diapositive et supprime toutes les relations et les parties connexes qui seraient: /ppt/embeddings/Microsoft_Excel_WorksheetN.xlsx, /ppt/charts/colorsN.xml et /ppt/charts/styleN.xml. Seul le /ppt/charts/chartN.xml ne peut pas être supprimé, car il est commenté.

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

Après avoir ouvert le PPTWithChartsNew.pptx en utilisant PowerPoint et l'enregistrer puis, on élimine les parties inutiles /ppt/charts/styleN.xml aussi, car il n'y a plus de relations pour eux.


Modifier 24 septembre 2017:

trouvé une solution utilisant la réflexion. Comme dit, le retrait des parties de graphique connexes doit être descendant, signifie POIXMLDocumentPart niveau jusqu'à PackagePart niveau. Et puisque POIXMLDocumentPart.removeRelation est protégé, nous avons besoin de le faire en utilisant la réflexion.

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

Cela fonctionne comme un charme, merci. –