
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