| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| package org.apache.poi.xslf.usermodel; |
| |
| import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; |
| |
| import java.awt.Dimension; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| import org.apache.poi.POIXMLDocument; |
| import org.apache.poi.POIXMLDocumentPart; |
| import org.apache.poi.POIXMLException; |
| import org.apache.poi.openxml4j.exceptions.OpenXML4JException; |
| import org.apache.poi.openxml4j.opc.OPCPackage; |
| import org.apache.poi.openxml4j.opc.PackagePart; |
| import org.apache.poi.sl.usermodel.MasterSheet; |
| import org.apache.poi.sl.usermodel.PictureData.PictureType; |
| import org.apache.poi.sl.usermodel.Resources; |
| import org.apache.poi.sl.usermodel.SlideShow; |
| import org.apache.poi.util.Beta; |
| import org.apache.poi.util.IOUtils; |
| import org.apache.poi.util.Internal; |
| import org.apache.poi.util.LittleEndian; |
| import org.apache.poi.util.LittleEndianConsts; |
| import org.apache.poi.util.POILogFactory; |
| import org.apache.poi.util.POILogger; |
| import org.apache.poi.util.PackageHelper; |
| import org.apache.poi.util.Units; |
| import org.apache.xmlbeans.XmlException; |
| import org.apache.xmlbeans.XmlObject; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTNotesMasterIdList; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTNotesMasterIdListEntry; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTPresentation; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdList; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdListEntry; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMasterIdListEntry; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideSize; |
| import org.openxmlformats.schemas.presentationml.x2006.main.PresentationDocument; |
| |
| /** |
| * High level representation of a ooxml slideshow. |
| * This is the first object most users will construct whether |
| * they are reading or writing a slideshow. It is also the |
| * top level object for creating new slides/etc. |
| */ |
| @Beta |
| public class XMLSlideShow extends POIXMLDocument |
| implements SlideShow<XSLFShape,XSLFTextParagraph> { |
| private final static POILogger _logger = POILogFactory.getLogger(XMLSlideShow.class); |
| |
| private CTPresentation _presentation; |
| private List<XSLFSlide> _slides; |
| private List<XSLFSlideMaster> _masters; |
| private List<XSLFPictureData> _pictures; |
| private XSLFTableStyles _tableStyles; |
| private XSLFNotesMaster _notesMaster; |
| private XSLFCommentAuthors _commentAuthors; |
| |
| public XMLSlideShow() { |
| this(empty()); |
| } |
| |
| public XMLSlideShow(OPCPackage pkg) { |
| super(pkg); |
| |
| try { |
| if(getCorePart().getContentType().equals(XSLFRelation.THEME_MANAGER.getContentType())) { |
| rebase(getPackage()); |
| } |
| |
| //build a tree of POIXMLDocumentParts, this presentation being the root |
| load(XSLFFactory.getInstance()); |
| } catch (Exception e){ |
| throw new POIXMLException(e); |
| } |
| } |
| |
| public XMLSlideShow(InputStream is) throws IOException { |
| this(PackageHelper.open(is)); |
| } |
| |
| static OPCPackage empty() { |
| InputStream is = XMLSlideShow.class.getResourceAsStream("empty.pptx"); |
| if (is == null) { |
| throw new POIXMLException("Missing resource 'empty.pptx'"); |
| } |
| try { |
| return OPCPackage.open(is); |
| } catch (Exception e){ |
| throw new POIXMLException(e); |
| } finally { |
| IOUtils.closeQuietly(is); |
| } |
| } |
| |
| @Override |
| protected void onDocumentRead() throws IOException { |
| try { |
| PresentationDocument doc = |
| PresentationDocument.Factory.parse(getCorePart().getInputStream(), DEFAULT_XML_OPTIONS); |
| _presentation = doc.getPresentation(); |
| |
| Map<String, XSLFSlideMaster> masterMap = new HashMap<String, XSLFSlideMaster>(); |
| Map<String, XSLFSlide> shIdMap = new HashMap<String, XSLFSlide>(); |
| for (RelationPart rp : getRelationParts()) { |
| POIXMLDocumentPart p = rp.getDocumentPart(); |
| if (p instanceof XSLFSlide) { |
| shIdMap.put(rp.getRelationship().getId(), (XSLFSlide) p); |
| } else if (p instanceof XSLFSlideMaster) { |
| masterMap.put(getRelationId(p), (XSLFSlideMaster) p); |
| } else if (p instanceof XSLFTableStyles){ |
| _tableStyles = (XSLFTableStyles)p; |
| } else if (p instanceof XSLFNotesMaster) { |
| _notesMaster = (XSLFNotesMaster)p; |
| } else if (p instanceof XSLFCommentAuthors) { |
| _commentAuthors = (XSLFCommentAuthors)p; |
| } |
| } |
| |
| _masters = new ArrayList<XSLFSlideMaster>(masterMap.size()); |
| for (CTSlideMasterIdListEntry masterId : _presentation.getSldMasterIdLst().getSldMasterIdList()) { |
| XSLFSlideMaster master = masterMap.get(masterId.getId2()); |
| _masters.add(master); |
| } |
| |
| _slides = new ArrayList<XSLFSlide>(shIdMap.size()); |
| if (_presentation.isSetSldIdLst()) { |
| for (CTSlideIdListEntry slId : _presentation.getSldIdLst().getSldIdList()) { |
| XSLFSlide sh = shIdMap.get(slId.getId2()); |
| if (sh == null) { |
| _logger.log(POILogger.WARN, "Slide with r:id " + slId.getId() + " was defined, but didn't exist in package, skipping"); |
| continue; |
| } |
| _slides.add(sh); |
| } |
| } |
| } catch (XmlException e) { |
| throw new POIXMLException(e); |
| } |
| } |
| |
| @Override |
| protected void commit() throws IOException { |
| PackagePart part = getPackagePart(); |
| OutputStream out = part.getOutputStream(); |
| _presentation.save(out, DEFAULT_XML_OPTIONS); |
| out.close(); |
| } |
| |
| /** |
| * Get the document's embedded files. |
| */ |
| @Override |
| public List<PackagePart> getAllEmbedds() throws OpenXML4JException { |
| return Collections.unmodifiableList( |
| getPackage().getPartsByName(Pattern.compile("/ppt/embeddings/.*?")) |
| ); |
| } |
| |
| @Override |
| public List<XSLFPictureData> getPictureData() { |
| if(_pictures == null){ |
| List<PackagePart> mediaParts = getPackage().getPartsByName(Pattern.compile("/ppt/media/.*?")); |
| _pictures = new ArrayList<XSLFPictureData>(mediaParts.size()); |
| for(PackagePart part : mediaParts){ |
| XSLFPictureData pd = new XSLFPictureData(part); |
| pd.setIndex(_pictures.size()); |
| _pictures.add(pd); |
| } |
| } |
| return Collections.unmodifiableList(_pictures); |
| } |
| |
| /** |
| * Create a slide and initialize it from the specified layout. |
| * |
| * @param layout The layout to use for the new slide. |
| * @return created slide |
| */ |
| public XSLFSlide createSlide(XSLFSlideLayout layout) { |
| int slideNumber = 256, cnt = 1; |
| CTSlideIdList slideList; |
| if (!_presentation.isSetSldIdLst()) slideList = _presentation.addNewSldIdLst(); |
| else { |
| slideList = _presentation.getSldIdLst(); |
| for(CTSlideIdListEntry slideId : slideList.getSldIdArray()){ |
| slideNumber = (int)Math.max(slideId.getId() + 1, slideNumber); |
| cnt++; |
| } |
| |
| // Bug 55791: We also need to check that the resulting file name is not already taken |
| // this can happen when removing/adding slides |
| while(true) { |
| String slideName = XSLFRelation.SLIDE.getFileName(cnt); |
| boolean found = false; |
| for (POIXMLDocumentPart relation : getRelations()) { |
| if (relation.getPackagePart() != null && |
| slideName.equals(relation.getPackagePart().getPartName().getName())) { |
| // name is taken => try next one |
| found = true; |
| break; |
| } |
| } |
| |
| if(!found && |
| getPackage().getPartsByName(Pattern.compile(Pattern.quote(slideName))).size() > 0) { |
| // name is taken => try next one |
| found = true; |
| } |
| |
| if (!found) { |
| break; |
| } |
| cnt++; |
| } |
| } |
| |
| RelationPart rp = createRelationship( |
| XSLFRelation.SLIDE, XSLFFactory.getInstance(), cnt, false); |
| XSLFSlide slide = rp.getDocumentPart(); |
| |
| CTSlideIdListEntry slideId = slideList.addNewSldId(); |
| slideId.setId(slideNumber); |
| slideId.setId2(rp.getRelationship().getId()); |
| |
| layout.copyLayout(slide); |
| slide.addRelation(null, XSLFRelation.SLIDE_LAYOUT, layout); |
| |
| _slides.add(slide); |
| return slide; |
| } |
| |
| /** |
| * Create a blank slide using the default (first) master. |
| */ |
| @Override |
| public XSLFSlide createSlide() { |
| XSLFSlideLayout layout = _masters.get(0).getLayout(SlideLayout.BLANK); |
| if(layout == null) throw new IllegalArgumentException("Blank layout was not found"); |
| |
| return createSlide(layout); |
| } |
| |
| /** |
| * Return notes slide for the specified slide or create new if it does not exist yet. |
| */ |
| public XSLFNotes getNotesSlide(XSLFSlide slide) { |
| |
| XSLFNotes notesSlide = slide.getNotes(); |
| if (notesSlide == null) { |
| notesSlide = createNotesSlide(slide); |
| } |
| |
| return notesSlide; |
| } |
| |
| /** |
| * Create a blank notes slide. |
| */ |
| private XSLFNotes createNotesSlide(XSLFSlide slide) { |
| |
| if (_notesMaster == null) { |
| createNotesMaster(); |
| } |
| |
| Integer slideIndex = XSLFRelation.SLIDE.getFileNameIndex(slide); |
| |
| // add notes slide to presentation |
| XSLFNotes notesSlide = (XSLFNotes) createRelationship |
| (XSLFRelation.NOTES, XSLFFactory.getInstance(), slideIndex); |
| // link slide and notes slide with each other |
| slide.addRelation(null, XSLFRelation.NOTES, notesSlide); |
| notesSlide.addRelation(null, XSLFRelation.NOTES_MASTER, _notesMaster); |
| notesSlide.addRelation(null, XSLFRelation.SLIDE, slide); |
| |
| notesSlide.importContent(_notesMaster); |
| |
| return notesSlide; |
| } |
| |
| /** |
| * Create a notes master. |
| */ |
| public void createNotesMaster() { |
| RelationPart rp = createRelationship |
| (XSLFRelation.NOTES_MASTER, XSLFFactory.getInstance(), 1, false); |
| _notesMaster = rp.getDocumentPart(); |
| |
| CTNotesMasterIdList notesMasterIdList = _presentation.addNewNotesMasterIdLst(); |
| CTNotesMasterIdListEntry notesMasterId = notesMasterIdList.addNewNotesMasterId(); |
| notesMasterId.setId(rp.getRelationship().getId()); |
| |
| Integer themeIndex = 1; |
| // TODO: check if that list can be replaced by idx = Math.max(idx,themeIdx) |
| List<Integer> themeIndexList = new ArrayList<Integer>(); |
| for (POIXMLDocumentPart p : getRelations()) { |
| if (p instanceof XSLFTheme) { |
| themeIndexList.add(XSLFRelation.THEME.getFileNameIndex(p)); |
| } |
| } |
| |
| if (!themeIndexList.isEmpty()) { |
| Boolean found = false; |
| for (Integer i = 1; i <= themeIndexList.size(); i++) { |
| if (!themeIndexList.contains(i)) { |
| found = true; |
| themeIndex = i; |
| } |
| } |
| if (!found) { |
| themeIndex = themeIndexList.size() + 1; |
| } |
| } |
| |
| XSLFTheme theme = (XSLFTheme) createRelationship |
| (XSLFRelation.THEME, XSLFFactory.getInstance(), themeIndex); |
| theme.importTheme(getSlides().get(0).getTheme()); |
| |
| _notesMaster.addRelation(null, XSLFRelation.THEME, theme); |
| } |
| |
| /** |
| * Return the Notes Master, if there is one. |
| * (May not be present if no notes exist) |
| */ |
| public XSLFNotesMaster getNotesMaster() { |
| return _notesMaster; |
| } |
| |
| @Override |
| public List<XSLFSlideMaster> getSlideMasters() { |
| return _masters; |
| } |
| |
| /** |
| * Return all the slides in the slideshow |
| */ |
| public List<XSLFSlide> getSlides() { |
| return _slides; |
| } |
| |
| /** |
| * Returns the list of comment authors, if there is one. |
| * Will only be present if at least one slide has comments on it. |
| */ |
| public XSLFCommentAuthors getCommentAuthors() { |
| return _commentAuthors; |
| } |
| |
| /** |
| * |
| * @param newIndex 0-based index of the slide |
| */ |
| public void setSlideOrder(XSLFSlide slide, int newIndex){ |
| int oldIndex = _slides.indexOf(slide); |
| if(oldIndex == -1) throw new IllegalArgumentException("Slide not found"); |
| if (oldIndex == newIndex) return; |
| |
| // fix the usermodel container |
| _slides.add(newIndex, _slides.remove(oldIndex)); |
| |
| // fix ordering in the low-level xml |
| CTSlideIdList sldIdLst = _presentation.getSldIdLst(); |
| CTSlideIdListEntry[] entries = sldIdLst.getSldIdArray(); |
| CTSlideIdListEntry oldEntry = entries[oldIndex]; |
| if (oldIndex < newIndex) { |
| System.arraycopy(entries, oldIndex + 1, entries, oldIndex, newIndex - oldIndex); |
| } else { |
| System.arraycopy(entries, newIndex, entries, newIndex + 1, oldIndex - newIndex); |
| } |
| entries[newIndex] = oldEntry; |
| sldIdLst.setSldIdArray(entries); |
| } |
| |
| public XSLFSlide removeSlide(int index){ |
| XSLFSlide slide = _slides.remove(index); |
| removeRelation(slide); |
| _presentation.getSldIdLst().removeSldId(index); |
| return slide; |
| } |
| |
| @Override |
| public Dimension getPageSize(){ |
| CTSlideSize sz = _presentation.getSldSz(); |
| int cx = sz.getCx(); |
| int cy = sz.getCy(); |
| return new Dimension((int)Units.toPoints(cx), (int)Units.toPoints(cy)); |
| } |
| |
| @Override |
| public void setPageSize(Dimension pgSize){ |
| CTSlideSize sz = CTSlideSize.Factory.newInstance(); |
| sz.setCx(Units.toEMU(pgSize.getWidth())); |
| sz.setCy(Units.toEMU(pgSize.getHeight())); |
| _presentation.setSldSz(sz); |
| } |
| |
| |
| @Internal |
| public CTPresentation getCTPresentation(){ |
| return _presentation; |
| } |
| |
| /** |
| * Adds a picture to the workbook. |
| * |
| * @param pictureData The bytes of the picture |
| * @param format The format of the picture. |
| * |
| * @return the picture data |
| */ |
| public XSLFPictureData addPicture(byte[] pictureData, PictureType format) { |
| XSLFPictureData img = findPictureData(pictureData); |
| |
| if (img != null) return img; |
| |
| int imageNumber = _pictures.size(); |
| XSLFRelation relType = XSLFPictureData.getRelationForType(format); |
| if (relType == null) { |
| throw new IllegalArgumentException("Picture type "+format+" is not supported."); |
| } |
| img = (XSLFPictureData) createRelationship(relType, XSLFFactory.getInstance(), imageNumber + 1, true).getDocumentPart(); |
| img.setIndex(imageNumber); |
| _pictures.add(img); |
| try { |
| OutputStream out = img.getPackagePart().getOutputStream(); |
| out.write(pictureData); |
| out.close(); |
| } catch (IOException e) { |
| throw new POIXMLException(e); |
| } |
| |
| return img; |
| } |
| |
| |
| /** |
| * Adds a picture to the slideshow. |
| * |
| * @param is The stream to read image from |
| * @param format The format of the picture |
| * |
| * @return the picture data |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public XSLFPictureData addPicture(InputStream is, PictureType format) throws IOException |
| { |
| return addPicture(IOUtils.toByteArray(is), format); |
| } |
| |
| |
| /** |
| * Adds a picture to the presentation. |
| * |
| * @param pict The file containing the image to add |
| * @param format The format of the picture. |
| * |
| * @return the picture data |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public XSLFPictureData addPicture(File pict, PictureType format) throws IOException |
| { |
| int length = (int) pict.length(); |
| byte[] data = new byte[length]; |
| FileInputStream is = new FileInputStream(pict); |
| try { |
| IOUtils.readFully(is, data); |
| } finally { |
| is.close(); |
| } |
| return addPicture(data, format); |
| } |
| |
| |
| /** |
| * check if a picture with this picture data already exists in this presentation |
| * |
| * @param pictureData The picture data to find in the SlideShow |
| * @return {@code null} if picture data is not found in this slideshow |
| * @since 3.15 beta 2 |
| */ |
| @Override |
| public XSLFPictureData findPictureData(byte[] pictureData) { |
| long checksum = IOUtils.calculateChecksum(pictureData); |
| byte cs[] = new byte[LittleEndianConsts.LONG_SIZE]; |
| LittleEndian.putLong(cs,0,checksum); |
| |
| for(XSLFPictureData pic : getPictureData()){ |
| if(Arrays.equals(pic.getChecksum(), cs)) { |
| return pic; |
| } |
| } |
| return null; |
| } |
| |
| |
| /** |
| * Scan the master slides for the first slide layout with the given name. |
| * |
| * @param name The layout name (case-insensitive). Cannot be null. |
| * @return the first layout found or null on failure |
| */ |
| public XSLFSlideLayout findLayout(String name) { |
| for (XSLFSlideMaster master : getSlideMasters()) { |
| XSLFSlideLayout layout = master.getLayout(name); |
| if (layout != null) { |
| return layout; |
| } |
| } |
| return null; |
| } |
| |
| |
| public XSLFTableStyles getTableStyles(){ |
| return _tableStyles; |
| } |
| |
| CTTextParagraphProperties getDefaultParagraphStyle(int level) { |
| XmlObject[] o = _presentation.selectPath( |
| "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' " + |
| "declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' " + |
| ".//p:defaultTextStyle/a:lvl" +(level+1)+ "pPr"); |
| if(o.length == 1){ |
| return (CTTextParagraphProperties)o[0]; |
| } |
| return null; |
| } |
| |
| @Override |
| public MasterSheet<XSLFShape,XSLFTextParagraph> createMasterSheet() throws IOException { |
| // TODO: implement! |
| throw new UnsupportedOperationException(); |
| } |
| |
| public Resources getResources() { |
| // TODO: implement! |
| throw new UnsupportedOperationException(); |
| } |
| } |