package nota.oxygen.epub;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.swing.JTextArea;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import nota.oxygen.common.Utils;
import nota.oxygen.epub.headings.UpdateNavigationDocumentsOperation;
import org.apache.commons.io.FileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TVFS;
import de.schlichtherle.truezip.fs.FsSyncException;
import ro.sync.ecss.extensions.api.AuthorAccess;
import ro.sync.ecss.extensions.api.AuthorDocumentController;
import ro.sync.ecss.extensions.api.AuthorOperationException;
import ro.sync.ecss.extensions.api.access.AuthorWorkspaceAccess;
import ro.sync.ecss.extensions.api.node.AttrValue;
import ro.sync.ecss.extensions.api.node.AuthorElement;
import ro.sync.ecss.extensions.api.node.AuthorNode;
import ro.sync.exml.workspace.api.editor.WSEditor;
import ro.sync.exml.workspace.api.editor.page.WSEditorPage;
import ro.sync.exml.workspace.api.editor.page.author.WSAuthorEditorPage;
import ro.sync.exml.workspace.api.editor.page.text.WSTextEditorPage;
public class EpubUtils {
public static URL getEpubUrl(URL baseEpubUrl, String url)
{
URL epubUrl = Utils.getZipRootUrl(baseEpubUrl);
if (epubUrl == null) return null;
try {
return new URL(epubUrl, url);
} catch (MalformedURLException e) {
return null;
}
}
public static AuthorAccess getAuthorDocument(AuthorAccess authorAccess, URL docUrl)
{
AuthorWorkspaceAccess wa = authorAccess.getWorkspaceAccess();
WSEditor editor = wa.getEditorAccess(docUrl);
if (editor == null)
{
if (!wa.open(docUrl, WSEditor.PAGE_AUTHOR)) return null;
editor = wa.getEditorAccess(docUrl);
if (editor == null) return null;
}
if (editor.getCurrentPageID() != WSEditor.PAGE_AUTHOR) editor.changePage(WSEditor.PAGE_AUTHOR);
WSEditorPage wep = editor.getCurrentPage();
WSAuthorEditorPage aea = (wep instanceof WSAuthorEditorPage ? (WSAuthorEditorPage)wep : null);
if (aea == null) return null;
return aea.getAuthorAccess();
}
public static URL getPackageUrl(AuthorAccess authorAccess) {
try {
if (authorAccess == null) return null;
URL docUrl = authorAccess.getDocumentController().getAuthorDocumentNode().getXMLBaseURL();
AuthorAccess containerDocAccess = getAuthorDocument(authorAccess, getEpubUrl(docUrl, "META-INF/container.xml"));
if (containerDocAccess == null) return null;
Element rootElem = Utils.deserializeElement(Utils.serialize(
containerDocAccess,
containerDocAccess.getDocumentController().getAuthorDocumentNode()));
containerDocAccess.getEditorAccess().close(true);
XPath xp = Utils.getXPath("ns", "urn:oasis:names:tc:opendocument:xmlns:container");
try {
String relUrl = xp.evaluate("//ns:rootfile[@media-type='application/oebps-package+xml']/@full-path", rootElem);
return getEpubUrl(docUrl, relUrl);
} catch (XPathExpressionException e) {
return null;
}
}
catch (Exception e)
{
return null;
}
}
public static String getEpubFolder(AuthorAccess authorAccess) {
try {
if (authorAccess == null) return null;
URL docUrl = authorAccess.getDocumentController().getAuthorDocumentNode().getXMLBaseURL();
AuthorAccess containerDocAccess = getAuthorDocument(authorAccess, getEpubUrl(docUrl, "META-INF/container.xml"));
if (containerDocAccess == null) return null;
Element rootElem = Utils.deserializeElement(Utils.serialize(
containerDocAccess,
containerDocAccess.getDocumentController().getAuthorDocumentNode()));
containerDocAccess.getEditorAccess().close(true);
XPath xp = Utils.getXPath("ns", "urn:oasis:names:tc:opendocument:xmlns:container");
try {
URL rootUrl = Utils.getZipRootUrl(docUrl);
String epubUrl = xp.evaluate("//ns:rootfile[@media-type='application/oebps-package+xml']/@full-path", rootElem);
return rootUrl.toString() + epubUrl.substring(0, epubUrl.indexOf("/"));
} catch (XPathExpressionException e) {
return "";
}
}
catch (Exception e)
{
return null;
}
}
public static URL getPackageItemURLByXPath(AuthorAccess opfAccess, String xpath) {
try {
AuthorNode[] res = opfAccess.getDocumentController().findNodesByXPath(xpath, true, true, true);
if (res.length > 0)
{
AttrValue itemHref = ((AuthorElement)res[0]).getAttribute("href");
if (itemHref != null)
{
return new URL(opfAccess.getEditorAccess().getEditorLocation(), itemHref.getValue());
}
}
}
catch (Exception e) {
return null;
}
return null;
}
public static AuthorAccess getPackageItemDocumentByXPath(AuthorAccess opfAccess, String xpath) {
URL itemUrl = getPackageItemURLByXPath(opfAccess, xpath);
if (itemUrl != null) {
return getAuthorDocument(opfAccess, itemUrl);
}
return null;
}
public static AuthorAccess getXHTMLNavDocument(AuthorAccess opfAccess) {
return getPackageItemDocumentByXPath(opfAccess, "//item[@media-type='application/xhtml+xml' and @properties='nav']");
}
public static AuthorAccess getNCXDocument(AuthorAccess opfAccess) {
return getPackageItemDocumentByXPath(opfAccess, "//item[@media-type='application/x-dtbncx+xml']");
}
public static URL[] getSpineUrls(AuthorAccess opfAccess, boolean includeNonLinear) throws AuthorOperationException {
String xpath = "/package/spine/itemref" + (includeNonLinear ? "" : "[not(@linear='no')]");
AuthorNode[] itemrefs = opfAccess.getDocumentController().findNodesByXPath(xpath, true, true, true);
List<URL> res = new ArrayList<URL>();
for (int i = 0; i < itemrefs.length; i++) {
AuthorElement itemref = (AuthorElement)itemrefs[i];
AttrValue idref = itemref.getAttribute("idref");
if (idref != null) {
URL spineUrl = getPackageItemURLByXPath(
opfAccess,
String.format("/package/manifest/item[@id='%s']", idref.getValue()));
if (spineUrl != null) res.add(spineUrl);
}
}
return res.toArray(new URL[res.size()]);
}
public static AuthorAccess[] getSpine(AuthorAccess opfAccess, boolean includeNonLinear) throws AuthorOperationException {
List<AuthorAccess> res = new ArrayList<AuthorAccess>();
for (URL spineUrl : getSpineUrls(opfAccess, includeNonLinear)) {
AuthorAccess spineAccess = getAuthorDocument(opfAccess, spineUrl);
if (spineAccess != null) res.add(spineAccess);
}
return res.toArray(new AuthorAccess[0]);
}
public static boolean updateNavigationDocuments(AuthorAccess authorAccess) {
ERROR_MESSAGE = "";
try {
UpdateNavigationDocumentsOperation navigationOperation = new UpdateNavigationDocumentsOperation();
navigationOperation.setAuthorAccess(authorAccess);
navigationOperation.doOperation();
} catch (AuthorOperationException e) {
e.printStackTrace();
ERROR_MESSAGE = "Could not update navigation documents - an error occurred: " + e.getMessage();
return false;
}
return true;
}
/*********************************************************************************************************************************************
* OLD CONCAT & SPLIT METHODS (USING OXYGEN API)
*********************************************************************************************************************************************/
public static WSTextEditorPage getTextDocument(AuthorAccess authorAccess, URL docUrl)
{
AuthorWorkspaceAccess wa = authorAccess.getWorkspaceAccess();
WSEditor editor = wa.getEditorAccess(docUrl);
if (editor == null)
{
if (!wa.open(docUrl, WSEditor.PAGE_TEXT)) return null;
editor = wa.getEditorAccess(docUrl);
if (editor == null) return null;
}
editor.close(true);
if (editor.getCurrentPageID() != WSEditor.PAGE_TEXT) editor.changePage(WSEditor.PAGE_TEXT);
WSEditorPage wep = editor.getCurrentPage();
WSTextEditorPage editorPage = (wep instanceof WSTextEditorPage ? (WSTextEditorPage)wep : null);
if (editorPage == null) return null;
return editorPage;
}
public static boolean addUniqueIds(AuthorAccess xhtmlAccess) {
ERROR_MESSAGE = "";
try {
AuthorElement rootElement = xhtmlAccess.getDocumentController().getAuthorDocumentNode().getRootElement();
if (rootElement == null) {
ERROR_MESSAGE = "Found no root in xhtml file";
return false;
}
// insert unique ids for elements
XHTMLUniqueAttributesRecognizer uniqueAttributesRecognizer = new XHTMLUniqueAttributesRecognizer();
uniqueAttributesRecognizer.activated(xhtmlAccess);
uniqueAttributesRecognizer.assignUniqueIDs(rootElement.getStartOffset(), rootElement.getEndOffset(), false);
uniqueAttributesRecognizer.deactivated(xhtmlAccess);
// save and close
xhtmlAccess.getEditorAccess().save();
xhtmlAccess.getEditorAccess().close(true);
} catch (Exception e) {
e.printStackTrace();
ERROR_MESSAGE = "Could not add ids to document - an error occurred: " + e.getMessage();
return false;
}
return true;
}
private static AuthorElement getFirstElement(AuthorNode[] nodes) {
for (int i=0; i<nodes.length; i++) {
if (nodes[i] instanceof AuthorElement) return (AuthorElement)nodes[i];
}
return null;
}
public static boolean removeFallbackFromOpf(AuthorAccess opfAccess, String fileName) {
ERROR_MESSAGE = "";
if (opfAccess == null) {
ERROR_MESSAGE = "Could not access opf document";
return false;
}
AuthorDocumentController opfCtrl = opfAccess.getDocumentController();
opfCtrl.beginCompoundEdit();
try {
AuthorElement manifest = getFirstElement(opfCtrl.findNodesByXPath("/package/manifest", true, true, true));
if (manifest == null) {
ERROR_MESSAGE = "Found no manifest in package file";
return false;
}
AuthorElement item = getFirstElement(opfCtrl.findNodesByXPath(String.format("/package/manifest/item[contains(@href,'%s')]", fileName), true, true, true));
if (item != null) {
String idValue = item.getAttribute("id").getValue();
opfCtrl.removeAttribute("fallback", item);
AuthorElement itemRef = getFirstElement(opfCtrl.findNodesByXPath(String.format("/package/spine/itemref[@idref='%s']", idValue), true, true, true));
if (itemRef != null) {
opfCtrl.deleteNode(itemRef);
}
}
}
catch (Exception e) {
opfCtrl.cancelCompoundEdit();
ERROR_MESSAGE = "Could not add item to opf document - an error occurred: " + e.getMessage();
return false;
}
opfCtrl.endCompoundEdit();
return true;
}
public static boolean removeOpfItem(AuthorAccess opfAccess, String fileName) {
ERROR_MESSAGE = "";
if (opfAccess == null) {
ERROR_MESSAGE = "Could not access opf document";
return false;
}
AuthorDocumentController opfCtrl = opfAccess.getDocumentController();
opfCtrl.beginCompoundEdit();
try {
AuthorElement manifest = getFirstElement(opfCtrl.findNodesByXPath("/package/manifest", true, true, true));
if (manifest == null) {
ERROR_MESSAGE = "Found no manifest in package file";
return false;
}
AuthorElement item = getFirstElement(opfCtrl.findNodesByXPath(String.format("/package/manifest/item[@href='%s']", fileName), true, true, true));
if (item != null) {
String idValue = item.getAttribute("id").getValue();
opfCtrl.deleteNode(item);
AuthorElement itemRef = getFirstElement(opfCtrl.findNodesByXPath(String.format("/package/spine/itemref[@idref='%s']", idValue), true, true, true));
if (itemRef != null) {
opfCtrl.deleteNode(itemRef);
}
}
}
catch (Exception e) {
opfCtrl.cancelCompoundEdit();
ERROR_MESSAGE = "Could not add item to opf document - an error occurred: " + e.getMessage();
return false;
}
opfCtrl.endCompoundEdit();
return true;
}
public static boolean addOpfItem(AuthorAccess opfAccess, String fileName, boolean linear) {
ERROR_MESSAGE = "";
if (opfAccess == null) {
ERROR_MESSAGE = "Could not access opf document";
return false;
}
AuthorDocumentController opfCtrl = opfAccess.getDocumentController();
opfCtrl.beginCompoundEdit();
try {
AuthorElement manifest = getFirstElement(opfCtrl.findNodesByXPath("/package/manifest", true, true, true));
AuthorElement spine = getFirstElement(opfCtrl.findNodesByXPath("/package/spine", true, true, true));
if (manifest == null) {
ERROR_MESSAGE = "Found no manifest in package file";
return false;
}
AuthorElement item = getFirstElement(opfCtrl.findNodesByXPath(String.format("/package/manifest/item[@href='%s']", fileName), true, true, true));
if (item == null) {
String itemXml = "<item xmlns='" + EpubUtils.OPF_NS + "' media-type='application/xhtml+xml' href='" + fileName + "'/>";
opfCtrl.insertXMLFragment(itemXml, manifest.getEndOffset());
opfCtrl.getUniqueAttributesProcessor().assignUniqueIDs(manifest.getStartOffset(), manifest.getEndOffset(), true);
item = getFirstElement(opfCtrl.findNodesByXPath(String.format("/package/manifest/item[@href='%s']", fileName), true, true, true));
if (item != null) {
String idValue = item.getAttribute("id").getValue();
AuthorElement itemRef = getFirstElement(opfCtrl.findNodesByXPath(String.format("/package/spine/itemref[@idref='%s']", idValue), true, true, true));
if (itemRef == null) {
String itemRefXml="";
if(linear)
{
itemRefXml = "<itemref xmlns='" + EpubUtils.OPF_NS + "' idref='" + idValue + "'/>";
}
else
{
itemRefXml = "<itemref xmlns='" + EpubUtils.OPF_NS + "' idref='" + idValue + "' linear='no'/>";
}
opfCtrl.insertXMLFragment(itemRefXml, spine.getEndOffset());
}
}
}
} catch (Exception e) {
opfCtrl.cancelCompoundEdit();
ERROR_MESSAGE = "Could not add item to opf document - an error occurred: " + e.getMessage();
return false;
}
opfCtrl.endCompoundEdit();
return true;
}
public static Document createDocument() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder;
builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
return doc;
} catch (ParserConfigurationException e) {
}
return null;
}
public static boolean saveDocument(AuthorAccess authorAccess, Document doc, URL file) {
ERROR_MESSAGE = "";
try {
// remove all empty lines in document
removeEmptyLines(doc);
// create transformer
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
writer.write("<?xml version='1.0' encoding='UTF-8'?>\n");
writer.write("<!DOCTYPE html>\n");
StreamResult result = new StreamResult(writer);
// transform concatenated document to xml content string
transformer.transform(new DOMSource(doc), result);
// open new editor with concatenated xml content
AuthorWorkspaceAccess awa = authorAccess.getWorkspaceAccess();
URL newEditorUrl = awa.createNewEditor("xhtml", "text/xml", writer.toString());
// save content in editor into new concatenated xhtml file
WSEditor editor = awa.getEditorAccess(newEditorUrl);
editor.saveAs(file);
editor.close(true);
return true;
} catch (TransformerException e) {
e.printStackTrace();
ERROR_MESSAGE = "Could not save document - an error occurred: " + e.getMessage();
return false;
} catch (Exception e) {
e.printStackTrace();
ERROR_MESSAGE = "Could not save document - an error occurred: " + e.getMessage();
return false;
}
}
private static void removeEmptyLines(Document doc) {
try {
XPath xp = XPathFactory.newInstance().newXPath();
NodeList nl = (NodeList) xp.evaluate("//text()[normalize-space(.)='']", doc, XPathConstants.NODESET);
for (int i=0; i < nl.getLength(); ++i) {
Node node = nl.item(i);
node.getParentNode().removeChild(node);
}
} catch (XPathExpressionException e) {
e.printStackTrace();
}
}
public static String getMetaNodeValue(Node meta) {
String value = "";
if (meta.getNodeName().equalsIgnoreCase("meta")) {
Node metaName = meta.getAttributes().getNamedItem("name");
if (metaName != null) {
value = String.valueOf(meta.getAttributes().getNamedItem("name").getNodeValue());
}
}
return value;
}
/*********************************************************************************************************************************************
* NEW CONCAT & SPLIT METHODS
*********************************************************************************************************************************************/
public static void prepare(String workFolder, String backupExt) {
// set working folder
WORK_FOLDER = EPUB.getParent() + File.separator + EPUB.getName() + "." + workFolder;
// set epub folder name
EPUB_FOLDER = WORK_FOLDER + File.separator + EPUB_FOLDER.substring(EPUB_FOLDER.lastIndexOf("/")).replace("/", "");
// set backup name
BACKUP_EXT = "." + backupExt + ".bak";
}
public static boolean start(JTextArea taskOutput) {
outputProcess("STARTING", false, taskOutput);
// create working folder
if (!new File(WORK_FOLDER).exists())
new File(WORK_FOLDER).mkdir();
// clean workfolder
if (!cleanWorkfolder(WORK_FOLDER, taskOutput))
return false;
return true;
}
public static boolean backup(JTextArea taskOutput) {
outputProcess("BACKING UP EPUB", true, taskOutput);
// backup epub
if (!backupEpub(EPUB, BACKUP_EXT, taskOutput))
return false;
return true;
}
public static boolean unzip(JTextArea taskOutput) {
outputProcess("UNZIPPING", true, taskOutput);
// unzip epub to workfolder
if (!unzip(EPUB, WORK_FOLDER, taskOutput))
return false;
return true;
}
public static boolean finish(JTextArea taskOutput) {
outputProcess("FINISHING", true, taskOutput);
// clean working folder
if (!cleanWorkfolder(WORK_FOLDER, taskOutput))
return false;
// delete folder
if (new File(WORK_FOLDER).exists())
new File(WORK_FOLDER).delete();
outputMessage(taskOutput, "");
return true;
}
public static void outputProcess(String process, boolean newLine, JTextArea taskOutput) {
if (newLine) outputMessage(taskOutput, "");
outputMessage(taskOutput, "********************************************************");
outputMessage(taskOutput, process);
outputMessage(taskOutput, "********************************************************");
}
public static void outputMessage(JTextArea taskOutput, String output) {
taskOutput.append(output + "\n");
taskOutput.setCaretPosition(taskOutput.getDocument().getLength());
}
public static boolean cleanWorkfolder(String workFolder, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Cleaning " + workFolder);
FileUtils.cleanDirectory(new File(workFolder));
return true;
} catch (IOException ioe) {
outputMessage(taskOutput, "Could not clean folder " + workFolder + ". An IOException occurred: " + ioe.getMessage());
} catch (Exception e) {
outputMessage(taskOutput, "Could not clean folder " + workFolder + ". An Exception occurred: " + e.getMessage());
}
return false;
}
public static boolean backupEpub(File epub, String extension, JTextArea taskOutput) {
try {
File epubBak = new File(epub.getParent() + File.separator + epub.getName() + extension);
outputMessage(taskOutput, "Backing up epub " + epub.getPath() + " to " + epubBak.getPath());
final FileInputStream fileInputStream = new FileInputStream(epub);
final FileOutputStream fileOutputStream = new FileOutputStream(epubBak);
final byte[] buffer = new byte[16 * 1024 * 1024];
// copy the file content in bytes
int length;
while ((length = fileInputStream .read(buffer)) > 0) {
fileOutputStream .write(buffer, 0, length);
}
fileInputStream .close();
fileOutputStream .close();
return true;
} catch (IOException ioe) {
outputMessage(taskOutput, "Could not backup epub " + epub.getParent() + ". An IOException occurred: " + ioe.getMessage());
} catch (Exception e) {
outputMessage(taskOutput, "Could not backup epub " + epub.getParent() + ". An Exception occurred: " + e.getMessage());
}
return false;
}
public static boolean unzip(File epub, String destDir, JTextArea taskOutput) {
byte[] byteBuffer = new byte[16 * 1024 * 1024];
try {
ZipInputStream inZip = new ZipInputStream(new FileInputStream(epub.getPath()));
ZipEntry inZipEntry = inZip.getNextEntry();
while (inZipEntry != null) {
String fileName = inZipEntry.getName();
if (!fileName.endsWith(".xhtml") && !fileName.endsWith(".ncx") && !fileName.endsWith(".opf")) {
inZipEntry = inZip.getNextEntry();
continue;
}
File unZippedFile = new File(destDir + File.separator + fileName);
outputMessage(taskOutput, "Unzipping: " + unZippedFile.getAbsoluteFile());
if (inZipEntry.isDirectory()) {
unZippedFile.mkdirs();
} else {
new File(unZippedFile.getParent()).mkdirs();
unZippedFile.createNewFile();
FileOutputStream unZippedFileOutputStream = new FileOutputStream(unZippedFile);
int length;
while ((length = inZip.read(byteBuffer)) > 0) {
unZippedFileOutputStream.write(byteBuffer, 0, length);
}
unZippedFileOutputStream.close();
}
inZipEntry = inZip.getNextEntry();
}
inZip.close();
outputMessage(taskOutput, "Finished unzipping");
return true;
} catch (IOException ioe) {
outputMessage(taskOutput, "Could not unzip epub " + epub.getParent() + " to " + destDir + ". An IOException occurred: " + ioe.getMessage());
} catch (Exception e) {
outputMessage(taskOutput, "Could not unzip epub " + epub.getParent() + " to " + destDir + ". An Exception occurred: " + e.getMessage());
}
return false;
}
public static boolean canConcat(JTextArea taskOutput) {
outputMessage(taskOutput, "");
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".xhtml") && !name.equals(NAVIGATION_FILENAME);
}
};
File[] files = new File(EPUB_FOLDER).listFiles(filter);
boolean exists = false;
for (File file : files) {
if (file.getName().equals(CONCAT_FILENAME)) {
exists = true;
}
}
if (files.length == 0) {
outputMessage(taskOutput, "Cannot concat ePub - no documents found");
} else if (exists) {
outputMessage(taskOutput, "Cannot concat ePub - concat document already exists");
} else if (files.length == 1) {
outputMessage(taskOutput, "Cannot concat ePub - only one document exists");
} else {
return true;
}
outputMessage(taskOutput, "");
return false;
}
public static boolean canSplit(JTextArea taskOutput) {
outputMessage(taskOutput, "");
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".xhtml") && !name.equals(NAVIGATION_FILENAME);
}
};
File[] files = new File(EPUB_FOLDER).listFiles(filter);
boolean exists = true;
for (File file : files) {
if (file.getName().equals(CONCAT_FILENAME)) {
exists = true;
}
}
if (files.length == 0) {
outputMessage(taskOutput, "Cannot split ePub - no documents found");
} else if (files.length > 1) {
outputMessage(taskOutput, "Cannot split ePub - too many documents exists");
} else if (!exists) {
outputMessage(taskOutput, "Cannot split ePub - no concat document exists");
} else {
return true;
}
outputMessage(taskOutput, "");
return false;
}
public static File[] getFiles(final boolean concat, final boolean split) {
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
if (concat) return name.endsWith(".xhtml") && !name.equals(NAVIGATION_FILENAME) && name.equals(CONCAT_FILENAME);
else if (split) return name.endsWith(".xhtml") && !name.equals(NAVIGATION_FILENAME) && !name.equals(CONCAT_FILENAME);
else return name.endsWith(".xhtml") && !name.equals(NAVIGATION_FILENAME);
}
};
return new File(EPUB_FOLDER).listFiles(filter);
}
public static boolean prepareFile(File file, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Preparing " + file.getPath());
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(file.getPath());
// add unique ids to missing elements
addUniqueIds(doc.getDocumentElement(), taskOutput);
// write the content into xml file
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(file);
transformer.transform(source, result);
return true;
} catch (ParserConfigurationException pce) {
outputMessage(taskOutput, "Could not prepare " + file.getPath() + ". An ParserConfigurationException occurred: " + pce.getMessage());
} catch (TransformerException tfe) {
outputMessage(taskOutput, "Could not prepare " + file.getPath() + ". An ParserConfigurationException occurred: " + tfe.getMessage());
} catch (IOException ioe) {
outputMessage(taskOutput, "Could not prepare " + file.getPath() + ". An ParserConfigurationException occurred: " + ioe.getMessage());
} catch (SAXException sae) {
outputMessage(taskOutput, "Could not prepare " + file.getPath() + ". An ParserConfigurationException occurred: " + sae.getMessage());
} catch (Exception e) {
outputMessage(taskOutput, "Could not prepare " + file.getPath() + ". An ParserConfigurationException occurred: " + e.getMessage());
}
return false;
}
public static boolean parseFile(File file, DefaultHandler handler, JTextArea taskOutput) {
try {
// parse source file
EpubUtils.outputMessage(taskOutput, "Parsing " + file.getPath());
String fileName = file.getName();
if (handler.getClass().isAssignableFrom(ConcatHandler.class)) {
((ConcatHandler)handler).setFileEpubType(fileName.substring(fileName.lastIndexOf("-") + 1, fileName.lastIndexOf(".")));
}
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser;
saxParser = factory.newSAXParser();
saxParser.parse(file, handler);
return true;
} catch (ParserConfigurationException pce) {
EpubUtils.outputMessage(taskOutput, "Could not parse " + file.getPath() + ". An ParserConfigurationException occurred: " + pce.getMessage());
} catch (SAXException se) {
EpubUtils.outputMessage(taskOutput, "Could not parse " + file.getPath() + ". An SAXException occurred: " + se.getMessage());
} catch (IOException ioe) {
EpubUtils.outputMessage(taskOutput, "Could not parse " + file.getPath() + ". An IOException occurred: " + ioe.getMessage());
} catch (Exception e) {
EpubUtils.outputMessage(taskOutput, "Could not parse " + file.getPath() + ". An Exception occurred: " + e.getMessage());
}
return false;
}
public static void addUniqueIds(Node node, JTextArea taskOutput) {
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node currentNode = nodeList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
if (currentNode.getNodeName().equals("a")
|| currentNode.getNodeName().equals("aside")
|| currentNode.getNodeName().equals("body")
|| currentNode.getNodeName().equals("div")
|| currentNode.getNodeName().equals("h1")
|| currentNode.getNodeName().equals("h2")
|| currentNode.getNodeName().equals("h3")
|| currentNode.getNodeName().equals("h4")
|| currentNode.getNodeName().equals("h5")
|| currentNode.getNodeName().equals("h6")
|| currentNode.getNodeName().equals("hd")
|| currentNode.getNodeName().equals("img")
|| currentNode.getNodeName().equals("section")
|| currentNode.getNodeName().equals("span")
|| currentNode.getNodeName().equals("td")
|| currentNode.getNodeName().equals("th")
|| currentNode.getNodeName().equals("tr")) {
if (!((Element)currentNode).hasAttribute("id")) {
String uniqueId = UUID.randomUUID().toString();
((Element) currentNode).setAttribute("id", currentNode.getNodeName() + "_" + uniqueId);
outputMessage(taskOutput, "Added unique id " + uniqueId + " to element " + currentNode.getNodeName());
}
}
// calls this method for all the children which is Element
addUniqueIds(currentNode, taskOutput);
}
}
}
public static Document createDocument(File file, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Creating document");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(file);
} catch (ParserConfigurationException pce) {
EpubUtils.outputMessage(taskOutput, "File " + file.getPath() + " could not be created. An ParserConfigurationException occurred: " + pce.getMessage());
} catch (SAXException se) {
EpubUtils.outputMessage(taskOutput, "File " + file.getPath() + " could not be created. An SAXException occurred: " + se.getMessage());
} catch (IOException ioe) {
EpubUtils.outputMessage(taskOutput, "File " + file.getPath() + " could not be created. An IOException occurred: " + ioe.getMessage());
} catch (Exception e) {
EpubUtils.outputMessage(taskOutput, "File " + file.getPath() + " could not be created. An Exception occurred: " + e.getMessage());
}
return null;
}
public static boolean saveDocument(Document doc, File file, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Saving document to " + file.getPath());
// remove all empty lines in document
removeEmptyLines(doc, taskOutput);
// create transformer
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
//transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "about:legacy-compat");
FileOutputStream fop = new FileOutputStream(file);
String xmlDeclaration = "<?xml version='1.0' encoding='UTF-8'?>\n";
fop.write(xmlDeclaration.getBytes());
if (!file.getName().equals(PACKAGE_FILENAME)) {
String doctype = "<!DOCTYPE html>\n";
fop.write(doctype.getBytes());
}
StreamResult result = new StreamResult(fop);
// transform concatenated document to xml content string
transformer.transform(new DOMSource(doc), result);
fop.close();
return true;
} catch (TransformerException te) {
outputMessage(taskOutput, "Could not save document to " + file.getPath() + ". An TransformerException occurred: " + te.getMessage());
} catch (Exception e) {
outputMessage(taskOutput, "Could not save document to " + file.getPath() + ". An Exception occurred: " + e.getMessage());
}
return false;
}
private static void removeEmptyLines(Document doc, JTextArea taskOutput) {
try {
XPath xp = XPathFactory.newInstance().newXPath();
NodeList nl = (NodeList) xp.evaluate("//text()[normalize-space(.)='']", doc, XPathConstants.NODESET);
for (int i=0; i < nl.getLength(); ++i) {
Node node = nl.item(i);
node.getParentNode().removeChild(node);
}
} catch (XPathExpressionException xpee) {
outputMessage(taskOutput, "Could not remove empty lines in document. An XPathExpressionException occurred: " + xpee.getMessage());
}
}
public static boolean addOpfItem(Document opfDoc, String fileName, int number, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Adding " + fileName + " to package document");
NodeList manifest = opfDoc.getElementsByTagName("manifest");
NodeList spine = opfDoc.getElementsByTagName("spine");
String id = "item_cs_" + number;
Element manifestItem = opfDoc.createElement("item");
manifestItem.setAttribute("href", fileName);
manifestItem.setAttribute("id", id);
manifestItem.setAttribute("media-type", "application/xhtml+xml");
manifest.item(0).appendChild(manifestItem);
Element spineItem = opfDoc.createElement("itemref");
spineItem.setAttribute("idref", id);
if (fileName.endsWith("-cover.xhtml")) {
spineItem.setAttribute("linear", "no");
}
spine.item(0).appendChild(spineItem);
return true;
} catch (Exception e) {
outputMessage(taskOutput, "Could not add " + fileName + " to package document. An Exception occurred: " + e.getMessage());
}
return false;
}
public static boolean removeOpfItem(Document opfDoc, String fileName, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Removing " + fileName + " from package document");
NodeList items = opfDoc.getElementsByTagName("item");
NodeList itemRefs = opfDoc.getElementsByTagName("itemref");
if (items != null && items.getLength() > 0) {
for (int i = 0; i < items.getLength(); i++) {
if (items.item(i).getNodeType() == Node.ELEMENT_NODE) {
Element item = (Element) items.item(i);
String id = item.getAttribute("id");
if (item.getAttribute("href").equals(fileName)) {
item.getParentNode().removeChild(item);
if (itemRefs != null && itemRefs.getLength() > 0) {
for (int j = 0; j < itemRefs.getLength(); j++) {
if (itemRefs.item(j).getNodeType() == Node.ELEMENT_NODE) {
Element itemRef = (Element) itemRefs
.item(j);
if (itemRef.getAttribute("idref")
.equals(id)) {
itemRef.getParentNode()
.removeChild(itemRef);
}
}
}
}
}
}
}
}
return true;
} catch (Exception e) {
outputMessage(taskOutput, "Could not remove " + fileName + " from package document. An Exception occurred: " + e.getMessage());
}
return false;
}
public static boolean removeFallbackFromOpf(Document opfDoc, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Removing fallback from package document");
List<String> nonSpineElements = new ArrayList<String>();
getNonSpineElements(new File(EPUB_FOLDER), "", nonSpineElements);
NodeList items = opfDoc.getElementsByTagName("item");
NodeList itemRefs = opfDoc.getElementsByTagName("itemref");
if (items != null && items.getLength() > 0) {
for (int i = 0; i < items.getLength(); i++) {
if (items.item(i).getNodeType() == Node.ELEMENT_NODE) {
Element item = (Element) items.item(i);
String id = item.getAttribute("id");
for (String nonSpineElement : nonSpineElements) {
if (item.getAttribute("href").equals(nonSpineElement)) {
item.removeAttribute("fallback");
if (itemRefs != null && itemRefs.getLength() > 0) {
for (int j = 0; j < itemRefs.getLength(); j++) {
if (itemRefs.item(j).getNodeType() == Node.ELEMENT_NODE) {
Element itemRef = (Element) itemRefs
.item(j);
if (itemRef.getAttribute("idref")
.equals(id)) {
itemRef.getParentNode()
.removeChild(itemRef);
}
}
}
}
}
}
}
}
}
return true;
} catch (Exception e) {
outputMessage(taskOutput, "Could not remove fallback from package document. An Exception occurred: " + e.getMessage());
}
return false;
}
private static void getNonSpineElements(File dir, String subFolder, List<String> nonSpineElements) {
File[] files = dir.listFiles();
for (int i = 0; i < files.length; i++) {
String fileName = files[i].getName();
if (fileName.endsWith(NAVIGATION_FILENAME) || !fileName.endsWith(".xhtml")) {
if (files[i].isFile()) {
if (!subFolder.equals("")) nonSpineElements.add(subFolder + "/" + files[i].getName());
else nonSpineElements.add(files[i].getName());
}
}
if (files[i].isDirectory()) {
getNonSpineElements(files[i], files[i].getName(), nonSpineElements);
}
}
}
public static boolean addFileToEpub(TFile source, TFile destination, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Adding file " + source.getName() + " to epub");
source.cp_rp(new TFile(destination, source.getName()));
return true;
} catch (IOException ioe) {
outputMessage(taskOutput, "Could not add file " + source.getName() + " to epub. An IOException occurred: " + ioe.getMessage());
} catch (Exception e) {
outputMessage(taskOutput, "Could not add file " + source.getName() + " to epub. An Exception occurred: " + e.getMessage());
}
return false;
}
public static boolean removeFileFromEpub(TFile destination, JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Removing file " + destination.getName() + " from epub");
destination.rm();
return true;
} catch (IOException ioe) {
outputMessage(taskOutput, "Could not remove file " + destination.getName() + " from epub. An IOException occurred: " + ioe.getMessage());
} catch (Exception e) {
outputMessage(taskOutput, "Could not remove file " + destination.getName() + " from epub. An Exception occurred: " + e.getMessage());
}
return false;
}
public static boolean commitChanges(JTextArea taskOutput) {
try {
outputMessage(taskOutput, "Committing changes to epub");
//TVFS.umount(new TFile(EPUB));
TVFS.umount();
return true;
} catch (FsSyncException fsse) {
outputMessage(taskOutput, "Could not commit changes to epub. An FsSyncException occurred: " + fsse.getMessage());
} catch (Exception e) {
outputMessage(taskOutput, "Could not commit changes to epub. An Exception occurred: " + e.getMessage());
}
return false;
}
public static String XHTML_NS = "http://www.w3.org/1999/xhtml";
public static String NCX_NS = "http://www.daisy.org/z3986/2005/ncx/";
public static String EPUB_NS = "http://www.idpf.org/2007/ops";
public static String OPF_NS = "http://www.idpf.org/2007/opf";
public static String ERROR_MESSAGE;
public static String PACKAGE_FILENAME = "package.opf";
public static String CONCAT_FILENAME = "concat.xhtml";
public static String NAVIGATION_FILENAME = "nav.xhtml";
public static File EPUB;
public static String EPUB_FOLDER = "";
public static String WORK_FOLDER = "";
public static String BACKUP_EXT = "";
}