Alright, thanks for the help. I made a quick and dirty java program for editing content.opf in the zip file. I'll drop it here just in case someone with the same problem happens to find this thread.
Spoiler:
Code:
import java.io.BufferedWriter;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class EditContentOPF {
static String readFileInZip(String pathToZip, String pathInZip, String charsetName) throws IOException {
try (ZipFile zip = new ZipFile(pathToZip)) {
ZipEntry entry = zip.getEntry(pathInZip);
if (entry == null) throw new RuntimeException("content.opf not found");
try (InputStream is = zip.getInputStream(entry)) {
try (@SuppressWarnings("resource") Scanner s = new Scanner(is, charsetName).useDelimiter("\\A")) { // eclipse bug? try-with should not have resource leak
return s.hasNext() ? s.next() : "";
}
}
}
}
static String editContentFileText(String text) throws ParserConfigurationException, SAXException, IOException, TransformerException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
builder.setErrorHandler(null);
Document document = builder.parse(new InputSource(new StringReader(text)));
Element packageNode = document.getDocumentElement();
// get list of items to reference in spine
Node manifest = packageNode.getElementsByTagName("manifest").item(0);
NodeList manifestItems = ((Element)manifest).getElementsByTagName("item");
List<Element> manifestHtmlItems = new ArrayList<>();
for (int i = 0; i < manifestItems.getLength(); i++) {
Element item = (Element)manifestItems.item(i);
String href = item.getAttribute("href");
if (href.substring(href.lastIndexOf('.')).equals(".html"))
manifestHtmlItems.add(item);
}
// we expect index.html to be first in the manifest
Element indexItem = (Element)manifestHtmlItems.get(0);
if (!indexItem.getAttribute("href").equals("index.html"))
throw new RuntimeException("index.html is not first");
String indexId = indexItem.getAttribute("id");
manifestHtmlItems.remove(0);
// we expect index.html to be the only item referenced in the spine
Node spine = packageNode.getElementsByTagName("spine").item(0);
NodeList spineChildren = ((Element)spine).getChildNodes();
List<Element> spineNonTextChildren = new ArrayList<>();
for (int i = 0; i < spineChildren.getLength(); i++) {
if (spineChildren.item(i).getNodeType() != Node.TEXT_NODE)
spineNonTextChildren.add((Element)spineChildren.item(i));
}
if (spineNonTextChildren.size() != 1)
throw new RuntimeException("unexpected number of nodes in spine");
if (!spineNonTextChildren.get(0).getAttribute("idref").equals(indexId))
throw new RuntimeException("index.html is not referenced in spine");
// add references in spine
for (Element item : manifestHtmlItems) {
String id = item.getAttribute("id");
if (id.isEmpty())
throw new RuntimeException("item has no id, href=" + item.getAttribute("href"));
Element itemref = document.createElement("itemref");
itemref.setAttribute("idref", id);
spine.appendChild(itemref);
}
// turn document back into string
StringWriter writer = new StringWriter();
TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document), new StreamResult(writer));
return writer.toString();
}
static void writeToFileInZip(String pathToZip, String pathInZip, String text, String charsetName) throws IOException {
String[] lines = text.split("\\R");
try (FileSystem fs = FileSystems.newFileSystem(Paths.get(pathToZip), null)) {
Path fullPath = fs.getPath(pathInZip);
Files.delete(fullPath);
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(fullPath), charsetName))) {
for (String line : lines) {
bw.write(line);
bw.newLine();
}
}
}
}
public static String input() {
try (Scanner sc = new Scanner( // this yields a scanner of System.in that, when closed, does not close System.in
new FilterInputStream(System.in) {
@Override
public void close() throws IOException {}
})) {
return sc.nextLine();
}
}
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, TransformerException {
System.out.print("enter zip path: ");
String pathToZip = input();
String charsetName = "utf-8";
String pathInZip = "content.opf";
String contentFileText = readFileInZip(pathToZip, pathInZip, charsetName);
String editedContentFileText = editContentFileText(contentFileText);
writeToFileInZip(pathToZip, pathInZip, editedContentFileText, charsetName);
}
}