package nota.oxygen.epub;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.beans.*;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import nota.oxygen.common.Utils;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import ro.sync.ecss.extensions.api.AuthorOperationException;
import de.schlichtherle.truezip.file.TArchiveDetector;
import de.schlichtherle.truezip.file.TConfig;
import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.fs.archive.zip.JarDriver;
import de.schlichtherle.truezip.socket.sl.IOPoolLocator;
@SuppressWarnings("serial")
public class Splitter extends JPanel implements ActionListener, PropertyChangeListener {
private static JFrame frame;
private JButton startButton;
private JTextArea taskOutput;
private Task task;
private File[] listOfFiles;
private PackageHandler packageHandler;
private SplitHandler splitHandler;
private int docNumber;
private String idPrefix;
private Map<String, Document> docList;
private boolean stop;
private Map<String, String> ids;
class Task extends SwingWorker<Void, Void> {
@Override
public Void doInBackground() {
if (!EpubUtils.start(taskOutput))
return null;
if (!EpubUtils.unzip(taskOutput))
return null;
if (!EpubUtils.canSplit(taskOutput))
return null;
if (!EpubUtils.backup(taskOutput))
return null;
EpubUtils.outputProcess("PREPARING AND PARSING", true, taskOutput);
// create package handler instance
packageHandler = new PackageHandler();
if (!EpubUtils.parseFile(new File(EpubUtils.EPUB_FOLDER + File.separator + EpubUtils.PACKAGE_FILENAME), packageHandler, taskOutput))
return null;
docNumber = 0;
docList = new TreeMap<String, Document>();
ids = new HashMap<String, String>();
// get concat xhtml file from extracted zip file
listOfFiles = EpubUtils.getFiles(true, false);
// create split handler instance
splitHandler = new SplitHandler();
// prepare source file
if (!EpubUtils.prepareFile(listOfFiles[0], taskOutput))
return null;
// parse source file
if (!EpubUtils.parseFile(listOfFiles[0], splitHandler, taskOutput))
return null;
if (splitHandler.getMetaNodes().containsKey("dc:identifier")) {
idPrefix = splitHandler.getMetaNodes().get("dc:identifier");
}
EpubUtils.outputProcess("BUILDING SPLIT DOCUMENTS", true, taskOutput);
if (!splitConcatDocument(listOfFiles[0]))
return null;
if (!mapRefs())
return null;
if (!saveDocs())
return null;
EpubUtils.outputProcess("MODIFYING PACKAGE DOCUMENT", true, taskOutput);
listOfFiles = EpubUtils.getFiles(false, true);
if (listOfFiles.length == 0) {
EpubUtils.outputMessage(taskOutput, "No split files found");
return null;
}
else if (listOfFiles.length == 1) {
EpubUtils.outputMessage(taskOutput, "Only one file found - concat file have not been splitted correctly");
return null;
}
Document packageDoc = EpubUtils.createDocument(new File(EpubUtils.EPUB_FOLDER + File.separator + EpubUtils.PACKAGE_FILENAME), taskOutput);
if (packageDoc == null) {
return null;
}
for (int i = 0; i < listOfFiles.length; i++) {
// add split document to opf document
if (!EpubUtils.addOpfItem(packageDoc, listOfFiles[i].getName(), i+1, taskOutput))
return null;
}
// remove concat document from opf document
if (!EpubUtils.removeOpfItem(packageDoc, EpubUtils.CONCAT_FILENAME, taskOutput))
return null;
// remove fallback from non xhtml spine elements
if (!EpubUtils.removeFallbackFromOpf(packageDoc, taskOutput))
return null;
// save opf document
if (!EpubUtils.saveDocument(packageDoc, new File(EpubUtils.EPUB_FOLDER + File.separator + EpubUtils.PACKAGE_FILENAME), taskOutput))
return null;
EpubUtils.outputProcess("MODIFYING EPUB", true, taskOutput);
// obtain the global configuration
TConfig config = TConfig.get();
config.setArchiveDetector(new TArchiveDetector("epub", new JarDriver(IOPoolLocator.SINGLETON)));
// get epub file destination
String epubPath = EpubUtils.EPUB.getPath();
String epubFolder = EpubUtils.EPUB_FOLDER.substring(EpubUtils.EPUB_FOLDER.lastIndexOf(File.separator)).replace(File.separator, "");
TFile destination = new TFile(epubPath + File.separator + epubFolder);
// modify epub file destination
for (int i = 0; i < listOfFiles.length; i++) {
if (!EpubUtils.addFileToEpub(new TFile(listOfFiles[i]), destination, taskOutput))
return null;
}
if (!EpubUtils.addFileToEpub(new TFile(EpubUtils.EPUB_FOLDER + File.separator + EpubUtils.PACKAGE_FILENAME), destination, taskOutput))
return null;
if (!EpubUtils.removeFileFromEpub(new TFile(destination, EpubUtils.CONCAT_FILENAME), taskOutput))
return null;
// commit changes to epub file destination
if (!EpubUtils.commitChanges(taskOutput))
return null;
if (!EpubUtils.finish(taskOutput))
return null;
return null;
}
@Override
public void done() {
Toolkit.getDefaultToolkit().beep();
startButton.setEnabled(true);
setCursor(null);
EpubUtils.outputMessage(taskOutput, "Done");
}
}
public Splitter() {
super(new BorderLayout());
// Create the demo's UI.
startButton = new JButton("Start");
startButton.setActionCommand("start");
startButton.addActionListener(this);
startButton.setVisible(false);
taskOutput = new JTextArea(30, 130);
taskOutput.setMargin(new Insets(5, 5, 5, 5));
taskOutput.setEditable(false);
JPanel panel = new JPanel();
panel.add(startButton);
add(panel, BorderLayout.PAGE_START);
add(new JScrollPane(taskOutput), BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
startButton.doClick();
}
@Override
public void propertyChange(PropertyChangeEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void actionPerformed(ActionEvent evt) {
startButton.setEnabled(false);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
this.task = new Task();
this.task.addPropertyChangeListener(this);
this.task.execute();
}
private static void createAndShowGUI() {
JComponent newContentPane = new Splitter();
newContentPane.setOpaque(true);
frame = new JFrame("Splitting " + EpubUtils.EPUB.getName());
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
EpubUtils.EPUB = new File(args[0]);
EpubUtils.EPUB_FOLDER = args[1];
EpubUtils.prepare("splitter", "split");
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private boolean splitConcatDocument(File concatFile) {
try {
EpubUtils.outputMessage(taskOutput, "Splitting concat document");
// create concat document
Document sourceDoc = EpubUtils.createDocument(concatFile, taskOutput);
if (sourceDoc == null) {
return false;
}
// Find Source body
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
Node sourceBody = (Node) xpath.evaluate("//*[local-name() = 'body']", sourceDoc.getDocumentElement(), XPathConstants.NODE);
NodeList bodyNodes = sourceBody.getChildNodes();
for (int i = 0; i < bodyNodes.getLength(); i++) {
Node sectionNodeAtLevel1 = bodyNodes.item(i);
if (sectionNodeAtLevel1.getNodeType() == Node.ELEMENT_NODE) {
createNewEpubDoc(sectionNodeAtLevel1);
}
}
return true;
} catch (XPathExpressionException xpee) {
EpubUtils.outputMessage(taskOutput, "Could not split concat document. An XPathExpressionException occurred: " + xpee.getMessage());
} catch (AuthorOperationException aoe) {
EpubUtils.outputMessage(taskOutput, "Could not split concat document. An AuthorOperationException occurred: " + aoe.getMessage());
} catch (Exception e) {
EpubUtils.outputMessage(taskOutput, "Could not split concat document. An Exception occurred: " + e.getMessage());
}
return false;
}
private void createNewEpubDoc(Node section) throws AuthorOperationException {
docNumber = docNumber + 1;
StringBuilder xmlTemplate = new StringBuilder();
xmlTemplate.append("<html");
for (Map.Entry<String, String> entry : splitHandler.getHtmlAttributes().entrySet()) {
xmlTemplate.append(" " + entry.getKey() + "='" + entry.getValue() + "'");
}
xmlTemplate.append(">");
xmlTemplate.append("<head>");
xmlTemplate.append("<meta charset='UTF-8'/>");
if (packageHandler.getTitle() != null) {
xmlTemplate.append("<title>" + packageHandler.getTitle() + "</title>");
} else {
xmlTemplate.append("<title>" + splitHandler.getTitle() + "</title>");
}
for (Map.Entry<String, String> entry : splitHandler.getMetaNodes().entrySet()) {
xmlTemplate.append("<meta content='" + entry.getValue() + "' name='" + entry.getKey() + "'/>");
}
for (String cssLink : splitHandler.getCssLinks()) {
xmlTemplate.append("<link href='" + cssLink + "' rel='stylesheet' type='text/css'/>");
}
xmlTemplate.append("</head>");
xmlTemplate.append("<body/>");
xmlTemplate.append("</html>");
Document template = Utils.deserializeDocument(xmlTemplate.toString(), null);
Node templateBody = template.getElementsByTagName("body").item(0);
// Read Section Node
// Section Classname
String className = getAttributeFromNode(section, "class");
if (!className.equals("")) {
// Add class to body
Attr attrClass = template.createAttribute("class");
attrClass.setValue(className);
NamedNodeMap bodyAttrs = templateBody.getAttributes();
bodyAttrs.setNamedItem(attrClass);
}
// Section Id
String id = getAttributeFromNode(section, "id");
if (!id.equals("")) {
// Add id to body
Attr attrID = template.createAttribute("id");
attrID.setValue(id);
NamedNodeMap bodyAttrs = templateBody.getAttributes();
bodyAttrs.setNamedItem(attrID);
}
// Section epub:type
String epubType = getAttributeFromNode(section, "epub:type");
// Add epub:type to body
Attr attrEpubType = template.createAttributeNS("http://www.idpf.org/2007/ops", "epub:type");
attrEpubType.setValue(epubType);
NamedNodeMap bodyAttrs = templateBody.getAttributes();
bodyAttrs.setNamedItemNS(attrEpubType);
// epub:type might be divided by spaces - only 1 value will be used
String epubMainType = getEpubMainType(epubType);
if(epubMainType.equals("")) {
epubMainType = "unknown";
}
// Add Nodes to body
NodeList sectionNodes = section.getChildNodes();
for (int i = 0; i < sectionNodes.getLength(); i++) {
Node sectionNode = sectionNodes.item(i);
// Import the node
Node importedNode = template.importNode(sectionNode, true);
templateBody.appendChild(importedNode);
}
// Create FileName
String strNum = "00" + Integer.toString(docNumber);
strNum = strNum.substring(strNum.length() - 3);
String newFileName = idPrefix + "-" + strNum + "-" + epubMainType + ".xhtml";
docList.put(newFileName, template);
stop = false;
collectIds(newFileName, templateBody);
}
private String getAttributeFromNode(Node node, String attrName) {
Element tmp = (Element) node;
return tmp.getAttribute(attrName);
}
private String getEpubMainType(String epubType) {
// Hvis der kun er en enkelt attributv�rdi returneres denne. Ellers
// returnes de v�rdier der IKKE er frontmatter, bodymatter eller
// rearmatter
epubType = epubType.replace(" ", " ");
if (epubType.contains(" ")) {
epubType = epubType.replace("frontmatter", "");
epubType = epubType.replace("bodymatter", "");
epubType = epubType.replace("backmatter", "");
}
epubType = epubType.trim();
epubType = epubType.replaceAll(" ", "-");
return epubType;
}
private void collectIds(String fileName, Node node) {
// K�r rekursivt
if (stop) {
return;
}
// Check on noden har et id
if (node.getNodeType() == Node.ELEMENT_NODE) {
String id = getAttributeFromNode(node, "id");
if (id != "") {
// L�g i Map
if (addId2Map(id, fileName) == false) {
stop = true;
return;
}
}
}
if (node.hasChildNodes()) {
NodeList lst = node.getChildNodes();
for (int i = 0; i < lst.getLength(); i++) {
Node childNode = lst.item(i);
collectIds(fileName, childNode);
}
}
}
private boolean addId2Map(String id, String fileName) {
if (ids.containsKey(id)) {
EpubUtils.outputMessage(taskOutput, "Id'et " + id + " findes flere gange i dokumentet");
return false;
}
ids.put(id, fileName);
return true;
}
private boolean mapRefs() {
try {
EpubUtils.outputMessage(taskOutput, "Editing references");
for (Map.Entry<String, Document> entry : docList.entrySet()) {
NodeList refNodes = null;
Document doc = entry.getValue();
// Get references
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
try {
refNodes = (NodeList) xpath.evaluate("//*[local-name() = 'a']", doc, XPathConstants.NODESET);
} catch (Exception e) {
EpubUtils.outputMessage(taskOutput, "Error in Xml document: " + e.getMessage());
return false;
}
// Check referencer mod samlingen af Id'er
for (int i = 0; i < refNodes.getLength(); i++) {
Node ref = refNodes.item(i);
String href = getAttributeFromNode(ref, "href");
href = href.replace("#", "");
String fileName = GetReferenceFileName(href);
if (fileName != "") {
// Change reference
Attr attHref = doc.createAttribute("href");
attHref.setValue(fileName + "#" + href);
NamedNodeMap bodyAttrs = ref.getAttributes();
bodyAttrs.setNamedItem(attHref);
}
}
}
return true;
}
catch (Exception e) {
EpubUtils.outputMessage(taskOutput, "Could not edit references. An Exception occurred: " + e.getMessage());
}
return false;
}
private String GetReferenceFileName(String ref) {
if (ids.containsKey(ref)) {
String fileName = (String) ids.get(ref);
return fileName;
}
return "";
}
private boolean saveDocs() {
try {
// save split documents
for (Map.Entry<String, Document> entry : docList.entrySet()) {
if (!EpubUtils.saveDocument(entry.getValue(), new File(EpubUtils.EPUB_FOLDER + File.separator + entry.getKey()), taskOutput)) {
return false;
}
}
return true;
}
catch (Exception e) {
EpubUtils.outputMessage(taskOutput, "Could not save documents. An Exception occurred: " + e.getMessage());
}
return false;
}
}