View Single Post
Old 08-06-2019, 10:19 AM   #11
Blrp
Member
Blrp began at the beginning.
 
Posts: 16
Karma: 10
Join Date: Jul 2014
Device: none
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);
	}
}
Blrp is offline   Reply With Quote