Merge pull request #957 from ag2s20150909/master

图片解码优化,epub读取导出优化。
pull/967/head
kunfei 4 years ago committed by GitHub
commit 428660171e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/build.gradle
  2. 25
      app/src/main/java/io/legado/app/help/BookHelp.kt
  3. 46
      app/src/main/java/io/legado/app/model/localBook/EpubFile.kt
  4. 7
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  5. 12
      app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt
  6. 2
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt
  7. 20
      app/src/main/java/io/legado/app/utils/BitmapUtils.kt
  8. 38
      app/src/main/java/io/legado/app/utils/FileUtils.kt
  9. 38
      epublib/src/main/java/me/ag2s/epublib/domain/FileResourceProvider.java
  10. 56
      epublib/src/main/java/me/ag2s/epublib/epub/DOMUtil.java
  11. 6
      epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java
  12. 13
      epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java
  13. 23
      epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataReader.java
  14. 59
      epublib/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java
  15. 32
      epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java
  16. 11
      epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java

@ -178,7 +178,8 @@ dependencies {
implementation 'com.github.gedoor:rhino-android:1.5' implementation 'com.github.gedoor:rhino-android:1.5'
// //
implementation 'com.squareup.okhttp3:okhttp:4.9.0' //noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.ljx.rxhttp:rxhttp:2.5.7' implementation 'com.ljx.rxhttp:rxhttp:2.5.7'
kapt 'com.ljx.rxhttp:rxhttp-compiler:2.5.7' kapt 'com.ljx.rxhttp:rxhttp-compiler:2.5.7'

@ -1,5 +1,6 @@
package io.legado.app.help package io.legado.app.help
import android.net.Uri
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
@ -55,6 +56,28 @@ object BookHelp {
} }
} }
fun getEpubFile(book: Book,): File {
val file = FileUtils.getFile(
downloadDir,
cacheFolderName,
book.getFolderName(),
"index.epubx"
)
if(!file.exists()){
val input = if (book.bookUrl.isContentScheme()) {
val uri = Uri.parse(book.bookUrl)
appCtx.contentResolver.openInputStream(uri)
} else {
File(book.bookUrl).inputStream()
}
if (input != null) {
FileUtils.writeInputStream(file, input)
}
}
return file
}
suspend fun saveContent(book: Book, bookChapter: BookChapter, content: String) { suspend fun saveContent(book: Book, bookChapter: BookChapter, content: String) {
if (content.isEmpty()) return if (content.isEmpty()) return
//保存文本 //保存文本
@ -113,7 +136,7 @@ object BookHelp {
) )
} }
private fun getImageSuffix(src: String): String { fun getImageSuffix(src: String): String {
var suffix = src.substringAfterLast(".").substringBefore(",") var suffix = src.substringAfterLast(".").substringBefore(",")
if (suffix.length > 5) { if (suffix.length > 5) {
suffix = ".jpg" suffix = ".jpg"

@ -2,16 +2,16 @@ package io.legado.app.model.localBook
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri
import android.text.TextUtils import android.text.TextUtils
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.utils.* import io.legado.app.help.BookHelp
import io.legado.app.utils.FileUtils
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.externalFilesDir
import me.ag2s.epublib.domain.EpubBook import me.ag2s.epublib.domain.EpubBook
import me.ag2s.epublib.domain.MediaTypes
import me.ag2s.epublib.domain.Resources
import me.ag2s.epublib.epub.EpubReader import me.ag2s.epublib.epub.EpubReader
import me.ag2s.epublib.util.ResourceUtil
import org.jsoup.Jsoup import org.jsoup.Jsoup
import splitties.init.appCtx import splitties.init.appCtx
import java.io.File import java.io.File
@ -20,8 +20,7 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.* import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
class EpubFile(var book: Book) { class EpubFile(var book: Book) {
@ -30,6 +29,7 @@ class EpubFile(var book: Book) {
@Synchronized @Synchronized
private fun getEFile(book: Book): EpubFile { private fun getEFile(book: Book): EpubFile {
BookHelp.getEpubFile(book)
if (eFile == null || eFile?.book?.bookUrl != book.bookUrl) { if (eFile == null || eFile?.book?.bookUrl != book.bookUrl) {
eFile = EpubFile(book) eFile = EpubFile(book)
return eFile!! return eFile!!
@ -101,32 +101,12 @@ class EpubFile(var book: Book) {
/*重写epub文件解析代码,直接读出压缩包文件生成Resources给epublib,这样的好处是可以逐一修改某些文件的格式错误*/ /*重写epub文件解析代码,直接读出压缩包文件生成Resources给epublib,这样的好处是可以逐一修改某些文件的格式错误*/
private fun readEpub(): EpubBook? { private fun readEpub(): EpubBook? {
try { try {
val input = if (book.bookUrl.isContentScheme()) {
val uri = Uri.parse(book.bookUrl) val file = BookHelp.getEpubFile(book)
appCtx.contentResolver.openInputStream(uri) //通过懒加载读取epub
} else { return EpubReader().readEpubLazy(ZipFile(file), "utf-8")
File(book.bookUrl).inputStream()
}
input ?: return null
val inZip = ZipInputStream(input)
var zipEntry: ZipEntry?
val resources = Resources()
do {
zipEntry = inZip.nextEntry
if ((zipEntry == null) || zipEntry.isDirectory || zipEntry == ZipEntry("<error>")) continue
val resource = ResourceUtil.createResource(zipEntry, inZip)
if (resource.mediaType == MediaTypes.XHTML) resource.inputEncoding = "UTF-8"
if (zipEntry.name.endsWith(".opf")) {
/*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/
val newS = String(resource.data).replace(
"\\smlns=\"http://www.idpf.org/2007/opf\"".toRegex(),
" xmlns=\"http://www.idpf.org/2007/opf\""
)
resource.data = newS.toByteArray()
}
resources.add(resource)
} while (zipEntry != null)
if (resources.size() > 0) return EpubReader().readEpub(resources)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }

@ -138,6 +138,13 @@ object LocalBook {
val bookFile = FileUtils.getFile(cacheFolder, book.originName) val bookFile = FileUtils.getFile(cacheFolder, book.originName)
bookFile.delete() bookFile.delete()
} }
if(book.isEpub()){
val bookFile=BookHelp.getEpubFile(book).parentFile
if (bookFile!=null&&bookFile.exists()){
FileUtils.delete(bookFile,true)
}
}
if (deleteOriginal) { if (deleteOriginal) {
if (book.bookUrl.isContentScheme()) { if (book.bookUrl.isContentScheme()) {

@ -24,7 +24,6 @@ import me.ag2s.epublib.epub.EpubWriter
import me.ag2s.epublib.util.ResourceUtil import me.ag2s.epublib.util.ResourceUtil
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.nio.charset.Charset import java.nio.charset.Charset
@ -184,6 +183,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
} }
private fun exportEpub(file: File, book: Book) { private fun exportEpub(file: File, book: Book) {
val filename = "${book.name} by ${book.author}.epub" val filename = "${book.name} by ${book.author}.epub"
val epubBook = EpubBook() val epubBook = EpubBook()
@ -254,9 +254,12 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
private fun setPic(src: String, book: Book, epubBook: EpubBook) { private fun setPic(src: String, book: Book, epubBook: EpubBook) {
val href = "${MD5Utils.md5Encode16(src)}${BookHelp.getImageSuffix(src)}"
val vFile = BookHelp.getImage(book, src) val vFile = BookHelp.getImage(book, src)
val fp = FileResourceProvider(vFile.parent)
if (vFile.exists()) { if (vFile.exists()) {
val img = Resource(FileInputStream(vFile), MD5Utils.md5Encode16(src) + ".jpg") val img = LazyResource(fp, href)
epubBook.resources.add(img) epubBook.resources.add(img)
} }
} }
@ -275,7 +278,10 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
matchResult.groupValues[1].let { matchResult.groupValues[1].let {
val src = NetworkUtils.getAbsoluteURL(chapter.url, it) val src = NetworkUtils.getAbsoluteURL(chapter.url, it)
setPic(src, book, epubBook) setPic(src, book, epubBook)
text1 = text1.replace(src, MD5Utils.md5Encode16(src) + ".jpg") text1 = text1.replace(
src,
"${MD5Utils.md5Encode16(src)}${BookHelp.getImageSuffix(src)}"
)
} }
} }

@ -54,7 +54,9 @@ object ImageProvider {
ChapterProvider.visibleWidth, ChapterProvider.visibleWidth,
ChapterProvider.visibleHeight ChapterProvider.visibleHeight
) )
if (bitmap != null) {
setCache(chapterIndex, src, bitmap) setCache(chapterIndex, src, bitmap)
}
bitmap bitmap
} catch (e: Exception) { } catch (e: Exception) {
null null

@ -9,6 +9,7 @@ import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur import android.renderscript.ScriptIntrinsicBlur
import android.view.View import android.view.View
import splitties.init.appCtx import splitties.init.appCtx
import java.io.FileInputStream
import java.io.IOException import java.io.IOException
import kotlin.math.* import kotlin.math.*
@ -25,11 +26,13 @@ object BitmapUtils {
* @param height 想要显示的图片的高度 * @param height 想要显示的图片的高度
* @return * @return
*/ */
fun decodeBitmap(path: String, width: Int, height: Int): Bitmap { fun decodeBitmap(path: String, width: Int, height: Int): Bitmap? {
val op = BitmapFactory.Options() val op = BitmapFactory.Options()
op.inPreferredConfig = Config.RGB_565
var ips = FileInputStream(path)
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
op.inJustDecodeBounds = true op.inJustDecodeBounds = true
BitmapFactory.decodeFile(path, op) //获取尺寸信息 BitmapFactory.decodeStream(ips, null, op)
//获取比例大小 //获取比例大小
val wRatio = ceil((op.outWidth / width).toDouble()).toInt() val wRatio = ceil((op.outWidth / width).toDouble()).toInt()
val hRatio = ceil((op.outHeight / height).toDouble()).toInt() val hRatio = ceil((op.outHeight / height).toDouble()).toInt()
@ -42,21 +45,26 @@ object BitmapUtils {
} }
} }
op.inJustDecodeBounds = false op.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, op) ips = FileInputStream(path)
return BitmapFactory.decodeStream(ips, null, op)
} }
/** 从path中获取Bitmap图片 /** 从path中获取Bitmap图片
* @param path 图片路径 * @param path 图片路径
* @return * @return
*/ */
fun decodeBitmap(path: String): Bitmap { fun decodeBitmap(path: String): Bitmap? {
val opts = BitmapFactory.Options() val opts = BitmapFactory.Options()
opts.inPreferredConfig = Config.RGB_565
var ips = FileInputStream(path)
opts.inJustDecodeBounds = true opts.inJustDecodeBounds = true
BitmapFactory.decodeFile(path, opts) BitmapFactory.decodeStream(ips, null, opts)
opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128) opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128)
opts.inJustDecodeBounds = false opts.inJustDecodeBounds = false
ips = FileInputStream(path)
return BitmapFactory.decodeFile(path, opts) return BitmapFactory.decodeStream(ips, null, opts)
} }
/** /**

@ -517,6 +517,44 @@ object FileUtils {
closeSilently(fos) closeSilently(fos)
} }
} }
/**
* 保存文件内容
*/
fun writeInputStream(filepath: String, data: InputStream): Boolean {
val file = File(filepath)
return writeInputStream(file,data)
}
/**
* 保存文件内容
*/
fun writeInputStream(file: File, data: InputStream): Boolean {
var fos: FileOutputStream? = null
return try {
if (!file.exists()) {
file.parentFile?.mkdirs()
file.createNewFile()
}
val buffer=ByteArray(1024*4)
fos = FileOutputStream(file)
while (true) {
val len = data.read(buffer, 0, buffer.size)
if (len == -1) {
break
} else {
fos.write(buffer, 0, len)
}
}
data.close()
fos.flush()
true
} catch (e: IOException) {
false
} finally {
closeSilently(fos)
}
}
/** /**
* 追加文本内容 * 追加文本内容

@ -0,0 +1,38 @@
package me.ag2s.epublib.domain;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 用于创建epub添加大文件如大量图片时容易OOM使用LazyResource避免OOM.
*
*/
public class FileResourceProvider implements LazyResourceProvider {
//需要导入资源的父目录
String dir;
/**
* 创建一个文件夹里面文件夹的LazyResourceProvider用于LazyResource
* @param dir 文件的目录
*/
public FileResourceProvider(String dir) {
this.dir = dir;
}
/**
* 创建一个文件夹里面文件夹的LazyResourceProvider用于LazyResource
* @param dirfile 文件夹
*/
@SuppressWarnings("unused")
public FileResourceProvider(File dirfile) {
this.dir = dirfile.getPath();
}
@Override
public InputStream getResourceStream(String href) throws IOException {
return new FileInputStream(new File(dir, href));
}
}

@ -1,19 +1,20 @@
package me.ag2s.epublib.epub; package me.ag2s.epublib.epub;
import me.ag2s.epublib.util.StringUtil;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.w3c.dom.Text; 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. * Utility methods for working with the DOM.
* *
* @author paul * @author paul
*
*/ */
// package // package
class DOMUtil { class DOMUtil {
@ -83,6 +84,47 @@ class DOMUtil {
return null; 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 * Gets the first element that is a child of the parentElement and has the given namespace and tagName
* *
@ -97,8 +139,8 @@ class DOMUtil {
if (nodes.getLength() != 0) { if (nodes.getLength() != 0) {
return (Element) nodes.item(0); return (Element) nodes.item(0);
} }
nodes= parentElement.getElementsByTagName(tagName); nodes = parentElement.getElementsByTagName(tagName);
if (nodes.getLength()==0){ if (nodes.getLength() == 0) {
return null; return null;
} }
return (Element) nodes.item(0); return (Element) nodes.item(0);
@ -107,7 +149,7 @@ class DOMUtil {
/** /**
* The contents of all Text nodes that are children of the given parentElement. * The contents of all Text nodes that are children of the given parentElement.
* The result is trim()-ed. * The result is trim()-ed.
* * <p>
* The reason for this more complicated procedure instead of just returning the data of the firstChild is that * 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 * when the text is Chinese characters then on Android each Characater is represented in the DOM as
* an individual Text node. * an individual Text node.

@ -148,9 +148,9 @@ public class NCXDocumentV2 {
if (resource == null) { if (resource == null) {
Log.e(TAG, "Resource with href " + href + " in NCX document not found"); Log.e(TAG, "Resource with href " + href + " in NCX document not found");
} }
Log.d(TAG, "label:" + label); Log.v(TAG, "label:" + label);
Log.d(TAG, "href:" + href); Log.v(TAG, "href:" + href);
Log.d(TAG, "fragmentId:" + fragmentId); Log.v(TAG, "fragmentId:" + fragmentId);
TOCReference result = new TOCReference(label, resource, fragmentId); TOCReference result = new TOCReference(label, resource, fragmentId);
List<TOCReference> childTOCReferences = readTOCReferences( List<TOCReference> childTOCReferences = readTOCReferences(
navpointElement.getChildNodes(), book); navpointElement.getChildNodes(), book);

@ -107,12 +107,21 @@ public class NCXDocumentV3 {
if (ncxResource == null) { if (ncxResource == null) {
return null; return null;
} }
//Log.d(TAG, ncxResource.getHref()); //一些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); Document ncxDocument = ResourceUtil.getAsDocument(ncxResource);
//Log.d(TAG, ncxDocument.getNodeName()); Log.d(TAG, ncxDocument.getNodeName());
Element navMapElement = (Element) ncxDocument.getElementsByTagName(XHTMLTgs.nav).item(0); 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); navMapElement = (Element) navMapElement.getElementsByTagName(XHTMLTgs.ol).item(0);
Log.d(TAG, navMapElement.getTagName()); Log.d(TAG, navMapElement.getTagName());

@ -22,7 +22,7 @@ import me.ag2s.epublib.util.StringUtil;
/** /**
* Reads the package document metadata. * Reads the package document metadata.
* * <p>
* In its own separate class because the PackageDocumentReader became a bit large and unwieldy. * In its own separate class because the PackageDocumentReader became a bit large and unwieldy.
* *
* @author paul * @author paul
@ -30,7 +30,7 @@ import me.ag2s.epublib.util.StringUtil;
// package // package
class PackageDocumentMetadataReader extends PackageDocumentBase { class PackageDocumentMetadataReader extends PackageDocumentBase {
private static final String TAG= PackageDocumentMetadataReader.class.getName(); private static final String TAG = PackageDocumentMetadataReader.class.getName();
public static Metadata readMetadata(Document packageDocument) { public static Metadata readMetadata(Document packageDocument) {
Metadata result = new Metadata(); Metadata result = new Metadata();
@ -38,7 +38,7 @@ class PackageDocumentMetadataReader extends PackageDocumentBase {
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(), .getFirstElementByTagNameNS(packageDocument.getDocumentElement(),
NAMESPACE_OPF, OPFTags.metadata); NAMESPACE_OPF, OPFTags.metadata);
if (metadataElement == null) { if (metadataElement == null) {
Log.e(TAG,"Package does not contain element " + OPFTags.metadata); Log.e(TAG, "Package does not contain element " + OPFTags.metadata);
return result; return result;
} }
result.setTitles(DOMUtil result.setTitles(DOMUtil
@ -78,6 +78,7 @@ class PackageDocumentMetadataReader extends PackageDocumentBase {
/** /**
* consumes meta tags that have a property attribute as defined in the standard. For example: * 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; * &lt;meta property="rendition:layout"&gt;pre-paginated&lt;/meta&gt;
*
* @param metadataElement metadataElement * @param metadataElement metadataElement
* @return Map<QName, String> * @return Map<QName, String>
*/ */
@ -103,6 +104,7 @@ class PackageDocumentMetadataReader extends PackageDocumentBase {
/** /**
* consumes meta tags that have a property attribute as defined in the standard. For example: * 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; * &lt;meta property="rendition:layout"&gt;pre-paginated&lt;/meta&gt;
*
* @param metadataElement metadataElement * @param metadataElement metadataElement
* @return Map<String, String> * @return Map<String, String>
*/ */
@ -128,8 +130,8 @@ class PackageDocumentMetadataReader extends PackageDocumentBase {
if (packageElement == null) { if (packageElement == null) {
return null; return null;
} }
return packageElement return DOMUtil.getAttribute(packageElement, NAMESPACE_OPF, OPFAttributes.uniqueIdentifier);
.getAttributeNS(NAMESPACE_OPF, OPFAttributes.uniqueIdentifier);
} }
private static List<Author> readCreators(Element metadataElement) { private static List<Author> readCreators(Element metadataElement) {
@ -165,10 +167,10 @@ class PackageDocumentMetadataReader extends PackageDocumentBase {
Date date; Date date;
try { try {
date = new Date(DOMUtil.getTextChildrenContent(dateElement), date = new Date(DOMUtil.getTextChildrenContent(dateElement),
dateElement.getAttributeNS(NAMESPACE_OPF, OPFAttributes.event)); DOMUtil.getAttribute(dateElement, NAMESPACE_OPF, OPFAttributes.event));
result.add(date); result.add(date);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.e(TAG,e.getMessage()); Log.e(TAG, e.getMessage());
} }
} }
return result; return result;
@ -189,7 +191,7 @@ class PackageDocumentMetadataReader extends PackageDocumentBase {
authorString.substring(spacePos + 1)); authorString.substring(spacePos + 1));
} }
result.setRole( result.setRole(
authorElement.getAttributeNS(NAMESPACE_OPF, OPFAttributes.role)); DOMUtil.getAttribute(authorElement, NAMESPACE_OPF, OPFAttributes.role));
return result; return result;
} }
@ -198,7 +200,7 @@ class PackageDocumentMetadataReader extends PackageDocumentBase {
NodeList identifierElements = metadataElement NodeList identifierElements = metadataElement
.getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.identifier); .getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.identifier);
if (identifierElements.getLength() == 0) { if (identifierElements.getLength() == 0) {
Log.e(TAG,"Package does not contain element " + DCTags.identifier); Log.e(TAG, "Package does not contain element " + DCTags.identifier);
return new ArrayList<>(); return new ArrayList<>();
} }
String bookIdId = getBookIdId(metadataElement.getOwnerDocument()); String bookIdId = getBookIdId(metadataElement.getOwnerDocument());
@ -206,8 +208,7 @@ class PackageDocumentMetadataReader extends PackageDocumentBase {
identifierElements.getLength()); identifierElements.getLength());
for (int i = 0; i < identifierElements.getLength(); i++) { for (int i = 0; i < identifierElements.getLength(); i++) {
Element identifierElement = (Element) identifierElements.item(i); Element identifierElement = (Element) identifierElements.item(i);
String schemeName = identifierElement String schemeName = DOMUtil.getAttribute(identifierElement, NAMESPACE_OPF, DCAttributes.scheme);
.getAttributeNS(NAMESPACE_OPF, DCAttributes.scheme);
String identifierValue = DOMUtil String identifierValue = DOMUtil
.getTextChildrenContent(identifierElement); .getTextChildrenContent(identifierElement);
if (StringUtil.isBlank(identifierValue)) { if (StringUtil.isBlank(identifierValue)) {

@ -36,13 +36,12 @@ import me.ag2s.epublib.util.StringUtil;
* Reads the opf package document as defined by namespace http://www.idpf.org/2007/opf * Reads the opf package document as defined by namespace http://www.idpf.org/2007/opf
* *
* @author paul * @author paul
*
*/ */
public class PackageDocumentReader extends PackageDocumentBase { public class PackageDocumentReader extends PackageDocumentBase {
private static final String TAG= PackageDocumentReader.class.getName(); private static final String TAG = PackageDocumentReader.class.getName();
private static final String[] POSSIBLE_NCX_ITEM_IDS = new String[]{"toc", private static final String[] POSSIBLE_NCX_ITEM_IDS = new String[]{"toc",
"ncx", "ncxtoc","htmltoc"}; "ncx", "ncxtoc", "htmltoc"};
public static void read( public static void read(
@ -56,7 +55,7 @@ public class PackageDocumentReader extends PackageDocumentBase {
// Books sometimes use non-identifier ids. We map these here to legal ones // Books sometimes use non-identifier ids. We map these here to legal ones
Map<String, String> idMapping = new HashMap<>(); Map<String, String> idMapping = new HashMap<>();
String version=DOMUtil.getAttribute(packageDocument.getDocumentElement(),PREFIX_OPF,PackageDocumentBase.version); String version = DOMUtil.getAttribute(packageDocument.getDocumentElement(), PREFIX_OPF, PackageDocumentBase.version);
resources = readManifest(packageDocument, packageHref, epubReader, resources = readManifest(packageDocument, packageHref, epubReader,
resources, idMapping); resources, idMapping);
@ -119,18 +118,18 @@ public class PackageDocumentReader extends PackageDocumentBase {
try { try {
href = URLDecoder.decode(href, Constants.CHARACTER_ENCODING); href = URLDecoder.decode(href, Constants.CHARACTER_ENCODING);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.e(TAG,e.getMessage()); Log.e(TAG, e.getMessage());
} }
String mediaTypeName = DOMUtil String mediaTypeName = DOMUtil
.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.media_type); .getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.media_type);
Resource resource = resources.remove(href); Resource resource = resources.remove(href);
if (resource == null) { if (resource == null) {
Log.e(TAG,"resource with href '" + href + "' not found"); Log.e(TAG, "resource with href '" + href + "' not found");
continue; continue;
} }
resource.setId(id); resource.setId(id);
//for epub3 //for epub3
String properties=DOMUtil.getAttribute(itemElement,NAMESPACE_OPF,OPFAttributes.properties); String properties = DOMUtil.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.properties);
resource.setProperties(properties); resource.setProperties(properties);
MediaType mediaType = MediaTypes.getMediaTypeByName(mediaTypeName); MediaType mediaType = MediaTypes.getMediaTypeByName(mediaTypeName);
@ -175,14 +174,14 @@ public class PackageDocumentReader extends PackageDocumentBase {
Resource resource = resources.getByHref(StringUtil Resource resource = resources.getByHref(StringUtil
.substringBefore(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR)); .substringBefore(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));
if (resource == null) { if (resource == null) {
Log.e(TAG,"Guide is referencing resource with href " + resourceHref Log.e(TAG, "Guide is referencing resource with href " + resourceHref
+ " which could not be found"); + " which could not be found");
continue; continue;
} }
String type = DOMUtil String type = DOMUtil
.getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.type); .getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.type);
if (StringUtil.isBlank(type)) { if (StringUtil.isBlank(type)) {
Log.e(TAG,"Guide is referencing resource with href " + resourceHref Log.e(TAG, "Guide is referencing resource with href " + resourceHref
+ " which is missing the 'type' attribute"); + " which is missing the 'type' attribute");
continue; continue;
} }
@ -201,7 +200,7 @@ public class PackageDocumentReader extends PackageDocumentBase {
/** /**
* Strips off the package prefixes up to the href of the packageHref. * Strips off the package prefixes up to the href of the packageHref.
* * <p>
* Example: * Example:
* If the packageHref is "OEBPS/content.opf" then a resource href like "OEBPS/foo/bar.html" will be turned into "foo/bar.html" * If the packageHref is "OEBPS/content.opf" then a resource href like "OEBPS/foo/bar.html" will be turned into "foo/bar.html"
* *
@ -241,31 +240,32 @@ public class PackageDocumentReader extends PackageDocumentBase {
.getFirstElementByTagNameNS(packageDocument.getDocumentElement(), .getFirstElementByTagNameNS(packageDocument.getDocumentElement(),
NAMESPACE_OPF, OPFTags.spine); NAMESPACE_OPF, OPFTags.spine);
if (spineElement == null) { if (spineElement == null) {
Log.e(TAG,"Element " + OPFTags.spine Log.e(TAG, "Element " + OPFTags.spine
+ " not found in package document, generating one automatically"); + " not found in package document, generating one automatically");
return generateSpineFromResources(resources); return generateSpineFromResources(resources);
} }
Spine result = new Spine(); Spine result = new Spine();
String tocResourceId = DOMUtil String tocResourceId = DOMUtil.getAttribute(spineElement, NAMESPACE_OPF, OPFAttributes.toc);
.getAttribute(spineElement, NAMESPACE_OPF, OPFAttributes.toc); Log.v(TAG,tocResourceId);
result result.setTocResource(findTableOfContentsResource(tocResourceId, resources));
.setTocResource(findTableOfContentsResource(tocResourceId, resources)); NodeList spineNodes = DOMUtil.getElementsByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.itemref);
NodeList spineNodes = packageDocument if(spineNodes==null){
.getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.itemref); Log.e(TAG,"spineNodes is null");
List<SpineReference> spineReferences = new ArrayList<>( return result;
spineNodes.getLength()); }
List<SpineReference> spineReferences = new ArrayList<>(spineNodes.getLength());
for (int i = 0; i < spineNodes.getLength(); i++) { for (int i = 0; i < spineNodes.getLength(); i++) {
Element spineItem = (Element) spineNodes.item(i); Element spineItem = (Element) spineNodes.item(i);
String itemref = DOMUtil String itemref = DOMUtil.getAttribute(spineItem, NAMESPACE_OPF, OPFAttributes.idref);
.getAttribute(spineItem, NAMESPACE_OPF, OPFAttributes.idref);
if (StringUtil.isBlank(itemref)) { if (StringUtil.isBlank(itemref)) {
Log.e(TAG,"itemref with missing or empty idref"); // XXX Log.e(TAG, "itemref with missing or empty idref"); // XXX
continue; continue;
} }
String id = idMapping.get(itemref); String id = idMapping.get(itemref);
if (id == null) { if (id == null) {
id = itemref; id = itemref;
} }
Resource resource = resources.getByIdOrHref(id); Resource resource = resources.getByIdOrHref(id);
if (resource == null) { if (resource == null) {
Log.e(TAG, "resource with id '" + id + "' not found"); Log.e(TAG, "resource with id '" + id + "' not found");
@ -308,7 +308,7 @@ public class PackageDocumentReader extends PackageDocumentBase {
/** /**
* The spine tag should contain a 'toc' attribute with as value the resource id of the table of contents resource. * 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. * 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. * 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.
* *
@ -318,7 +318,13 @@ public class PackageDocumentReader extends PackageDocumentBase {
*/ */
static Resource findTableOfContentsResource(String tocResourceId, static Resource findTableOfContentsResource(String tocResourceId,
Resources resources) { Resources resources) {
Resource tocResource = null; Resource tocResource;
//一些epub3的文件为了兼容epub2,保留的epub2的目录文件,这里优先选择epub3的xml目录
tocResource = resources.getByProperties("nav");
if (tocResource != null) {
return tocResource;
}
if (StringUtil.isNotBlank(tocResourceId)) { if (StringUtil.isNotBlank(tocResourceId)) {
tocResource = resources.getByIdOrHref(tocResourceId); tocResource = resources.getByIdOrHref(tocResourceId);
} }
@ -343,10 +349,7 @@ public class PackageDocumentReader extends PackageDocumentBase {
} }
} }
} }
//For EPUB3
if (tocResource==null){
tocResource=resources.getByProperties("nav");
}
if (tocResource == null) { if (tocResource == null) {
Log.e(TAG, Log.e(TAG,

@ -2,16 +2,6 @@ package me.ag2s.epublib.epub;
import android.util.Log; import android.util.Log;
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 java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -22,6 +12,16 @@ import java.util.zip.ZipException;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream; 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;
/** /**
* Loads Resources from inputStreams, ZipFiles, etc * Loads Resources from inputStreams, ZipFiles, etc
@ -72,6 +72,12 @@ public class ResourcesLoader {
} else { } else {
resource = ResourceUtil resource = ResourceUtil
.createResource(zipEntry, zipFile.getInputStream(zipEntry)); .createResource(zipEntry, zipFile.getInputStream(zipEntry));
/*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/
if (href.endsWith("opf")) {
String string = new String(resource.getData()).replace("smlns=\"", "xmlns=\"");
resource.setData(string.getBytes());
}
} }
if (resource.getMediaType() == MediaTypes.XHTML) { if (resource.getMediaType() == MediaTypes.XHTML) {
@ -123,9 +129,15 @@ public class ResourcesLoader {
if ((zipEntry == null) || zipEntry.isDirectory()) { if ((zipEntry == null) || zipEntry.isDirectory()) {
continue; continue;
} }
String href = zipEntry.getName();
// store resource // store resource
Resource resource = ResourceUtil.createResource(zipEntry, zipInputStream); Resource resource = ResourceUtil.createResource(zipEntry, zipInputStream);
///*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/
if (href.endsWith("opf")) {
String string = new String(resource.getData()).replace("smlns=\"", "xmlns=\"");
resource.setData(string.getBytes());
}
if (resource.getMediaType() == MediaTypes.XHTML) { if (resource.getMediaType() == MediaTypes.XHTML) {
resource.setInputEncoding(defaultHtmlEncoding); resource.setInputEncoding(defaultHtmlEncoding);
} }

@ -1,5 +1,7 @@
package me.ag2s.epublib.util; package me.ag2s.epublib.util;
import android.util.Log;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.EOFException; import java.io.EOFException;
@ -18,6 +20,7 @@ import java.nio.CharBuffer;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import me.ag2s.epublib.epub.PackageDocumentReader;
import me.ag2s.epublib.util.commons.io.IOConsumer; import me.ag2s.epublib.util.commons.io.IOConsumer;
/** /**
@ -28,6 +31,7 @@ import me.ag2s.epublib.util.commons.io.IOConsumer;
* and using my own implementation saves the inclusion of a 200Kb jar file. * and using my own implementation saves the inclusion of a 200Kb jar file.
*/ */
public class IOUtil { public class IOUtil {
private static final String TAG = IOUtil.class.getName();
/** /**
* Represents the end-of-file (or stream). * Represents the end-of-file (or stream).
@ -142,11 +146,7 @@ public class IOUtil {
// //
public static void copy(InputStream in, OutputStream result) throws IOException { public static void copy(InputStream in, OutputStream result) throws IOException {
int buffer=in.available(); copy(in, result,DEFAULT_BUFFER_SIZE);
if(buffer>IOUtil.DEFAULT_BUFFER_SIZE||buffer==0){
buffer=IOUtil.DEFAULT_BUFFER_SIZE;
}
copy(in, result,buffer);
} }
/** /**
@ -450,6 +450,7 @@ public class IOUtil {
output.write(buffer, 0, n); output.write(buffer, 0, n);
count += n; count += n;
} }
//input.close();
} }
return count; return count;
} }

Loading…
Cancel
Save