lollipop版本分开,有些新特性android5.0和6.0不支持

This commit is contained in:
kunfei committed 2023-03-30 08:40:58 +08:00
1 parent 0959e50f53
commit 3cf3231da5
258 files changed
+12817 -13529

No files matched your search

@@ -0,0 +1,20 @@
package me.ag2s.epublib.epub;
import me.ag2s.epublib.domain.EpubBook;
/**
* Post-processes a book.
* <p>
* Can be used to clean up a book after reading or before writing.
*
* @author paul
*/
public interface BookProcessor {
/**
* A BookProcessor that returns the input book unchanged.
*/
BookProcessor IDENTITY_BOOKPROCESSOR = book -> book;
EpubBook processBook(EpubBook book);
}
@@ -0,0 +1,73 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import me.ag2s.epublib.domain.EpubBook;
/**
* A book processor that combines several other bookprocessors
* <p>
* Fixes coverpage/coverimage.
* Cleans up the XHTML.
*
* @author paul.siegmann
*/
@SuppressWarnings("unused declaration")
public class BookProcessorPipeline implements BookProcessor {
private static final String TAG = BookProcessorPipeline.class.getName();
private List<BookProcessor> bookProcessors;
public BookProcessorPipeline() {
this(null);
}
public BookProcessorPipeline(List<BookProcessor> bookProcessingPipeline) {
this.bookProcessors = bookProcessingPipeline;
}
@Override
public EpubBook processBook(EpubBook book) {
if (bookProcessors == null) {
return book;
}
for (BookProcessor bookProcessor : bookProcessors) {
try {
book = bookProcessor.processBook(book);
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
return book;
}
public void addBookProcessor(BookProcessor bookProcessor) {
if (this.bookProcessors == null) {
bookProcessors = new ArrayList<>();
}
this.bookProcessors.add(bookProcessor);
}
public void addBookProcessors(Collection<BookProcessor> bookProcessors) {
if (this.bookProcessors == null) {
this.bookProcessors = new ArrayList<>();
}
this.bookProcessors.addAll(bookProcessors);
}
public List<BookProcessor> getBookProcessors() {
return bookProcessors;
}
public void setBookProcessingPipeline(
List<BookProcessor> bookProcessingPipeline) {
this.bookProcessors = bookProcessingPipeline;
}
}
@@ -0,0 +1,178 @@
package me.ag2s.epublib.epub;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import java.util.ArrayList;
import java.util.List;
import me.ag2s.epublib.util.StringUtil;
/**
* Utility methods for working with the DOM.
*
* @author paul
*/
// package
class DOMUtil {
/**
* First tries to get the attribute value by doing an getAttributeNS on the element, if that gets an empty element it does a getAttribute without namespace.
*
* @param element element
* @param namespace namespace
* @param attribute attribute
* @return String Attribute
*/
public static String getAttribute(Element element, String namespace,
String attribute) {
String result = element.getAttributeNS(namespace, attribute);
if (StringUtil.isEmpty(result)) {
result = element.getAttribute(attribute);
}
return result;
}
/**
* Gets all descendant elements of the given parentElement with the given namespace and tagname and returns their text child as a list of String.
*
* @param parentElement parentElement
* @param namespace namespace
* @param tagName tagName
* @return List<String>
*/
public static List<String> getElementsTextChild(Element parentElement,
String namespace, String tagName) {
NodeList elements = parentElement
.getElementsByTagNameNS(namespace, tagName);
//ArrayList 初始化时指定长度提高性能
List<String> result = new ArrayList<>(elements.getLength());
for (int i = 0; i < elements.getLength(); i++) {
result.add(getTextChildrenContent((Element) elements.item(i)));
}
return result;
}
/**
* Finds in the current document the first element with the given namespace and elementName and with the given findAttributeName and findAttributeValue.
* It then returns the value of the given resultAttributeName.
*
* @param document document
* @param namespace namespace
* @param elementName elementName
* @param findAttributeName findAttributeName
* @param findAttributeValue findAttributeValue
* @param resultAttributeName resultAttributeName
* @return String value
*/
public static String getFindAttributeValue(Document document,
String namespace, String elementName, String findAttributeName,
String findAttributeValue, String resultAttributeName) {
NodeList metaTags = document.getElementsByTagNameNS(namespace, elementName);
for (int i = 0; i < metaTags.getLength(); i++) {
Element metaElement = (Element) metaTags.item(i);
if (findAttributeValue
.equalsIgnoreCase(metaElement.getAttribute(findAttributeName))
&& StringUtil
.isNotBlank(metaElement.getAttribute(resultAttributeName))) {
return metaElement.getAttribute(resultAttributeName);
}
}
return null;
}
/**
* Gets the first element that is a child of the parentElement and has the given namespace and tagName
*
* @param parentElement parentElement
* @param namespace namespace
* @param tagName tagName
* @return Element
*/
public static NodeList getElementsByTagNameNS(Element parentElement,
String namespace, String tagName) {
NodeList nodes = parentElement.getElementsByTagNameNS(namespace, tagName);
if (nodes.getLength() != 0) {
return nodes;
}
nodes = parentElement.getElementsByTagName(tagName);
if (nodes.getLength() == 0) {
return null;
}
return nodes;
}
/**
* Gets the first element that is a child of the parentElement and has the given namespace and tagName
*
* @param parentElement parentElement
* @param namespace namespace
* @param tagName tagName
* @return Element
*/
public static NodeList getElementsByTagNameNS(Document parentElement,
String namespace, String tagName) {
NodeList nodes = parentElement.getElementsByTagNameNS(namespace, tagName);
if (nodes.getLength() != 0) {
return nodes;
}
nodes = parentElement.getElementsByTagName(tagName);
if (nodes.getLength() == 0) {
return null;
}
return nodes;
}
/**
* Gets the first element that is a child of the parentElement and has the given namespace and tagName
*
* @param parentElement parentElement
* @param namespace namespace
* @param tagName tagName
* @return Element
*/
public static Element getFirstElementByTagNameNS(Element parentElement,
String namespace, String tagName) {
NodeList nodes = parentElement.getElementsByTagNameNS(namespace, tagName);
if (nodes.getLength() != 0) {
return (Element) nodes.item(0);
}
nodes = parentElement.getElementsByTagName(tagName);
if (nodes.getLength() == 0) {
return null;
}
return (Element) nodes.item(0);
}
/**
* The contents of all Text nodes that are children of the given parentElement.
* The result is trim()-ed.
* <p>
* The reason for this more complicated procedure instead of just returning the data of the firstChild is that
* when the text is Chinese characters then on Android each Characater is represented in the DOM as
* an individual Text node.
*
* @param parentElement parentElement
* @return String value
*/
public static String getTextChildrenContent(Element parentElement) {
if (parentElement == null) {
return null;
}
StringBuilder result = new StringBuilder();
NodeList childNodes = parentElement.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if ((node == null) ||
(node.getNodeType() != Node.TEXT_NODE)) {
continue;
}
result.append(((Text) node).getData());
}
return result.toString().trim();
}
}
@@ -0,0 +1,139 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URL;
import java.util.Objects;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import me.ag2s.epublib.Constants;
/**
* Various low-level support methods for reading/writing epubs.
*
* @author paul.siegmann
*/
public class EpubProcessorSupport {
private static final String TAG = EpubProcessorSupport.class.getName();
protected static DocumentBuilderFactory documentBuilderFactory;
static {
init();
}
static class EntityResolverImpl implements EntityResolver {
private String previousLocation;
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws IOException {
String resourcePath;
if (systemId.startsWith("http:")) {
URL url = new URL(systemId);
resourcePath = "dtd/" + url.getHost() + url.getPath();
previousLocation = resourcePath
.substring(0, resourcePath.lastIndexOf('/'));
} else {
resourcePath =
previousLocation + systemId.substring(systemId.lastIndexOf('/'));
}
if (Objects.requireNonNull(this.getClass().getClassLoader()).getResource(resourcePath) == null) {
throw new RuntimeException(
"remote resource is not cached : [" + systemId
+ "] cannot continue");
}
InputStream in = Objects.requireNonNull(EpubProcessorSupport.class.getClassLoader())
.getResourceAsStream(resourcePath);
return new InputSource(in);
}
}
private static void init() {
EpubProcessorSupport.documentBuilderFactory = DocumentBuilderFactory
.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setValidating(false);
}
public static XmlSerializer createXmlSerializer(OutputStream out)
throws UnsupportedEncodingException {
return createXmlSerializer(
new OutputStreamWriter(out, Constants.CHARACTER_ENCODING));
}
public static XmlSerializer createXmlSerializer(Writer out) {
XmlSerializer result = null;
try {
/*
* Disable XmlPullParserFactory here before it doesn't work when
* building native image using GraalVM
*/
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setValidating(true);
result = factory.newSerializer();
//result = new KXmlSerializer();
result.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
result.setOutput(out);
} catch (Exception e) {
Log.e(TAG,
"When creating XmlSerializer: " + e.getClass().getName() + ": " + e
.getMessage());
}
return result;
}
/**
* Gets an EntityResolver that loads dtd's and such from the epub4j classpath.
* In order to enable the loading of relative urls the given EntityResolver contains the previousLocation.
* Because of a new EntityResolver is created every time this method is called.
* Fortunately the EntityResolver created uses up very little memory per instance.
*
* @return an EntityResolver that loads dtd's and such from the epub4j classpath.
*/
public static EntityResolver getEntityResolver() {
return new EntityResolverImpl();
}
@SuppressWarnings("unused")
public DocumentBuilderFactory getDocumentBuilderFactory() {
return documentBuilderFactory;
}
/**
* Creates a DocumentBuilder that looks up dtd's and schema's from epub4j's classpath.
*
* @return a DocumentBuilder that looks up dtd's and schema's from epub4j's classpath.
*/
public static DocumentBuilder createDocumentBuilder() {
DocumentBuilder result = null;
try {
result = documentBuilderFactory.newDocumentBuilder();
result.setEntityResolver(getEntityResolver());
} catch (ParserConfigurationException e) {
Log.e(TAG, e.getMessage());
}
return result;
}
}
@@ -0,0 +1,186 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import androidx.annotation.NonNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import me.ag2s.epublib.Constants;
import me.ag2s.epublib.domain.EpubBook;
import me.ag2s.epublib.domain.MediaType;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.domain.Resources;
import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.util.StringUtil;
import me.ag2s.epublib.util.zip.AndroidZipFile;
import me.ag2s.epublib.util.zip.ZipFileWrapper;
/**
* Reads an epub file.
*
* @author paul
*/
@SuppressWarnings("ALL")
public class EpubReader {
private static final String TAG = EpubReader.class.getName();
private final BookProcessor bookProcessor = BookProcessor.IDENTITY_BOOKPROCESSOR;
public EpubBook readEpub(InputStream in) throws IOException {
return readEpub(in, Constants.CHARACTER_ENCODING);
}
public EpubBook readEpub(ZipInputStream in) throws IOException {
return readEpub(in, Constants.CHARACTER_ENCODING);
}
public EpubBook readEpub(ZipFile zipfile) throws IOException {
return readEpub(zipfile, Constants.CHARACTER_ENCODING);
}
/**
* Read epub from inputstream
*
* @param in the inputstream from which to read the epub
* @param encoding the encoding to use for the html files within the epub
* @return the Book as read from the inputstream
* @throws IOException IOException
*/
public EpubBook readEpub(InputStream in, String encoding) throws IOException {
return readEpub(new ZipInputStream(in), encoding);
}
/**
* Reads this EPUB without loading any resources into memory.
*
* @param zipFile the file to load
* @param encoding the encoding for XHTML files
* @return this Book without loading all resources into memory.
* @throws IOException IOException
*/
public EpubBook readEpubLazy(@NonNull ZipFile zipFile, @NonNull String encoding)
throws IOException {
return readEpubLazy(zipFile, encoding, Arrays.asList(MediaTypes.mediaTypes));
}
public EpubBook readEpubLazy(@NonNull AndroidZipFile zipFile, @NonNull String encoding)
throws IOException {
return readEpubLazy(zipFile, encoding, Arrays.asList(MediaTypes.mediaTypes));
}
public EpubBook readEpub(@NonNull ZipInputStream in, @NonNull String encoding) throws IOException {
return readEpub(ResourcesLoader.loadResources(in, encoding));
}
public EpubBook readEpub(ZipFile in, String encoding) throws IOException {
return readEpub(ResourcesLoader.loadResources(new ZipFileWrapper(in), encoding));
}
/**
* Reads this EPUB without loading all resources into memory.
*
* @param zipFile the file to load
* @param encoding the encoding for XHTML files
* @param lazyLoadedTypes a list of the MediaType to load lazily
* @return this Book without loading all resources into memory.
* @throws IOException IOException
*/
public EpubBook readEpubLazy(@NonNull ZipFile zipFile, @NonNull String encoding,
@NonNull List<MediaType> lazyLoadedTypes) throws IOException {
Resources resources = ResourcesLoader
.loadResources(new ZipFileWrapper(zipFile), encoding, lazyLoadedTypes);
return readEpub(resources);
}
public EpubBook readEpubLazy(@NonNull AndroidZipFile zipFile, @NonNull String encoding,
@NonNull List<MediaType> lazyLoadedTypes) throws IOException {
Resources resources = ResourcesLoader
.loadResources(new ZipFileWrapper(zipFile), encoding, lazyLoadedTypes);
return readEpub(resources);
}
public EpubBook readEpub(Resources resources) {
return readEpub(resources, new EpubBook());
}
public EpubBook readEpub(Resources resources, EpubBook result) {
if (result == null) {
result = new EpubBook();
}
handleMimeType(result, resources);
String packageResourceHref = getPackageResourceHref(resources);
Resource packageResource = processPackageResource(packageResourceHref, result, resources);
result.setOpfResource(packageResource);
Resource ncxResource = processNcxResource(packageResource, result);
result.setNcxResource(ncxResource);
result = postProcessBook(result);
return result;
}
private EpubBook postProcessBook(EpubBook book) {
if (bookProcessor != null) {
book = bookProcessor.processBook(book);
}
return book;
}
private Resource processNcxResource(Resource packageResource, EpubBook book) {
Log.d(TAG, "OPF:getHref()" + packageResource.getHref());
if (book.isEpub3()) {
return NCXDocumentV3.read(book, this);
} else {
return NCXDocumentV2.read(book, this);
}
}
private Resource processPackageResource(String packageResourceHref, EpubBook book,
Resources resources) {
Resource packageResource = resources.remove(packageResourceHref);
try {
PackageDocumentReader.read(packageResource, this, book, resources);
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
return packageResource;
}
private String getPackageResourceHref(Resources resources) {
String defaultResult = "OEBPS/content.opf";
String result = defaultResult;
Resource containerResource = resources.remove("META-INF/container.xml");
if (containerResource == null) {
return result;
}
try {
Document document = ResourceUtil.getAsDocument(containerResource);
Element rootFileElement = (Element) ((Element) document
.getDocumentElement().getElementsByTagName("rootfiles").item(0))
.getElementsByTagName("rootfile").item(0);
result = rootFileElement.getAttribute("full-path");
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
if (StringUtil.isBlank(result)) {
result = defaultResult;
}
return result;
}
private void handleMimeType(EpubBook result, Resources resources) {
resources.remove("mimetype");
//result.setResources(resources);
}
}
@@ -0,0 +1,190 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import me.ag2s.epublib.domain.EpubBook;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.util.IOUtil;
/**
* Generates an epub file. Not thread-safe, single use object.
*
* @author paul
*/
public class EpubWriter {
private static final String TAG = EpubWriter.class.getName();
// package
static final String EMPTY_NAMESPACE_PREFIX = "";
private BookProcessor bookProcessor;
public EpubWriter() {
this(BookProcessor.IDENTITY_BOOKPROCESSOR);
}
public EpubWriter(BookProcessor bookProcessor) {
this.bookProcessor = bookProcessor;
}
public void write(EpubBook book, OutputStream out) throws IOException {
book = processBook(book);
ZipOutputStream resultStream = new ZipOutputStream(out);
writeMimeType(resultStream);
writeContainer(resultStream);
initTOCResource(book);
writeResources(book, resultStream);
writePackageDocument(book, resultStream);
resultStream.close();
}
private EpubBook processBook(EpubBook book) {
if (bookProcessor != null) {
book = bookProcessor.processBook(book);
}
return book;
}
private void initTOCResource(EpubBook book) {
Resource tocResource;
try {
if (book.isEpub3()) {
tocResource = NCXDocumentV3.createNCXResource(book);
} else {
tocResource = NCXDocumentV2.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 ex) {
Log.e(TAG,
"Error writing table of contents: "
+ ex.getClass().getName() + ": " + ex.getMessage(), ex);
}
}
private void writeResources(EpubBook book, ZipOutputStream resultStream) {
for (Resource resource : book.getResources().getAll()) {
writeResource(resource, resultStream);
}
}
/**
* Writes the resource to the resultStream.
*
* @param resource resource
* @param resultStream resultStream
*/
private void writeResource(Resource resource, ZipOutputStream resultStream) {
if (resource == null) {
return;
}
try {
resultStream.putNextEntry(new ZipEntry("OEBPS/" + resource.getHref()));
InputStream inputStream = resource.getInputStream();
IOUtil.copy(inputStream, resultStream);
inputStream.close();
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
private void writePackageDocument(EpubBook 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 resultStream
* @throws IOException 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 resultStream
* @throws IOException IOException
*/
private void writeMimeType(ZipOutputStream resultStream) throws IOException {
ZipEntry mimetypeZipEntry = new ZipEntry("mimetype");
mimetypeZipEntry.setMethod(ZipEntry.STORED);
byte[] mimetypeBytes = MediaTypes.EPUB.getName().getBytes();
mimetypeZipEntry.setSize(mimetypeBytes.length);
mimetypeZipEntry.setCrc(calculateCrc(mimetypeBytes));
resultStream.putNextEntry(mimetypeZipEntry);
resultStream.write(mimetypeBytes);
}
private long calculateCrc(byte[] data) {
CRC32 crc = new CRC32();
crc.update(data);
return crc.getValue();
}
String getNcxId() {
return "ncx";
}
String getNcxHref() {
return "toc.ncx";
}
String getNcxMediaType() {
return MediaTypes.NCX.getName();
}
@SuppressWarnings("unused")
public BookProcessor getBookProcessor() {
return bookProcessor;
}
@SuppressWarnings("unused")
public void setBookProcessor(BookProcessor bookProcessor) {
this.bookProcessor = bookProcessor;
}
}
@@ -0,0 +1,11 @@
package me.ag2s.epublib.epub;
import java.io.OutputStream;
import me.ag2s.epublib.domain.Resource;
@SuppressWarnings("unused")
public interface HtmlProcessor {
void processHtmlResource(Resource resource, OutputStream out);
}
@@ -0,0 +1,344 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import me.ag2s.epublib.Constants;
import me.ag2s.epublib.domain.Author;
import me.ag2s.epublib.domain.EpubBook;
import me.ag2s.epublib.domain.Identifier;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.domain.TOCReference;
import me.ag2s.epublib.domain.TableOfContents;
import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.util.StringUtil;
/**
* Writes the ncx document as defined by namespace http://www.daisy.org/z3986/2005/ncx/
*
* @author paul
*/
public class NCXDocumentV2 {
public static final String NAMESPACE_NCX = "http://www.daisy.org/z3986/2005/ncx/";
@SuppressWarnings("unused")
public static final String PREFIX_NCX = "ncx";
public static final String NCX_ITEM_ID = "ncx";
public static final String DEFAULT_NCX_HREF = "toc.ncx";
public static final String PREFIX_DTB = "dtb";
private static final String TAG = NCXDocumentV2.class.getName();
private interface NCXTags {
String ncx = "ncx";
String meta = "meta";
String navPoint = "navPoint";
String navMap = "navMap";
String navLabel = "navLabel";
String content = "content";
String text = "text";
String docTitle = "docTitle";
String docAuthor = "docAuthor";
String head = "head";
}
private interface NCXAttributes {
String src = "src";
String name = "name";
String content = "content";
String id = "id";
String playOrder = "playOrder";
String clazz = "class";
String version = "version";
}
private interface NCXAttributeValues {
String chapter = "chapter";
String version = "2005-1";
}
@SuppressWarnings("unused")
public static Resource read(EpubBook book, EpubReader epubReader) {
Resource ncxResource = null;
if (book.getSpine().getTocResource() == null) {
Log.e(TAG, "Book does not contain a table of contents file");
return null;
}
try {
ncxResource = book.getSpine().getTocResource();
if (ncxResource == null) {
return null;
}
Log.d(TAG, ncxResource.getHref());
Document ncxDocument = ResourceUtil.getAsDocument(ncxResource);
Element navMapElement = DOMUtil
.getFirstElementByTagNameNS(ncxDocument.getDocumentElement(),
NAMESPACE_NCX, NCXTags.navMap);
if (navMapElement == null) {
return null;
}
TableOfContents tableOfContents = new TableOfContents(
readTOCReferences(navMapElement.getChildNodes(), book));
book.setTableOfContents(tableOfContents);
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
return ncxResource;
}
static List<TOCReference> readTOCReferences(NodeList navpoints,
EpubBook book) {
if (navpoints == null) {
return new ArrayList<>();
}
List<TOCReference> result = new ArrayList<>(
navpoints.getLength());
for (int i = 0; i < navpoints.getLength(); i++) {
Node node = navpoints.item(i);
if (node.getNodeType() != Document.ELEMENT_NODE) {
continue;
}
if (!(node.getLocalName().equals(NCXTags.navPoint))) {
continue;
}
TOCReference tocReference = readTOCReference((Element) node, book);
result.add(tocReference);
}
return result;
}
static TOCReference readTOCReference(Element navpointElement, EpubBook book) {
String label = readNavLabel(navpointElement);
//Log.d(TAG,"label:"+label);
String tocResourceRoot = StringUtil
.substringBeforeLast(book.getSpine().getTocResource().getHref(), '/');
if (tocResourceRoot.length() == book.getSpine().getTocResource().getHref()
.length()) {
tocResourceRoot = "";
} else {
tocResourceRoot = tocResourceRoot + "/";
}
String reference = StringUtil
.collapsePathDots(tocResourceRoot + readNavReference(navpointElement));
String href = StringUtil
.substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR);
String fragmentId = StringUtil
.substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR);
Resource resource = book.getResources().getByHref(href);
if (resource == null) {
Log.e(TAG, "Resource with href " + href + " in NCX document not found");
}
Log.v(TAG, "label:" + label);
Log.v(TAG, "href:" + href);
Log.v(TAG, "fragmentId:" + fragmentId);
TOCReference result = new TOCReference(label, resource, fragmentId);
List<TOCReference> childTOCReferences = readTOCReferences(
navpointElement.getChildNodes(), book);
result.setChildren(childTOCReferences);
return result;
}
private static String readNavReference(Element navpointElement) {
Element contentElement = DOMUtil
.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX,
NCXTags.content);
if (contentElement == null) {
return null;
}
String result = DOMUtil
.getAttribute(contentElement, NAMESPACE_NCX, NCXAttributes.src);
try {
result = URLDecoder.decode(result, Constants.CHARACTER_ENCODING);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.getMessage());
}
return result;
}
private static String readNavLabel(Element navpointElement) {
//Log.d(TAG,navpointElement.getTagName());
Element navLabel = DOMUtil
.getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX,
NCXTags.navLabel);
assert navLabel != null;
return DOMUtil.getTextChildrenContent(DOMUtil
.getFirstElementByTagNameNS(navLabel, NAMESPACE_NCX, NCXTags.text));
}
@SuppressWarnings("unused")
public static void write(EpubWriter epubWriter, EpubBook book,
ZipOutputStream resultStream) throws IOException {
resultStream
.putNextEntry(new ZipEntry(book.getSpine().getTocResource().getHref()));
XmlSerializer out = EpubProcessorSupport.createXmlSerializer(resultStream);
write(out, book);
out.flush();
}
/**
* Generates a resource containing an xml document containing the table of contents of the book in ncx format.
*
* @param xmlSerializer the serializer used
* @param book the book to serialize
* @throws IOException IOException
* @throws IllegalStateException IllegalStateException
* @throws IllegalArgumentException IllegalArgumentException
*/
public static void write(XmlSerializer xmlSerializer, EpubBook book)
throws IllegalArgumentException, IllegalStateException, IOException {
write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(),
book.getMetadata().getAuthors(), book.getTableOfContents());
}
public static Resource createNCXResource(EpubBook book)
throws IllegalArgumentException, IllegalStateException, IOException {
return createNCXResource(book.getMetadata().getIdentifiers(),
book.getTitle(), book.getMetadata().getAuthors(),
book.getTableOfContents());
}
public static Resource createNCXResource(List<Identifier> identifiers,
String title, List<Author> authors, TableOfContents tableOfContents)
throws IllegalArgumentException, IllegalStateException, IOException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
XmlSerializer out = EpubProcessorSupport.createXmlSerializer(data);
write(out, identifiers, title, authors, tableOfContents);
return new Resource(NCX_ITEM_ID, data.toByteArray(),
DEFAULT_NCX_HREF, MediaTypes.NCX);
}
public static void write(XmlSerializer serializer,
List<Identifier> identifiers, String title, List<Author> authors,
TableOfContents tableOfContents)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startDocument(Constants.CHARACTER_ENCODING, false);
serializer.setPrefix(EpubWriter.EMPTY_NAMESPACE_PREFIX, NAMESPACE_NCX);
serializer.startTag(NAMESPACE_NCX, NCXTags.ncx);
// serializer.writeNamespace("ncx", NAMESPACE_NCX);
// serializer.attribute("xmlns", NAMESPACE_NCX);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.version,
NCXAttributeValues.version);
serializer.startTag(NAMESPACE_NCX, NCXTags.head);
for (Identifier identifier : identifiers) {
writeMetaElement(identifier.getScheme(), identifier.getValue(),
serializer);
}
writeMetaElement("generator", Constants.EPUB_GENERATOR_NAME, serializer);
writeMetaElement("depth", String.valueOf(tableOfContents.calculateDepth()),
serializer);
writeMetaElement("totalPageCount", "0", serializer);
writeMetaElement("maxPageNumber", "0", serializer);
serializer.endTag(NAMESPACE_NCX, "head");
serializer.startTag(NAMESPACE_NCX, NCXTags.docTitle);
serializer.startTag(NAMESPACE_NCX, NCXTags.text);
// write the first title
serializer.text(StringUtil.defaultIfNull(title));
serializer.endTag(NAMESPACE_NCX, NCXTags.text);
serializer.endTag(NAMESPACE_NCX, NCXTags.docTitle);
for (Author author : authors) {
serializer.startTag(NAMESPACE_NCX, NCXTags.docAuthor);
serializer.startTag(NAMESPACE_NCX, NCXTags.text);
serializer.text(author.getLastname() + ", " + author.getFirstname());
serializer.endTag(NAMESPACE_NCX, NCXTags.text);
serializer.endTag(NAMESPACE_NCX, NCXTags.docAuthor);
}
serializer.startTag(NAMESPACE_NCX, NCXTags.navMap);
writeNavPoints(tableOfContents.getTocReferences(), 1, serializer);
serializer.endTag(NAMESPACE_NCX, NCXTags.navMap);
serializer.endTag(NAMESPACE_NCX, "ncx");
serializer.endDocument();
}
private static void writeMetaElement(String dtbName, String content,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(NAMESPACE_NCX, NCXTags.meta);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.name,
PREFIX_DTB + ":" + dtbName);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.content,
content);
serializer.endTag(NAMESPACE_NCX, NCXTags.meta);
}
private static int writeNavPoints(List<TOCReference> tocReferences,
int playOrder,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
for (TOCReference tocReference : tocReferences) {
if (tocReference.getResource() == null) {
playOrder = writeNavPoints(tocReference.getChildren(), playOrder,
serializer);
continue;
}
writeNavPointStart(tocReference, playOrder, serializer);
playOrder++;
if (!tocReference.getChildren().isEmpty()) {
playOrder = writeNavPoints(tocReference.getChildren(), playOrder,
serializer);
}
writeNavPointEnd(tocReference, serializer);
}
return playOrder;
}
private static void writeNavPointStart(TOCReference tocReference,
int playOrder, XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(NAMESPACE_NCX, NCXTags.navPoint);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.id,
"navPoint-" + playOrder);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.playOrder,
String.valueOf(playOrder));
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.clazz,
NCXAttributeValues.chapter);
serializer.startTag(NAMESPACE_NCX, NCXTags.navLabel);
serializer.startTag(NAMESPACE_NCX, NCXTags.text);
serializer.text(tocReference.getTitle());
serializer.endTag(NAMESPACE_NCX, NCXTags.text);
serializer.endTag(NAMESPACE_NCX, NCXTags.navLabel);
serializer.startTag(NAMESPACE_NCX, NCXTags.content);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.src,
tocReference.getCompleteHref());
serializer.endTag(NAMESPACE_NCX, NCXTags.content);
}
@SuppressWarnings("unused")
private static void writeNavPointEnd(TOCReference tocReference,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.endTag(NAMESPACE_NCX, NCXTags.navPoint);
}
}
@@ -0,0 +1,461 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import me.ag2s.epublib.Constants;
import me.ag2s.epublib.domain.Author;
import me.ag2s.epublib.domain.EpubBook;
import me.ag2s.epublib.domain.Identifier;
import me.ag2s.epublib.domain.MediaType;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.domain.TOCReference;
import me.ag2s.epublib.domain.TableOfContents;
import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.util.StringUtil;
/**
* Writes the ncx document as defined by namespace http://www.daisy.org/z3986/2005/ncx/
*
* @author Ag2S20150909
*/
public class NCXDocumentV3 {
public static final String NAMESPACE_XHTML = "http://www.w3.org/1999/xhtml";
public static final String NAMESPACE_EPUB = "http://www.idpf.org/2007/ops";
public static final String LANGUAGE = "en";
@SuppressWarnings("unused")
public static final String PREFIX_XHTML = "html";
public static final String NCX_ITEM_ID = "htmltoc";
public static final String DEFAULT_NCX_HREF = "toc.xhtml";
public static final String V3_NCX_PROPERTIES = "nav";
public static final MediaType V3_NCX_MEDIATYPE = MediaTypes.XHTML;
private static final String TAG = NCXDocumentV3.class.getName();
private interface XHTMLTgs {
String html = "html";
String head = "head";
String title = "title";
String meta = "meta";
String link = "link";
String body = "body";
String h1 = "h1";
String h2 = "h2";
String nav = "nav";
String ol = "ol";
String li = "li";
String a = "a";
String span = "span";
}
private interface XHTMLAttributes {
String xmlns = "xmlns";
String xmlns_epub = "xmlns:epub";
String lang = "lang";
String xml_lang = "xml:lang";
String rel = "rel";
String type = "type";
String epub_type = "epub:type";//nav的必须属性
String id = "id";
String role = "role";
String href = "href";
String http_equiv = "http-equiv";
String content = "content";
}
private interface XHTMLAttributeValues {
String Content_Type = "Content-Type";
String HTML_UTF8 = "text/html; charset=utf-8";
String lang = "en";
String epub_type = "toc";
String role_toc = "doc-toc";
}
/**
* 解析epub的目录文件
*
* @param book Book
* @param epubReader epubreader
* @return Resource
*/
@SuppressWarnings("unused")
public static Resource read(EpubBook book, EpubReader epubReader) {
Resource ncxResource = null;
if (book.getSpine().getTocResource() == null) {
Log.e(TAG, "Book does not contain a table of contents file");
return null;
}
try {
ncxResource = book.getSpine().getTocResource();
if (ncxResource == null) {
return null;
}
//一些epub 3 文件没有按照epub3的标准使用删除掉ncx目录文件
if (ncxResource.getHref().endsWith(".ncx")) {
Log.v(TAG, "该epub文件不标准,使用了epub2的目录文件");
return NCXDocumentV2.read(book, epubReader);
}
Log.d(TAG, ncxResource.getHref());
Document ncxDocument = ResourceUtil.getAsDocument(ncxResource);
Log.d(TAG, ncxDocument.getNodeName());
Element navMapElement = (Element) ncxDocument.getElementsByTagName(XHTMLTgs.nav).item(0);
if (navMapElement == null) {
Log.d(TAG, "epub3目录文件未发现nav节点,尝试使用epub2的规则解析");
return NCXDocumentV2.read(book, epubReader);
}
navMapElement = (Element) navMapElement.getElementsByTagName(XHTMLTgs.ol).item(0);
Log.d(TAG, navMapElement.getTagName());
TableOfContents tableOfContents = new TableOfContents(
readTOCReferences(navMapElement.getChildNodes(), book));
Log.d(TAG, tableOfContents.toString());
book.setTableOfContents(tableOfContents);
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
return ncxResource;
}
private static List<TOCReference> doToc(Node n, EpubBook book) {
List<TOCReference> result = new ArrayList<>();
if (n == null || n.getNodeType() != Document.ELEMENT_NODE) {
return result;
} else {
Element el = (Element) n;
NodeList nodeList = el.getElementsByTagName(XHTMLTgs.li);
for (int i = 0; i < nodeList.getLength(); i++) {
result.add(readTOCReference((Element) nodeList.item(i), book));
}
}
return result;
}
static List<TOCReference> readTOCReferences(NodeList navpoints,
EpubBook book) {
if (navpoints == null) {
return new ArrayList<>();
}
//Log.d(TAG, "readTOCReferences:navpoints.getLength()" + navpoints.getLength());
List<TOCReference> result = new ArrayList<>(navpoints.getLength());
for (int i = 0; i < navpoints.getLength(); i++) {
Node node = navpoints.item(i);
//如果该node是null,或者不是Element,跳出本次循环
if (node == null || node.getNodeType() != Document.ELEMENT_NODE) {
continue;
}
Element el = (Element) node;
//如果该Element的name为”li“,将其添加到目录结果
if (el.getTagName().equals(XHTMLTgs.li)) {
result.add(readTOCReference(el, book));
}
}
return result;
}
static TOCReference readTOCReference(Element navpointElement, EpubBook book) {
//章节的名称
String label = readNavLabel(navpointElement);
//Log.d(TAG, "label:" + label);
String tocResourceRoot = StringUtil
.substringBeforeLast(book.getSpine().getTocResource().getHref(), '/');
if (tocResourceRoot.length() == book.getSpine().getTocResource().getHref()
.length()) {
tocResourceRoot = "";
} else {
tocResourceRoot = tocResourceRoot + "/";
}
String reference = StringUtil
.collapsePathDots(tocResourceRoot + readNavReference(navpointElement));
String href = StringUtil
.substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR);
String fragmentId = StringUtil
.substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR);
Resource resource = book.getResources().getByHref(href);
if (resource == null) {
Log.e(TAG, "Resource with href " + href + " in NCX document not found");
}
Log.v(TAG, "label:" + label);
Log.v(TAG, "href:" + href);
Log.v(TAG, "fragmentId:" + fragmentId);
//父级目录
TOCReference result = new TOCReference(label, resource, fragmentId);
//解析子级目录
List<TOCReference> childTOCReferences = doToc(navpointElement, book);
//readTOCReferences(
//navpointElement.getChildNodes(), book);
result.setChildren(childTOCReferences);
return result;
}
/**
* 获取目录节点的href
*
* @param navpointElement navpointElement
* @return String
*/
private static String readNavReference(Element navpointElement) {
//https://www.w3.org/publishing/epub/epub-packages.html#sec-package-nav
//父级节点必须是 "li"
//Log.d(TAG, "readNavReference:" + navpointElement.getTagName());
Element contentElement = DOMUtil
.getFirstElementByTagNameNS(navpointElement, "", XHTMLTgs.a);
if (contentElement == null) {
return null;
}
String result = DOMUtil
.getAttribute(contentElement, "", XHTMLAttributes.href);
try {
result = URLDecoder.decode(result, Constants.CHARACTER_ENCODING);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.getMessage());
}
return result;
}
/**
* 获取目录节点里面的章节名
*
* @param navpointElement navpointElement
* @return String
*/
private static String readNavLabel(Element navpointElement) {
//https://www.w3.org/publishing/epub/epub-packages.html#sec-package-nav
//父级节点必须是 "li"
//Log.d(TAG, "readNavLabel:" + navpointElement.getTagName());
String label;
Element labelElement = DOMUtil.getFirstElementByTagNameNS(navpointElement, "", "a");
assert labelElement != null;
label = labelElement.getTextContent();
if (StringUtil.isNotBlank(label)) {
return label;
} else {
labelElement = DOMUtil.getFirstElementByTagNameNS(navpointElement, "", "span");
}
assert labelElement != null;
label = labelElement.getTextContent();
//如果通过 a 标签无法获取章节列表,则是无href章节名
return label;
}
public static Resource createNCXResource(EpubBook book)
throws IllegalArgumentException, IllegalStateException, IOException {
return createNCXResource(book.getMetadata().getIdentifiers(),
book.getTitle(), book.getMetadata().getAuthors(),
book.getTableOfContents());
}
public static Resource createNCXResource(List<Identifier> identifiers,
String title, List<Author> authors, TableOfContents tableOfContents)
throws IllegalArgumentException, IllegalStateException, IOException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
XmlSerializer out = EpubProcessorSupport.createXmlSerializer(data);
write(out, identifiers, title, authors, tableOfContents);
Resource resource = new Resource(NCX_ITEM_ID, data.toByteArray(),
DEFAULT_NCX_HREF, V3_NCX_MEDIATYPE);
resource.setProperties(V3_NCX_PROPERTIES);
return resource;
}
/**
* Generates a resource containing an xml document containing the table of contents of the book in ncx format.
*
* @param xmlSerializer the serializer used
* @param book the book to serialize
* @throws IOException IOException
* @throws IllegalStateException IllegalStateException
* @throws IllegalArgumentException IllegalArgumentException
*/
public static void write(XmlSerializer xmlSerializer, EpubBook book)
throws IllegalArgumentException, IllegalStateException, IOException {
write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(),
book.getMetadata().getAuthors(), book.getTableOfContents());
}
/**
* 写入
*
* @param serializer serializer
* @param identifiers identifiers
* @param title title
* @param authors authors
* @param tableOfContents tableOfContents
*/
@SuppressWarnings("unused")
public static void write(XmlSerializer serializer,
List<Identifier> identifiers, String title, List<Author> authors,
TableOfContents tableOfContents) throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startDocument(Constants.CHARACTER_ENCODING, false);
serializer.setPrefix(EpubWriter.EMPTY_NAMESPACE_PREFIX, NAMESPACE_XHTML);
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.html);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, XHTMLAttributes.xmlns_epub, NAMESPACE_EPUB);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, XHTMLAttributes.xml_lang, XHTMLAttributeValues.lang);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, XHTMLAttributes.lang, LANGUAGE);
//写入头部head标签
writeHead(title, serializer);
//body开始
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.body);
//h1开始
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.h1);
serializer.text(title);
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.h1);
//h1关闭
//nav开始
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.nav);
serializer.attribute("", XHTMLAttributes.epub_type, XHTMLAttributeValues.epub_type);
serializer.attribute("", XHTMLAttributes.id, XHTMLAttributeValues.epub_type);
serializer.attribute("", XHTMLAttributes.role, XHTMLAttributeValues.role_toc);
//h2开始
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.h2);
serializer.text("目录");
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.h2);
writeNavPoints(tableOfContents.getTocReferences(), 1, serializer);
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.nav);
//body关闭
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.body);
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.html);
serializer.endDocument();
}
private static int writeNavPoints(List<TOCReference> tocReferences,
int playOrder,
XmlSerializer serializer) throws IOException {
writeOlStart(serializer);
for (TOCReference tocReference : tocReferences) {
if (tocReference.getResource() == null) {
playOrder = writeNavPoints(tocReference.getChildren(), playOrder,
serializer);
continue;
}
writeNavPointStart(tocReference, serializer);
playOrder++;
if (!tocReference.getChildren().isEmpty()) {
playOrder = writeNavPoints(tocReference.getChildren(), playOrder,
serializer);
}
writeNavPointEnd(tocReference, serializer);
}
writeOlSEnd(serializer);
return playOrder;
}
private static void writeNavPointStart(TOCReference tocReference, XmlSerializer serializer) throws IOException {
writeLiStart(serializer);
String title = tocReference.getTitle();
String href = tocReference.getCompleteHref();
if (StringUtil.isNotBlank(href)) {
writeLabel(title, href, serializer);
} else {
writeLabel(title, serializer);
}
}
@SuppressWarnings("unused")
private static void writeNavPointEnd(TOCReference tocReference,
XmlSerializer serializer) throws IOException {
writeLiEnd(serializer);
}
protected static void writeLabel(String title, String href, XmlSerializer serializer) throws IOException {
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.a);
serializer.attribute("", XHTMLAttributes.href, href);
//attribute必须在Text之前设置。
serializer.text(title);
//serializer.attribute(NAMESPACE_XHTML, XHTMLAttributes.href, href);
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.a);
}
protected static void writeLabel(String title, XmlSerializer serializer) throws IOException {
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.span);
serializer.text(title);
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.span);
}
private static void writeLiStart(XmlSerializer serializer) throws IOException {
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.li);
Log.d(TAG, "writeLiStart");
}
private static void writeLiEnd(XmlSerializer serializer) throws IOException {
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.li);
Log.d(TAG, "writeLiEND");
}
private static void writeOlStart(XmlSerializer serializer) throws IOException {
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.ol);
Log.d(TAG, "writeOlStart");
}
private static void writeOlSEnd(XmlSerializer serializer) throws IOException {
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.ol);
Log.d(TAG, "writeOlEnd");
}
private static void writeHead(String title, XmlSerializer serializer) throws IOException {
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.head);
//title
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.title);
serializer.text(StringUtil.defaultIfNull(title));
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.title);
//link
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.link);
serializer.attribute("", XHTMLAttributes.rel, "stylesheet");
serializer.attribute("", XHTMLAttributes.type, "text/css");
serializer.attribute("", XHTMLAttributes.href, "css/style.css");
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.link);
//meta
serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.meta);
serializer.attribute("", XHTMLAttributes.http_equiv, XHTMLAttributeValues.Content_Type);
serializer.attribute("", XHTMLAttributes.content, XHTMLAttributeValues.HTML_UTF8);
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.meta);
serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.head);
}
}
@@ -0,0 +1,96 @@
package me.ag2s.epublib.epub;
/**
* Functionality shared by the PackageDocumentReader and the PackageDocumentWriter
*
* @author paul
*/
public class PackageDocumentBase {
public static final String BOOK_ID_ID = "duokan-book-id";
public static final String NAMESPACE_OPF = "http://www.idpf.org/2007/opf";
public static final String NAMESPACE_DUBLIN_CORE = "http://purl.org/dc/elements/1.1/";
public static final String PREFIX_DUBLIN_CORE = "dc";
//public static final String PREFIX_OPF = "opf";
//在EPUB3标准中,packge前面没有opf头,一些epub阅读器也不支持opf头。
//Some Epub Reader not reconize op:packge,So just let it empty;
public static final String PREFIX_OPF = "";
//添加 version 变量来区分Epub文件的版本
//Add the version field to distinguish the version of EPUB file
public static final String version = "version";
public static final String dateFormat = "yyyy-MM-dd";
protected interface DCTags {
String title = "title";
String creator = "creator";
String subject = "subject";
String description = "description";
String publisher = "publisher";
String contributor = "contributor";
String date = "date";
String type = "type";
String format = "format";
String identifier = "identifier";
String source = "source";
String language = "language";
String relation = "relation";
String coverage = "coverage";
String rights = "rights";
}
protected interface DCAttributes {
String scheme = "scheme";
String id = "id";
}
protected interface OPFTags {
String metadata = "metadata";
String meta = "meta";
String manifest = "manifest";
String packageTag = "package";
String itemref = "itemref";
String spine = "spine";
String reference = "reference";
String guide = "guide";
String item = "item";
}
protected interface OPFAttributes {
String uniqueIdentifier = "unique-identifier";
String idref = "idref";
String name = "name";
String content = "content";
String type = "type";
String href = "href";
String linear = "linear";
String event = "event";
String role = "role";
String file_as = "file-as";
String id = "id";
String media_type = "media-type";
String title = "title";
String toc = "toc";
String version = "version";
String scheme = "scheme";
String property = "property";
//add for epub3
/**
* add for epub3
*/
String properties = "properties";
}
protected interface OPFValues {
String meta_cover = "cover";
String reference_cover = "cover";
String no = "no";
String generator = "generator";
String duokan = "duokan-body-font";
}
}
@@ -0,0 +1,225 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import me.ag2s.epublib.domain.Author;
import me.ag2s.epublib.domain.Date;
import me.ag2s.epublib.domain.Identifier;
import me.ag2s.epublib.domain.Metadata;
import me.ag2s.epublib.util.StringUtil;
/**
* Reads the package document metadata.
* <p>
* In its own separate class because the PackageDocumentReader became a bit large and unwieldy.
*
* @author paul
*/
// package
class PackageDocumentMetadataReader extends PackageDocumentBase {
private static final String TAG = PackageDocumentMetadataReader.class.getName();
public static Metadata readMetadata(Document packageDocument) {
Metadata result = new Metadata();
Element metadataElement = DOMUtil
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(),
NAMESPACE_OPF, OPFTags.metadata);
if (metadataElement == null) {
Log.e(TAG, "Package does not contain element " + OPFTags.metadata);
return result;
}
result.setTitles(DOMUtil
.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,
DCTags.title));
result.setPublishers(DOMUtil
.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,
DCTags.publisher));
result.setDescriptions(DOMUtil
.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,
DCTags.description));
result.setRights(DOMUtil
.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,
DCTags.rights));
result.setTypes(DOMUtil
.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,
DCTags.type));
result.setSubjects(DOMUtil
.getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,
DCTags.subject));
result.setIdentifiers(readIdentifiers(metadataElement));
result.setAuthors(readCreators(metadataElement));
result.setContributors(readContributors(metadataElement));
result.setDates(readDates(metadataElement));
result.setOtherProperties(readOtherProperties(metadataElement));
result.setMetaAttributes(readMetaProperties(metadataElement));
Element languageTag = DOMUtil
.getFirstElementByTagNameNS(metadataElement, NAMESPACE_DUBLIN_CORE,
DCTags.language);
if (languageTag != null) {
result.setLanguage(DOMUtil.getTextChildrenContent(languageTag));
}
return result;
}
/**
* consumes meta tags that have a property attribute as defined in the standard. For example:
* &lt;meta property="rendition:layout"&gt;pre-paginated&lt;/meta&gt;
*
* @param metadataElement metadataElement
* @return Map<QName, String>
*/
private static Map<QName, String> readOtherProperties(
Element metadataElement) {
Map<QName, String> result = new HashMap<>();
NodeList metaTags = metadataElement.getElementsByTagName(OPFTags.meta);
for (int i = 0; i < metaTags.getLength(); i++) {
Node metaNode = metaTags.item(i);
Node property = metaNode.getAttributes()
.getNamedItem(OPFAttributes.property);
if (property != null) {
String name = property.getNodeValue();
String value = metaNode.getTextContent();
result.put(new QName(name), value);
}
}
return result;
}
/**
* consumes meta tags that have a property attribute as defined in the standard. For example:
* &lt;meta property="rendition:layout"&gt;pre-paginated&lt;/meta&gt;
*
* @param metadataElement metadataElement
* @return Map<String, String>
*/
private static Map<String, String> readMetaProperties(
Element metadataElement) {
Map<String, String> result = new HashMap<>();
NodeList metaTags = metadataElement.getElementsByTagName(OPFTags.meta);
for (int i = 0; i < metaTags.getLength(); i++) {
Element metaElement = (Element) metaTags.item(i);
String name = metaElement.getAttribute(OPFAttributes.name);
String value = metaElement.getAttribute(OPFAttributes.content);
result.put(name, value);
}
return result;
}
private static String getBookIdId(Document document) {
Element packageElement = DOMUtil
.getFirstElementByTagNameNS(document.getDocumentElement(),
NAMESPACE_OPF, OPFTags.packageTag);
if (packageElement == null) {
return null;
}
return DOMUtil.getAttribute(packageElement, NAMESPACE_OPF, OPFAttributes.uniqueIdentifier);
}
private static List<Author> readCreators(Element metadataElement) {
return readAuthors(DCTags.creator, metadataElement);
}
private static List<Author> readContributors(Element metadataElement) {
return readAuthors(DCTags.contributor, metadataElement);
}
private static List<Author> readAuthors(String authorTag,
Element metadataElement) {
NodeList elements = metadataElement
.getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, authorTag);
List<Author> result = new ArrayList<>(elements.getLength());
for (int i = 0; i < elements.getLength(); i++) {
Element authorElement = (Element) elements.item(i);
Author author = createAuthor(authorElement);
if (author != null) {
result.add(author);
}
}
return result;
}
private static List<Date> readDates(Element metadataElement) {
NodeList elements = metadataElement
.getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.date);
List<Date> result = new ArrayList<>(elements.getLength());
for (int i = 0; i < elements.getLength(); i++) {
Element dateElement = (Element) elements.item(i);
Date date;
try {
date = new Date(DOMUtil.getTextChildrenContent(dateElement),
DOMUtil.getAttribute(dateElement, NAMESPACE_OPF, OPFAttributes.event));
result.add(date);
} catch (IllegalArgumentException e) {
Log.e(TAG, e.getMessage());
}
}
return result;
}
private static Author createAuthor(Element authorElement) {
String authorString = DOMUtil.getTextChildrenContent(authorElement);
if (StringUtil.isBlank(authorString)) {
return null;
}
int spacePos = authorString.lastIndexOf(' ');
Author result;
if (spacePos < 0) {
result = new Author(authorString);
} else {
result = new Author(authorString.substring(0, spacePos),
authorString.substring(spacePos + 1));
}
result.setRole(
DOMUtil.getAttribute(authorElement, NAMESPACE_OPF, OPFAttributes.role));
return result;
}
private static List<Identifier> readIdentifiers(Element metadataElement) {
NodeList identifierElements = metadataElement
.getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.identifier);
if (identifierElements.getLength() == 0) {
Log.e(TAG, "Package does not contain element " + DCTags.identifier);
return new ArrayList<>();
}
String bookIdId = getBookIdId(metadataElement.getOwnerDocument());
List<Identifier> result = new ArrayList<>(
identifierElements.getLength());
for (int i = 0; i < identifierElements.getLength(); i++) {
Element identifierElement = (Element) identifierElements.item(i);
String schemeName = DOMUtil.getAttribute(identifierElement, NAMESPACE_OPF, DCAttributes.scheme);
String identifierValue = DOMUtil
.getTextChildrenContent(identifierElement);
if (StringUtil.isBlank(identifierValue)) {
continue;
}
Identifier identifier = new Identifier(schemeName, identifierValue);
if (identifierElement.getAttribute("id").equals(bookIdId)) {
identifier.setBookId(true);
}
result.add(identifier);
}
return result;
}
}
@@ -0,0 +1,187 @@
package me.ag2s.epublib.epub;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import me.ag2s.epublib.Constants;
import me.ag2s.epublib.domain.Author;
import me.ag2s.epublib.domain.Date;
import me.ag2s.epublib.domain.EpubBook;
import me.ag2s.epublib.domain.Identifier;
import me.ag2s.epublib.util.StringUtil;
public class PackageDocumentMetadataWriter extends PackageDocumentBase {
/**
* Writes the book's metadata.
*
* @param book book
* @param serializer serializer
* @throws IOException IOException
* @throws IllegalStateException IllegalStateException
* @throws IllegalArgumentException IllegalArgumentException
*/
public static void writeMetaData(EpubBook book, XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(NAMESPACE_OPF, OPFTags.metadata);
serializer.setPrefix(PREFIX_DUBLIN_CORE, NAMESPACE_DUBLIN_CORE);
serializer.setPrefix(PREFIX_OPF, NAMESPACE_OPF);
writeIdentifiers(book.getMetadata().getIdentifiers(), serializer);
writeSimpleMetdataElements(DCTags.title, book.getMetadata().getTitles(),
serializer);
writeSimpleMetdataElements(DCTags.subject, book.getMetadata().getSubjects(),
serializer);
writeSimpleMetdataElements(DCTags.description,
book.getMetadata().getDescriptions(), serializer);
writeSimpleMetdataElements(DCTags.publisher,
book.getMetadata().getPublishers(), serializer);
writeSimpleMetdataElements(DCTags.type, book.getMetadata().getTypes(),
serializer);
writeSimpleMetdataElements(DCTags.rights, book.getMetadata().getRights(),
serializer);
// write authors
for (Author author : book.getMetadata().getAuthors()) {
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.creator);
serializer.attribute(NAMESPACE_OPF, OPFAttributes.role,
author.getRelator().getCode());
serializer.attribute(NAMESPACE_OPF, OPFAttributes.file_as,
author.getLastname() + ", " + author.getFirstname());
serializer.text(author.getFirstname() + " " + author.getLastname());
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.creator);
}
// write contributors
for (Author author : book.getMetadata().getContributors()) {
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.contributor);
serializer.attribute(NAMESPACE_OPF, OPFAttributes.role,
author.getRelator().getCode());
serializer.attribute(NAMESPACE_OPF, OPFAttributes.file_as,
author.getLastname() + ", " + author.getFirstname());
serializer.text(author.getFirstname() + " " + author.getLastname());
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.contributor);
}
// write dates
for (Date date : book.getMetadata().getDates()) {
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.date);
if (date.getEvent() != null) {
serializer.attribute(NAMESPACE_OPF, OPFAttributes.event,
date.getEvent().toString());
}
serializer.text(date.getValue());
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.date);
}
// write language
if (StringUtil.isNotBlank(book.getMetadata().getLanguage())) {
serializer.startTag(NAMESPACE_DUBLIN_CORE, "language");
serializer.text(book.getMetadata().getLanguage());
serializer.endTag(NAMESPACE_DUBLIN_CORE, "language");
}
// write other properties
if (book.getMetadata().getOtherProperties() != null) {
for (Map.Entry<QName, String> mapEntry : book.getMetadata()
.getOtherProperties().entrySet()) {
serializer.startTag(mapEntry.getKey().getNamespaceURI(), OPFTags.meta);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX,
OPFAttributes.property, mapEntry.getKey().getLocalPart());
serializer.text(mapEntry.getValue());
serializer.endTag(mapEntry.getKey().getNamespaceURI(), OPFTags.meta);
}
}
// write coverimage
if (book.getCoverImage() != null) { // write the cover image
serializer.startTag(NAMESPACE_OPF, OPFTags.meta);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.name,
OPFValues.meta_cover);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.content,
book.getCoverImage().getId());
serializer.endTag(NAMESPACE_OPF, OPFTags.meta);
}
// write generator
serializer.startTag(NAMESPACE_OPF, OPFTags.meta);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.name,
OPFValues.generator);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.content,
Constants.EPUB_GENERATOR_NAME);
serializer.endTag(NAMESPACE_OPF, OPFTags.meta);
// write duokan
serializer.startTag(NAMESPACE_OPF, OPFTags.meta);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.name,
OPFValues.duokan);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.content,
Constants.EPUB_DUOKAN_NAME);
serializer.endTag(NAMESPACE_OPF, OPFTags.meta);
serializer.endTag(NAMESPACE_OPF, OPFTags.metadata);
}
private static void writeSimpleMetdataElements(String tagName,
List<String> values, XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
for (String value : values) {
if (StringUtil.isBlank(value)) {
continue;
}
serializer.startTag(NAMESPACE_DUBLIN_CORE, tagName);
serializer.text(value);
serializer.endTag(NAMESPACE_DUBLIN_CORE, tagName);
}
}
/**
* Writes out the complete list of Identifiers to the package document.
* The first identifier for which the bookId is true is made the bookId identifier.
* If no identifier has bookId == true then the first bookId identifier is written as the primary.
*
* @param identifiers identifiers
* @param serializer serializer
* @throws IllegalStateException e
* @throws IllegalArgumentException e
* @
*/
private static void writeIdentifiers(List<Identifier> identifiers,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
Identifier bookIdIdentifier = Identifier.getBookIdIdentifier(identifiers);
if (bookIdIdentifier == null) {
return;
}
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, DCAttributes.id,
BOOK_ID_ID);
serializer.attribute(NAMESPACE_OPF, OPFAttributes.scheme,
bookIdIdentifier.getScheme());
serializer.text(bookIdIdentifier.getValue());
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier);
for (Identifier identifier : identifiers.subList(1, identifiers.size())) {
if (identifier == bookIdIdentifier) {
continue;
}
serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier);
serializer.attribute(NAMESPACE_OPF, "scheme", identifier.getScheme());
serializer.text(identifier.getValue());
serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier);
}
}
}
@@ -0,0 +1,477 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import me.ag2s.epublib.Constants;
import me.ag2s.epublib.domain.EpubBook;
import me.ag2s.epublib.domain.Guide;
import me.ag2s.epublib.domain.GuideReference;
import me.ag2s.epublib.domain.MediaType;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.domain.Resources;
import me.ag2s.epublib.domain.Spine;
import me.ag2s.epublib.domain.SpineReference;
import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.util.StringUtil;
/**
* Reads the opf package document as defined by namespace http://www.idpf.org/2007/opf
*
* @author paul
*/
public class PackageDocumentReader extends PackageDocumentBase {
private static final String TAG = PackageDocumentReader.class.getName();
private static final String[] POSSIBLE_NCX_ITEM_IDS = new String[]{"toc",
"ncx", "ncxtoc", "htmltoc"};
public static void read(
Resource packageResource, EpubReader epubReader, EpubBook book,
Resources resources)
throws SAXException, IOException {
Document packageDocument = ResourceUtil.getAsDocument(packageResource);
String packageHref = packageResource.getHref();
resources = fixHrefs(packageHref, resources);
readGuide(packageDocument, epubReader, book, resources);
// Books sometimes use non-identifier ids. We map these here to legal ones
Map<String, String> idMapping = new HashMap<>();
String version = DOMUtil.getAttribute(packageDocument.getDocumentElement(), PREFIX_OPF, PackageDocumentBase.version);
resources = readManifest(packageDocument, packageHref, epubReader,
resources, idMapping);
book.setResources(resources);
book.setVersion(version);
readCover(packageDocument, book);
book.setMetadata(
PackageDocumentMetadataReader.readMetadata(packageDocument));
book.setSpine(readSpine(packageDocument, book.getResources(), idMapping));
// if we did not find a cover page then we make the first page of the book the cover page
if (book.getCoverPage() == null && book.getSpine().size() > 0) {
book.setCoverPage(book.getSpine().getResource(0));
}
}
/**
* 修复一些非标准epub格式由于 opf 文件内容不全而读取不到图片的问题
*
* @return ,修复图片路径后的一个Element列表
* @author qianfanguojin
*/
private static ArrayList<Element> ensureImageInfo(Resources resources,
Element manifestElement) {
ArrayList<Element> fixedElements = new ArrayList<>();
//加入当前所有的 item 标签
NodeList originItemElements = manifestElement
.getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.item);
for (int i = 0; i < originItemElements.getLength(); i++) {
fixedElements.add((Element) originItemElements.item(i));
}
//如果有图片资源未定义在 originItemElements ,则加入该图片信息得到 fixedElements 中
for (Resource resource : resources.getAll()) {
MediaType currentMediaType = resource.getMediaType();
if (currentMediaType == MediaTypes.JPG || currentMediaType == MediaTypes.PNG) {
String imageHref = resource.getHref();
//确保该图片信息 resource 在原 originItemElements 列表中没有出现过
boolean flag = false;
int i;
for (i = 0; i < originItemElements.getLength(); i++) {
Element itemElement = (Element) originItemElements.item(i);
String href = DOMUtil
.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.href);
try {
href = URLDecoder.decode(href, Constants.CHARACTER_ENCODING);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.getMessage());
}
if (href.equals(imageHref)) {
break;
}
}
if (i == originItemElements.getLength()) {
flag = true;
}
if (flag) {
//由于暂时无法实例化一个Element,则选择克隆一个已存在的节点来修改以达到新增 Element 的效果,作为临时解决方案
Element tempElement = (Element) manifestElement.getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.item).item(0).cloneNode(true);
tempElement.setAttribute("id", imageHref.replace("/", ""));
tempElement.setAttribute("href", imageHref);
tempElement.setAttribute("media-type", currentMediaType.getName());
fixedElements.add(tempElement);
}
}
}
return fixedElements;
}
/**
* Reads the manifest containing the resource ids, hrefs and mediatypes.
*
* @param packageDocument e
* @param packageHref e
* @param epubReader e
* @param resources e
* @param idMapping e
* @return a Map with resources, with their id's as key.
*/
@SuppressWarnings("unused")
private static Resources readManifest(Document packageDocument,
String packageHref,
EpubReader epubReader, Resources resources,
Map<String, String> idMapping) {
Element manifestElement = DOMUtil
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(),
NAMESPACE_OPF, OPFTags.manifest);
Resources result = new Resources();
if (manifestElement == null) {
Log.e(TAG,
"Package document does not contain element " + OPFTags.manifest);
return result;
}
List<Element> ensuredElements = ensureImageInfo(resources, manifestElement);
for (Element itemElement : ensuredElements) {
// Element itemElement = ;
String id = DOMUtil
.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.id);
String href = DOMUtil
.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.href);
try {
href = URLDecoder.decode(href, Constants.CHARACTER_ENCODING);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.getMessage());
}
String mediaTypeName = DOMUtil
.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.media_type);
Resource resource = resources.remove(href);
if (resource == null) {
Log.e(TAG, "resource with href '" + href + "' not found");
continue;
}
resource.setId(id);
//for epub3
String properties = DOMUtil.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.properties);
resource.setProperties(properties);
MediaType mediaType = MediaTypes.getMediaTypeByName(mediaTypeName);
if (mediaType != null) {
resource.setMediaType(mediaType);
}
result.add(resource);
idMapping.put(id, resource.getId());
}
return result;
}
/**
* Reads the book's guide.
* Here some more attempts are made at finding the cover page.
*
* @param packageDocument r
* @param epubReader r
* @param book r
* @param resources g
*/
@SuppressWarnings("unused")
private static void readGuide(Document packageDocument,
EpubReader epubReader, EpubBook book, Resources resources) {
Element guideElement = DOMUtil
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(),
NAMESPACE_OPF, OPFTags.guide);
if (guideElement == null) {
return;
}
Guide guide = book.getGuide();
NodeList guideReferences = guideElement
.getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.reference);
for (int i = 0; i < guideReferences.getLength(); i++) {
Element referenceElement = (Element) guideReferences.item(i);
String resourceHref = DOMUtil
.getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.href);
if (StringUtil.isBlank(resourceHref)) {
continue;
}
Resource resource = resources.getByHref(StringUtil
.substringBefore(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));
if (resource == null) {
Log.e(TAG, "Guide is referencing resource with href " + resourceHref
+ " which could not be found");
continue;
}
String type = DOMUtil
.getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.type);
if (StringUtil.isBlank(type)) {
Log.e(TAG, "Guide is referencing resource with href " + resourceHref
+ " which is missing the 'type' attribute");
continue;
}
String title = DOMUtil
.getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.title);
if (GuideReference.COVER.equalsIgnoreCase(type)) {
continue; // cover is handled elsewhere
}
GuideReference reference = new GuideReference(resource, type, title,
StringUtil
.substringAfter(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));
guide.addReference(reference);
}
}
/**
* Strips off the package prefixes up to the href of the packageHref.
* <p>
* Example:
* If the packageHref is "OEBPS/content.opf" then a resource href like "OEBPS/foo/bar.html" will be turned into "foo/bar.html"
*
* @param packageHref f
* @param resourcesByHref g
* @return The stripped package href
*/
static Resources fixHrefs(String packageHref,
Resources resourcesByHref) {
int lastSlashPos = packageHref.lastIndexOf('/');
if (lastSlashPos < 0) {
return resourcesByHref;
}
Resources result = new Resources();
for (Resource resource : resourcesByHref.getAll()) {
if (StringUtil.isNotBlank(resource.getHref())
&& resource.getHref().length() > lastSlashPos) {
resource.setHref(resource.getHref().substring(lastSlashPos + 1));
}
result.add(resource);
}
return result;
}
/**
* Reads the document's spine, containing all sections in reading order.
*
* @param packageDocument b
* @param resources b
* @param idMapping b
* @return the document's spine, containing all sections in reading order.
*/
private static Spine readSpine(Document packageDocument, Resources resources,
Map<String, String> idMapping) {
Element spineElement = DOMUtil
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(),
NAMESPACE_OPF, OPFTags.spine);
if (spineElement == null) {
Log.e(TAG, "Element " + OPFTags.spine
+ " not found in package document, generating one automatically");
return generateSpineFromResources(resources);
}
Spine result = new Spine();
String tocResourceId = DOMUtil.getAttribute(spineElement, NAMESPACE_OPF, OPFAttributes.toc);
Log.v(TAG, tocResourceId);
result.setTocResource(findTableOfContentsResource(tocResourceId, resources));
NodeList spineNodes = DOMUtil.getElementsByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.itemref);
if (spineNodes == null) {
Log.e(TAG, "spineNodes is null");
return result;
}
List<SpineReference> spineReferences = new ArrayList<>(spineNodes.getLength());
for (int i = 0; i < spineNodes.getLength(); i++) {
Element spineItem = (Element) spineNodes.item(i);
String itemref = DOMUtil.getAttribute(spineItem, NAMESPACE_OPF, OPFAttributes.idref);
if (StringUtil.isBlank(itemref)) {
Log.e(TAG, "itemref with missing or empty idref"); // XXX
continue;
}
String id = idMapping.get(itemref);
if (id == null) {
id = itemref;
}
Resource resource = resources.getByIdOrHref(id);
if (resource == null) {
Log.e(TAG, "resource with id '" + id + "' not found");
continue;
}
SpineReference spineReference = new SpineReference(resource);
if (OPFValues.no.equalsIgnoreCase(DOMUtil
.getAttribute(spineItem, NAMESPACE_OPF, OPFAttributes.linear))) {
spineReference.setLinear(false);
}
spineReferences.add(spineReference);
}
result.setSpineReferences(spineReferences);
return result;
}
/**
* Creates a spine out of all resources in the resources.
* The generated spine consists of all XHTML pages in order of their href.
*
* @param resources f
* @return a spine created out of all resources in the resources.
*/
private static Spine generateSpineFromResources(Resources resources) {
Spine result = new Spine();
List<String> resourceHrefs = new ArrayList<>(resources.getAllHrefs());
Collections.sort(resourceHrefs, String.CASE_INSENSITIVE_ORDER);
for (String resourceHref : resourceHrefs) {
Resource resource = resources.getByHref(resourceHref);
if (resource.getMediaType() == MediaTypes.NCX) {
result.setTocResource(resource);
} else if (resource.getMediaType() == MediaTypes.XHTML) {
result.addSpineReference(new SpineReference(resource));
}
}
return result;
}
/**
* The spine tag should contain a 'toc' attribute with as value the resource id of the table of contents resource.
* <p>
* Here we try several ways of finding this table of contents resource.
* We try the given attribute value, some often-used ones and finally look through all resources for the first resource with the table of contents mimetype.
*
* @param tocResourceId g
* @param resources g
* @return the Resource containing the table of contents
*/
static Resource findTableOfContentsResource(
String tocResourceId,
Resources resources
) {
Resource tocResource;
//一些epub3的文件为了兼容epub2,保留的epub2的目录文件,这里优先选择epub3的xml目录
tocResource = resources.getByProperties("nav");
if (tocResource != null) {
return tocResource;
}
if (StringUtil.isNotBlank(tocResourceId)) {
tocResource = resources.getByIdOrHref(tocResourceId);
}
if (tocResource != null) {
return tocResource;
}
// get the first resource with the NCX mediatype
tocResource = resources.findFirstResourceByMediaType(MediaTypes.NCX);
if (tocResource == null) {
for (String possibleNcxItemId : POSSIBLE_NCX_ITEM_IDS) {
tocResource = resources.getByIdOrHref(possibleNcxItemId);
if (tocResource != null) {
break;
}
tocResource = resources
.getByIdOrHref(possibleNcxItemId.toUpperCase());
if (tocResource != null) {
break;
}
}
}
if (tocResource == null) {
Log.e(TAG,
"Could not find table of contents resource. Tried resource with id '"
+ tocResourceId + "', " + Constants.DEFAULT_TOC_ID + ", "
+ Constants.DEFAULT_TOC_ID.toUpperCase()
+ " and any NCX resource.");
}
return tocResource;
}
/**
* Find all resources that have something to do with the coverpage and the cover image.
* Search the meta tags and the guide references
*
* @param packageDocument s
* @return all resources that have something to do with the coverpage and the cover image.
*/
// package
static Set<String> findCoverHrefs(Document packageDocument) {
Set<String> result = new HashSet<>();
// try and find a meta tag with name = 'cover' and a non-blank id
String coverResourceId = DOMUtil
.getFindAttributeValue(packageDocument, NAMESPACE_OPF,
OPFTags.meta, OPFAttributes.name, OPFValues.meta_cover,
OPFAttributes.content);
if (StringUtil.isNotBlank(coverResourceId)) {
String coverHref = DOMUtil
.getFindAttributeValue(packageDocument, NAMESPACE_OPF,
OPFTags.item, OPFAttributes.id, coverResourceId,
OPFAttributes.href);
if (StringUtil.isNotBlank(coverHref)) {
result.add(coverHref);
} else {
result.add(
coverResourceId); // maybe there was a cover href put in the cover id attribute
}
}
// try and find a reference tag with type is 'cover' and reference is not blank
String coverHref = DOMUtil
.getFindAttributeValue(packageDocument, NAMESPACE_OPF,
OPFTags.reference, OPFAttributes.type, OPFValues.reference_cover,
OPFAttributes.href);
if (StringUtil.isNotBlank(coverHref)) {
result.add(coverHref);
}
return result;
}
/**
* Finds the cover resource in the packageDocument and adds it to the book if found.
* Keeps the cover resource in the resources map
*
* @param packageDocument s
* @param book x
*/
private static void readCover(Document packageDocument, EpubBook book) {
Collection<String> coverHrefs = findCoverHrefs(packageDocument);
for (String coverHref : coverHrefs) {
Resource resource = book.getResources().getByHref(coverHref);
if (resource == null) {
Log.e(TAG, "Cover resource " + coverHref + " not found");
continue;
}
if (resource.getMediaType() == MediaTypes.XHTML) {
book.setCoverPage(resource);
} else if (MediaTypes.isBitmapImage(resource.getMediaType())) {
book.setCoverImage(resource);
}
}
}
}
@@ -0,0 +1,247 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.ag2s.epublib.Constants;
import me.ag2s.epublib.domain.EpubBook;
import me.ag2s.epublib.domain.Guide;
import me.ag2s.epublib.domain.GuideReference;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.domain.Spine;
import me.ag2s.epublib.domain.SpineReference;
import me.ag2s.epublib.util.StringUtil;
/**
* Writes the opf package document as defined by namespace http://www.idpf.org/2007/opf
*
* @author paul
*/
public class PackageDocumentWriter extends PackageDocumentBase {
private static final String TAG = PackageDocumentWriter.class.getName();
public static void write(EpubWriter epubWriter, XmlSerializer serializer,
EpubBook book) {
try {
serializer.startDocument(Constants.CHARACTER_ENCODING, false);
serializer.setPrefix(PREFIX_OPF, NAMESPACE_OPF);
serializer.setPrefix(PREFIX_DUBLIN_CORE, NAMESPACE_DUBLIN_CORE);
serializer.startTag(NAMESPACE_OPF, OPFTags.packageTag);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.version,
book.getVersion());
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX,
OPFAttributes.uniqueIdentifier, BOOK_ID_ID);
PackageDocumentMetadataWriter.writeMetaData(book, serializer);
writeManifest(book, epubWriter, serializer);
writeSpine(book, epubWriter, serializer);
writeGuide(book, epubWriter, serializer);
serializer.endTag(NAMESPACE_OPF, OPFTags.packageTag);
serializer.endDocument();
serializer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Writes the package's spine.
*
* @param book e
* @param epubWriter g
* @param serializer g
* @throws IOException g
* @throws IllegalStateException g
* @throws IllegalArgumentException 1@throws XMLStreamException
*/
@SuppressWarnings("unused")
private static void writeSpine(EpubBook book, EpubWriter epubWriter,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(NAMESPACE_OPF, OPFTags.spine);
Resource tocResource = book.getSpine().getTocResource();
String tocResourceId = tocResource.getId();
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.toc,
tocResourceId);
if (book.getCoverPage() != null // there is a cover page
&& book.getSpine().findFirstResourceById(book.getCoverPage().getId())
< 0) { // cover page is not already in the spine
// write the cover html file
serializer.startTag(NAMESPACE_OPF, OPFTags.itemref);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.idref,
book.getCoverPage().getId());
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.linear,
"no");
serializer.endTag(NAMESPACE_OPF, OPFTags.itemref);
}
writeSpineItems(book.getSpine(), serializer);
serializer.endTag(NAMESPACE_OPF, OPFTags.spine);
}
private static void writeManifest(EpubBook book, EpubWriter epubWriter,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(NAMESPACE_OPF, OPFTags.manifest);
serializer.startTag(NAMESPACE_OPF, OPFTags.item);
//For EPUB3
if (book.isEpub3()) {
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.properties, NCXDocumentV3.V3_NCX_PROPERTIES);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id, NCXDocumentV3.NCX_ITEM_ID);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href, NCXDocumentV3.DEFAULT_NCX_HREF);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type, NCXDocumentV3.V3_NCX_MEDIATYPE.getName());
} else {
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id,
epubWriter.getNcxId());
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href, epubWriter.getNcxHref());
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type, epubWriter.getNcxMediaType());
}
serializer.endTag(NAMESPACE_OPF, OPFTags.item);
// writeCoverResources(book, serializer);
for (Resource resource : getAllResourcesSortById(book)) {
writeItem(book, resource, serializer);
}
serializer.endTag(NAMESPACE_OPF, OPFTags.manifest);
}
private static List<Resource> getAllResourcesSortById(EpubBook book) {
List<Resource> allResources = new ArrayList<>(
book.getResources().getAll());
Collections.sort(allResources, (resource1, resource2) -> resource1.getId().compareToIgnoreCase(resource2.getId()));
return allResources;
}
/**
* Writes a resources as an item element
*
* @param resource g
* @param serializer g
* @throws IOException g
* @throws IllegalStateException g
* @throws IllegalArgumentException 1@throws XMLStreamException
*/
private static void writeItem(EpubBook book, Resource resource,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
if (resource == null ||
(resource.getMediaType() == MediaTypes.NCX
&& book.getSpine().getTocResource() != null)) {
return;
}
if (StringUtil.isBlank(resource.getId())) {
Log.e(TAG, "resource id must not be empty (href: " + resource.getHref()
+ ", mediatype:" + resource.getMediaType() + ")");
return;
}
if (StringUtil.isBlank(resource.getHref())) {
Log.e(TAG, "resource href must not be empty (id: " + resource.getId()
+ ", mediatype:" + resource.getMediaType() + ")");
return;
}
if (resource.getMediaType() == null) {
Log.e(TAG, "resource mediatype must not be empty (id: " + resource.getId()
+ ", href:" + resource.getHref() + ")");
return;
}
serializer.startTag(NAMESPACE_OPF, OPFTags.item);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id,
resource.getId());
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href,
resource.getHref());
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type,
resource.getMediaType().getName());
serializer.endTag(NAMESPACE_OPF, OPFTags.item);
}
/**
* List all spine references
*
* @throws IOException f
* @throws IllegalStateException f
* @throws IllegalArgumentException f
*/
@SuppressWarnings("unused")
private static void writeSpineItems(Spine spine, XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
for (SpineReference spineReference : spine.getSpineReferences()) {
serializer.startTag(NAMESPACE_OPF, OPFTags.itemref);
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.idref,
spineReference.getResourceId());
if (!spineReference.isLinear()) {
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.linear,
OPFValues.no);
}
serializer.endTag(NAMESPACE_OPF, OPFTags.itemref);
}
}
private static void writeGuide(EpubBook book, EpubWriter epubWriter,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(NAMESPACE_OPF, OPFTags.guide);
ensureCoverPageGuideReferenceWritten(book.getGuide(), epubWriter,
serializer);
for (GuideReference reference : book.getGuide().getReferences()) {
writeGuideReference(reference, serializer);
}
serializer.endTag(NAMESPACE_OPF, OPFTags.guide);
}
@SuppressWarnings("unused")
private static void ensureCoverPageGuideReferenceWritten(Guide guide,
EpubWriter epubWriter, XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
if (!(guide.getGuideReferencesByType(GuideReference.COVER).isEmpty())) {
return;
}
Resource coverPage = guide.getCoverPage();
if (coverPage != null) {
writeGuideReference(
new GuideReference(guide.getCoverPage(), GuideReference.COVER,
GuideReference.COVER), serializer);
}
}
private static void writeGuideReference(GuideReference reference,
XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
if (reference == null) {
return;
}
serializer.startTag(NAMESPACE_OPF, OPFTags.reference);
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.type,
reference.getType());
serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href,
reference.getCompleteHref());
if (StringUtil.isNotBlank(reference.getTitle())) {
serializer
.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.title,
reference.getTitle());
}
serializer.endTag(NAMESPACE_OPF, OPFTags.reference);
}
}
@@ -0,0 +1,192 @@
package me.ag2s.epublib.epub;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
import me.ag2s.epublib.domain.EpubResourceProvider;
import me.ag2s.epublib.domain.LazyResource;
import me.ag2s.epublib.domain.LazyResourceProvider;
import me.ag2s.epublib.domain.MediaType;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.domain.Resources;
import me.ag2s.epublib.util.CollectionUtil;
import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.util.zip.ZipEntryWrapper;
import me.ag2s.epublib.util.zip.ZipFileWrapper;
/**
* Loads Resources from inputStreams, ZipFiles, etc
*
* @author paul
*/
public class ResourcesLoader {
private static final String TAG = ResourcesLoader.class.getName();
/**
* Loads the entries of the zipFileWrapper as resources.
* <p>
* The MediaTypes that are in the lazyLoadedTypes will not get their
* contents loaded, but are stored as references to entries into the
* AndroidZipFile and are loaded on demand by the Resource system.
*
* @param zipFileWrapper import epub zipfile
* @param defaultHtmlEncoding epub xhtml default encoding
* @param lazyLoadedTypes lazyLoadedTypes
* @return Resources
* @throws IOException IOException
*/
public static Resources loadResources(
ZipFileWrapper zipFileWrapper,
String defaultHtmlEncoding,
List<MediaType> lazyLoadedTypes
) throws IOException {
LazyResourceProvider resourceProvider =
new EpubResourceProvider(zipFileWrapper);
Resources result = new Resources();
Enumeration entries = zipFileWrapper.entries();
while (entries.hasMoreElements()) {
ZipEntryWrapper zipEntry = new ZipEntryWrapper(entries.nextElement());
if (zipEntry == null || zipEntry.isDirectory()) {
continue;
}
String href = zipEntry.getName();
Resource resource;
if (shouldLoadLazy(href, lazyLoadedTypes)) {
resource = new LazyResource(resourceProvider, zipEntry.getSize(), href);
} else {
resource = ResourceUtil
.createResource(zipEntry.getName(), zipFileWrapper.getInputStream(zipEntry));
/*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/
if (href.endsWith("opf")) {
String string = new String(resource.getData())
.replace(" smlns=\"", " xmlns=\"")
.replace(" mlns=\"", " xmlns=\"");
resource.setData(string.getBytes());
}
}
if (resource.getMediaType() == MediaTypes.XHTML) {
resource.setInputEncoding(defaultHtmlEncoding);
}
result.add(resource);
}
return result;
}
/**
* Whether the given href will load a mediaType that is in the
* collection of lazilyLoadedMediaTypes.
*
* @param href href
* @param lazilyLoadedMediaTypes lazilyLoadedMediaTypes
* @return Whether the given href will load a mediaType that is
* in the collection of lazilyLoadedMediaTypes.
*/
private static boolean shouldLoadLazy(String href,
Collection<MediaType> lazilyLoadedMediaTypes) {
if (CollectionUtil.isEmpty(lazilyLoadedMediaTypes)) {
return false;
}
MediaType mediaType = MediaTypes.determineMediaType(href);
return lazilyLoadedMediaTypes.contains(mediaType);
}
/**
* Loads all entries from the ZipInputStream as Resources.
* <p>
* Loads the contents of all ZipEntries into memory.
* Is fast, but may lead to memory problems when reading large books
* on devices with small amounts of memory.
*
* @param zipInputStream zipInputStream
* @param defaultHtmlEncoding defaultHtmlEncoding
* @return Resources
* @throws IOException IOException
*/
public static Resources loadResources(ZipInputStream zipInputStream,
String defaultHtmlEncoding) throws IOException {
Resources result = new Resources();
ZipEntry zipEntry;
do {
// get next valid zipEntry
zipEntry = getNextZipEntry(zipInputStream);
if ((zipEntry == null) || zipEntry.isDirectory()) {
continue;
}
String href = zipEntry.getName();
// store resource
Resource resource = ResourceUtil.createResource(zipEntry.getName(), zipInputStream);
///*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/
if (href.endsWith("opf")) {
String string = new String(resource.getData())
.replace(" smlns=\"", " xmlns=\"")
.replace(" mlns=\"", " xmlns=\"");
resource.setData(string.getBytes());
}
if (resource.getMediaType() == MediaTypes.XHTML) {
resource.setInputEncoding(defaultHtmlEncoding);
}
result.add(resource);
} while (zipEntry != null);
return result;
}
private static ZipEntry getNextZipEntry(ZipInputStream zipInputStream)
throws IOException {
try {
return zipInputStream.getNextEntry();
} catch (ZipException e) {
//see <a href="https://github.com/psiegman/epublib/issues/122">Issue #122 Infinite loop</a>.
//when reading a file that is not a real zip archive or a zero length file, zipInputStream.getNextEntry()
//throws an exception and does not advance, so loadResources enters an infinite loop
//log.error("Invalid or damaged zip file.", e);
Log.e(TAG, e.getLocalizedMessage());
try {
zipInputStream.closeEntry();
} catch (Exception ignored) {
}
throw e;
}
}
/**
* Loads all entries from the ZipInputStream as Resources.
* <p>
* Loads the contents of all ZipEntries into memory.
* Is fast, but may lead to memory problems when reading large books
* on devices with small amounts of memory.
*
* @param zipFile zipFile
* @param defaultHtmlEncoding defaultHtmlEncoding
* @return Resources
* @throws IOException IOException
*/
public static Resources loadResources(ZipFileWrapper zipFile, String defaultHtmlEncoding) throws IOException {
List<MediaType> ls = new ArrayList<>();
return loadResources(zipFile, defaultHtmlEncoding, ls);
}
}