All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.dam.handler.standard.keynote.KeynotePresentation Maven / Gradle / Ivy

package com.day.cq.dam.handler.standard.keynote;

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.SAXException;

import com.day.cq.dam.commons.xml.DocumentBuilderFactoryProvider;
import com.day.image.Layer;

/**
 * The Keynote presentation is a zip file in the end. the zip file
 * contains xml files describing the structure as well as thumbnails for each slide.
 */
public class KeynotePresentation {
    /**
     * the default logger
     */
    private static final Logger log = LoggerFactory.getLogger(KeynotePresentation.class);

	public static final String NAMESPACES_KEYNOTE2 = "http://developer.apple.com/namespaces/keynote2";
	public static final String NAMESAPCES_SF = "http://developer.apple.com/namespaces/sf";
	public static final String NAMESAPCES_SFA = "http://developer.apple.com/namespaces/sfa";
	
	private static final int BUFFER = 2048;

	private Document document;
	private byte[] quicklookThumbnail;
	private Map thumbnails;
	private Map resources;
	private int width;
	private int height;
	private List slides;
	private LinkedHashMap masters;

    /**
     * The constructor expects the InputStream from the actual
     * presentation.
     *
     * @param is InputStream from the actual presentation
     *
     * @throws IOException
     * @throws SAXException
     * @throws ParserConfigurationException
     */
	public KeynotePresentation(InputStream is) throws IOException,
			SAXException, ParserConfigurationException {
		ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));

		this.thumbnails = new HashMap();
		this.resources = new HashMap();
		ZipEntry entry;
		while ((entry = zis.getNextEntry()) != null) {
			addEntry(entry, zis);
		}
	}

    /**
     * Get width
     *
     * @return width
     */
    public int getWidth() {
		if (width < 1) {
			extractSize();
		}
		return width;
	}

    /**
     * Get height
     *
     * @return height
     */
	public int getHeight() {
		if (height < 1) {
			extractSize();
		}
		return height;
	}

    /**
     * Get presentation thumbnail
     *
     * @return thumbnail
     *
     * @throws IOException
     */
    public BufferedImage getThumbnail() throws IOException {
		if (quicklookThumbnail != null && quicklookThumbnail.length != 0) {
			return new Layer(new ByteArrayInputStream(this.quicklookThumbnail)).getImage();
		} else {
			BufferedImage slidethumb = null;
			for (KeynoteSlide slide : getSlides()) {
				if (!slide.isHidden()) {
					slidethumb = slide.getThumbnail();
				}
				if (slidethumb!=null) {
					return slidethumb;
				}
			}
		}
		return null;
	}

    /**
     * Get the list of slides
     *
     * @return list of slides
     */
    public List getSlides() {
		if (this.slides==null) {
			extractSlides();
		}
		return new ArrayList(this.slides);
	}

    /**
     * Removes the given slide from the presentation
     *
     * @param slide {@link com.day.cq.dam.handler.standard.keynote.KeynoteSlide Slide}
     * to be removed
     */
	public void removeSlide(KeynoteSlide slide) {
		slide.removeElement();
		this.slides.remove(slide);
	}

    /**
     * Removes the given slide master
     *
     * @param master {@link KeynoteMasterSlide Master Slide}
     * to be removed
     */
	public void removeMaster(KeynoteMasterSlide master) {
		master.removeElement();
		this.slides.remove(master);
	}

    /**
	 * Removes master slides that are no longer in use.
	 */
	public void clearUnusedMasters() {
		Set used = new HashSet();
		for (KeynoteSlide slide : this.slides) {
			used.add(slide.getMaster());
		}
		for (Object unused : CollectionUtils.subtract(this.masters.values(), used)) {
			KeynoteMasterSlide unusedslide = (KeynoteMasterSlide) unused;
			unusedslide.removeElement();
			this.masters.values().remove(unusedslide);
		}
	}

    /**
	 * Removes resources that are no longer in use.
	 */
	public void clearUnusedResources() {
		Set used = new HashSet();
		for (KeynoteBaseSlide slide : this.slides) {
			used.addAll(slide.getResources());
		}
		for (KeynoteBaseSlide slide : this.masters.values()) {
			used.addAll(slide.getResources());
		}
		for (Object unused : CollectionUtils.subtract(this.resources.keySet(), used)) {
			this.resources.remove(unused);
		}
	}

    /**
     * Save modified presentation
     *
     * @param out {@link OutputStream} used for save "destination"
     *
     * @throws IOException
     */
	public void save(OutputStream out) throws IOException {
		clearUnusedMasters();
		clearUnusedResources();

		ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(out));
		for (String resource : this.resources.keySet()) {
			ZipEntry entry = new ZipEntry(resource);
			InputStream origin = new ByteArrayInputStream(this.resources.get(resource).toByteArray());
			zos.putNextEntry(entry);
			int count;
			byte[] data = new byte[BUFFER];
			while((count = origin.read(data, 0, BUFFER)) != -1) {
			   zos.write(data, 0, count);
			}
		}
		for (String resource : this.thumbnails.keySet()) {
			ByteArrayOutputStream baos = this.thumbnails.get(resource);
			if (baos!=null) {
				ZipEntry entry = new ZipEntry(resource);
				InputStream origin = new ByteArrayInputStream(baos.toByteArray());
				zos.putNextEntry(entry);
				int count;
				byte[] data = new byte[BUFFER];
				while((count = origin.read(data, 0, BUFFER)) != -1) {
					zos.write(data, 0, count);
				}
			}
		}
        
		// write index.apxl
        ZipEntry entry = new ZipEntry("index.apxl");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            TransformerFactory.newInstance().newTransformer().transform(new DOMSource(this.document), new StreamResult(baos));
            InputStream origin = new ByteArrayInputStream(baos.toByteArray());
            zos.putNextEntry(entry);
            int count;
            byte[] data = new byte[BUFFER];
            while((count = origin.read(data, 0, BUFFER)) != -1) {
                zos.write(data, 0, count);
            }
        } catch (TransformerConfigurationException e) {
            throw new IOException(e.getMessage());
        } catch (TransformerException e) {
            throw new IOException(e.getMessage());
        } catch (TransformerFactoryConfigurationError e) {
            throw new IOException(e.getMessage());
        }
		zos.close();
	}

	public InputStream getResource(String path) {
		if (resources.containsKey(path)) {
			return new ByteArrayInputStream(resources.get(path).toByteArray());
		}
		if (thumbnails.containsKey(path)) {
			return new ByteArrayInputStream(thumbnails.get(path).toByteArray());
		}
		return null;
	}

	public Collection getMasters() {
		if (this.masters==null) extractMasters();
		return new ArrayList(this.masters.values());
	}

	public KeynoteMasterSlide getMaster(String masterid) {
		//slides can have references to a master slide
		if (this.masters==null) {
			extractMasters();
		}
		return this.masters.get(masterid);
	}

	// ------------< helpers >--------------------------------------------------
	private void extractMasters() {
		this.masters = new LinkedHashMap();
		DocumentTraversal dt = (DocumentTraversal) this.document;
        if (dt == null) {
            log.warn("Document is null. Cannot extract masters");
        } else {
            NodeIterator nit = dt.createNodeIterator(document, NodeFilter.SHOW_ELEMENT,
                    new ElementFilter(NodeFilter.FILTER_REJECT)
                        .skipNodes(NAMESPACES_KEYNOTE2, "presentation", "theme-list", "theme", "master-slides")
                        .acceptNodes(NAMESPACES_KEYNOTE2, "master-slide")
            , true);
            Element next;
            while ((next = (Element) nit.nextNode())!=null) {
                KeynoteMasterSlide keynoteMasterSlide = new KeynoteMasterSlide(next, this);
                this.masters.put(keynoteMasterSlide.getId(), keynoteMasterSlide);
            }
        }
	}
	
	private void extractSlides() {
		this.slides = new ArrayList();
		
		DocumentTraversal dt = (DocumentTraversal) this.document;
        if (dt == null) {
            log.warn("Document is null. Cannot extract slides");
        } else {
            NodeIterator nit = dt.createNodeIterator(document, NodeFilter.SHOW_ELEMENT,
                    new ElementFilter(NodeFilter.FILTER_REJECT)
                        .skipNodes(NAMESPACES_KEYNOTE2, "presentation", "slide-list")
                        .acceptNodes(NAMESPACES_KEYNOTE2, "slide")
            , true);
            Element next;
            while ((next = (Element) nit.nextNode())!=null) {
                this.slides.add(new KeynoteSlide(next, this));
            }
        }
	}
	
	private void extractSize() {
		DocumentTraversal dt = (DocumentTraversal) this.document;
        if (dt == null) {
            log.warn("Document is null. Cannot extract size");
        } else {
            NodeIterator nit = dt.createNodeIterator(document, NodeFilter.SHOW_ELEMENT,
                    new ElementFilter(NodeFilter.FILTER_REJECT)
                        .skipNodes(NAMESPACES_KEYNOTE2, "presentation")
                        .acceptNodes(NAMESPACES_KEYNOTE2, "size"), true);
            Element next;
            while ((next = (Element) nit.nextNode())!=null) {
                this.handleSize(next);
            }
        }
	}

	private void addEntry(ZipEntry ze, ZipInputStream zis) throws IOException,
			SAXException, ParserConfigurationException {
		if (ze.isDirectory()) {
			// skip
		} else if (ze.getName().equals("index.apxl")) {
			ByteArrayOutputStream dest = getExtractedBytes(zis);
			DocumentBuilderFactoryProvider factoryprovider = new DocumentBuilderFactoryProvider();
			DocumentBuilderFactory documentBuilderFactory = factoryprovider.createSecureBuilderFactory(true);
			this.document = documentBuilderFactory
					.newDocumentBuilder().parse(
							new ByteArrayInputStream(dest.toByteArray()));
		} else if (ze.getName().equals("QuickLook/Thumbnail.jpg") || ze.getName().equals("QuickLook/Thumbnail.png")) {
			this.quicklookThumbnail = getExtractedBytes(zis).toByteArray();
		} else if (ze.getName().startsWith("thumbs/")) {
			this.thumbnails.put(ze.getName(),
					getExtractedBytes(zis));
		} else {
			this.resources.put(ze.getName(), getExtractedBytes(zis));
		}
	}

	private ByteArrayOutputStream getExtractedBytes(ZipInputStream zis)
			throws IOException {
		ByteArrayOutputStream dest = new ByteArrayOutputStream();
		int count;
		byte data[] = new byte[BUFFER];
		while ((count = zis.read(data, 0, BUFFER)) != -1) {
			dest.write(data, 0, count);
		}
		return dest;
	}

	private void handleSize(Element element) {
		this.width = Integer.parseInt(element.getAttributeNS(NAMESAPCES_SFA, "w"));
		this.height = Integer.parseInt(element.getAttributeNS(NAMESAPCES_SFA, "h"));
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy