package nl.siegmann.epublib.epub;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import nl.siegmann.epublib.domain.Book;
import nl.siegmann.epublib.domain.Resource;
import nl.siegmann.epublib.service.MediatypeService;
import nl.siegmann.epublib.util.IOUtil;
import org.rr.commons.utils.compression.truezip.TrueZipUtils;
import org.rr.commons.utils.compression.truezip.ZipEntry;
import org.rr.commons.utils.compression.truezip.ZipOutputStream;
import org.xmlpull.v1.XmlSerializer;
/**
* Generates an epub file. Not thread-safe, single use object.
*
* @author paul
*
*/
public class EpubWriter {
private final static Logger log = Logger.getLogger(EpubWriter.class.getName());
// package
static final String EMPTY_NAMESPACE_PREFIX = "";
private BookProcessor bookProcessor = BookProcessor.IDENTITY_BOOKPROCESSOR;
public EpubWriter() {
this(BookProcessor.IDENTITY_BOOKPROCESSOR);
}
public EpubWriter(BookProcessor bookProcessor) {
this.bookProcessor = bookProcessor;
}
public void write(Book book, OutputStream out) throws IOException {
book = processBook(book);
ZipOutputStream resultStream = TrueZipUtils.createZipOutputStream(out);
writeMimeType(resultStream);
writeContainer(resultStream);
initTOCResource(book);
writeResources(book, resultStream);
writePackageDocument(book, resultStream);
resultStream.close();
}
private Book processBook(Book book) {
if (bookProcessor != null) {
book = bookProcessor.processBook(book);
}
return book;
}
private void initTOCResource(Book book) {
Resource tocResource;
try {
tocResource = NCXDocument.createNCXResource(book);
Resource currentTocResource = book.getSpine().getTocResource();
if (currentTocResource != null) {
book.getResources().remove(currentTocResource.getHref());
}
book.getSpine().setTocResource(tocResource);
book.getResources().add(tocResource);
} catch (Exception e) {
log.warning("Error writing table of contents: " + e.getClass().getName() + ": " + e.getMessage());
}
}
private void writeResources(Book book, ZipOutputStream resultStream) throws IOException {
for(Resource resource: book.getResources().getAll()) {
writeResource(resource, resultStream);
}
for(Resource resource: book.getUnlistedResources().getAll()) {
writeResource(resource, resultStream, true);
}
}
private void writeResource(Resource resource, ZipOutputStream resultStream) throws IOException {
writeResource(resource, resultStream, false);
}
/**
* Writes the resource to the resultStream.
*
* @param resource
* @param resultStream
* @throws IOException
*/
private void writeResource(Resource resource, ZipOutputStream resultStream, boolean unlisted)
throws IOException {
if(resource == null) {
return;
}
try {
if(unlisted) {
//unlisted ones are stored with full path
resultStream.putNextEntry(new ZipEntry(resource.getHref()));
} else {
resultStream.putNextEntry(new ZipEntry("OEBPS/" + resource.getHref()));
}
InputStream inputStream = resource.getInputStream();
IOUtil.copy(inputStream, resultStream);
inputStream.close();
} catch(Exception e) {
log.log(Level.WARNING, e.getMessage(), e);
}
}
private void writePackageDocument(Book book, ZipOutputStream resultStream) throws IOException {
resultStream.putNextEntry(new ZipEntry("OEBPS/content.opf"));
XmlSerializer xmlSerializer = EpubProcessorSupport.createXmlSerializer(resultStream);
PackageDocumentWriter.write(this, xmlSerializer, book);
xmlSerializer.flush();
// String resultAsString = result.toString();
// resultStream.write(resultAsString.getBytes(Constants.ENCODING));
}
/**
* Writes the META-INF/container.xml file.
*
* @param resultStream
* @throws IOException
*/
private void writeContainer(ZipOutputStream resultStream) throws IOException {
resultStream.putNextEntry(new ZipEntry("META-INF/container.xml"));
Writer out = new OutputStreamWriter(resultStream);
out.write("<?xml version=\"1.0\"?>\n");
out.write("<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n");
out.write("\t<rootfiles>\n");
out.write("\t\t<rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n");
out.write("\t</rootfiles>\n");
out.write("</container>");
out.flush();
}
/**
* Stores the mimetype as an uncompressed file in the ZipOutputStream.
*
* @param resultStream
* @throws IOException
*/
private void writeMimeType(ZipOutputStream resultStream) throws IOException {
ZipEntry mimetypeZipEntry = new ZipEntry("mimetype");
mimetypeZipEntry.setMethod(ZipEntry.STORED);
byte[] mimetypeBytes = MediatypeService.EPUB.getName().getBytes();
mimetypeZipEntry.setSize(mimetypeBytes.length);
mimetypeZipEntry.setCrc(calculateCrc(mimetypeBytes));
resultStream.putNextEntry(mimetypeZipEntry);
resultStream.write(mimetypeBytes);
}
String getNcxId() {
return "ncx";
}
String getNcxHref() {
return "toc.ncx";
}
String getNcxMediaType() {
return "application/x-dtbncx+xml";
}
public BookProcessor getBookProcessor() {
return bookProcessor;
}
public void setBookProcessor(BookProcessor bookProcessor) {
this.bookProcessor = bookProcessor;
}
private static long calculateCrc(byte[] data) {
CRC32 crc = new CRC32();
crc.update(data);
return crc.getValue();
}
}