15 | * Each specific charset that can be recognized will have an instance
16 | * of some subclass of this class. All interaction between the overall
17 | * CharsetDetector and the stuff specific to an individual charset happens
18 | * via the interface provided here.
19 | *
20 | * Instances of CharsetDetector DO NOT have or maintain
21 | * state pertaining to a specific match or detect operation.
22 | * The WILL be shared by multiple instances of CharsetDetector.
23 | * They encapsulate const charset-specific information.
24 | */
25 | abstract class CharsetRecognizer {
26 | /**
27 | * Get the IANA name of this charset.
28 | *
29 | * @return the charset name.
30 | */
31 | abstract String getName();
32 |
33 | /**
34 | * Get the ISO language code for this charset.
35 | *
36 | * @return the language code, or null if the language cannot be determined.
37 | */
38 | public String getLanguage() {
39 | return null;
40 | }
41 |
42 | /**
43 | * Test the match of this charset with the input text data
44 | * which is obtained via the CharsetDetector object.
45 | *
46 | * @param det The CharsetDetector, which contains the input text
47 | * to be checked for being in this charset.
48 | * @return A CharsetMatch object containing details of match
49 | * with this charset, or null if there was no match.
50 | */
51 | abstract CharsetMatch match(CharsetDetector det);
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/model/Debug.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.model
2 |
3 | import io.legado.app.data.entities.Book
4 | import io.legado.app.data.entities.BookChapter
5 | import io.legado.app.model.webBook.WebBook
6 | import mu.KotlinLogging
7 |
8 | private val logger = KotlinLogging.logger {}
9 |
10 | object Debug : DebugLog{
11 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/model/DebugLog.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.model
2 |
3 | import io.legado.app.data.entities.Book
4 | import io.legado.app.data.entities.BookChapter
5 | import mu.KotlinLogging
6 | import okhttp3.logging.HttpLoggingInterceptor
7 |
8 | private val logger = KotlinLogging.logger {}
9 |
10 | interface DebugLog: HttpLoggingInterceptor.Logger {
11 | fun log(
12 | sourceUrl: String? = "",
13 | msg: String? = "",
14 | isHtml: Boolean = false
15 | ) {
16 | logger.info("sourceUrl: {}, msg: {}", sourceUrl, msg)
17 | }
18 |
19 | override fun log(message: String) {
20 | logger.debug(message)
21 | }
22 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/model/README.md:
--------------------------------------------------------------------------------
1 | # 放置一些模块类
2 | * analyzeRule 书源规则解析
3 | * localBook 本地书籍解析
4 | * rss 订阅规则解析
5 | * webBook 获取网络书籍
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByRegex.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.model.analyzeRule
2 |
3 | import java.util.*
4 | import java.util.regex.Pattern
5 |
6 | object AnalyzeByRegex {
7 |
8 | fun getElement(res: String, regs: Array, index: Int = 0): List? {
9 | var vIndex = index
10 | val resM = Pattern.compile(regs[vIndex]).matcher(res)
11 | if (!resM.find()) {
12 | return null
13 | }
14 | // 判断索引的规则是最后一个规则
15 | return if (vIndex + 1 == regs.size) {
16 | // 新建容器
17 | val info = arrayListOf()
18 | for (groupIndex in 0..resM.groupCount()) {
19 | info.add(resM.group(groupIndex)!!)
20 | }
21 | info
22 | } else {
23 | val result = StringBuilder()
24 | do {
25 | result.append(resM.group())
26 | } while (resM.find())
27 | getElement(result.toString(), regs, ++vIndex)
28 | }
29 | }
30 |
31 | fun getElements(res: String, regs: Array, index: Int = 0): List> {
32 | var vIndex = index
33 | val resM = Pattern.compile(regs[vIndex]).matcher(res)
34 | if (!resM.find()) {
35 | return arrayListOf()
36 | }
37 | // 判断索引的规则是最后一个规则
38 | if (vIndex + 1 == regs.size) {
39 | // 创建书息缓存数组
40 | val books = ArrayList>()
41 | // 提取列表
42 | do {
43 | // 新建容器
44 | val info = arrayListOf()
45 | for (groupIndex in 0..resM.groupCount()) {
46 | info.add(resM.group(groupIndex) ?: "")
47 | }
48 | books.add(info)
49 | } while (resM.find())
50 | return books
51 | } else {
52 | val result = StringBuilder()
53 | do {
54 | result.append(resM.group())
55 | } while (resM.find())
56 | return getElements(result.toString(), regs, ++vIndex)
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/model/analyzeRule/RuleData.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.model.analyzeRule
2 |
3 | import io.legado.app.utils.GSON
4 |
5 | class RuleData : RuleDataInterface {
6 |
7 | override val variableMap by lazy {
8 | hashMapOf()
9 | }
10 |
11 | override fun putVariable(key: String, value: String?) {
12 | if (value == null) {
13 | variableMap.remove(key)
14 | } else {
15 | variableMap[key] = value
16 | }
17 | }
18 |
19 | fun getVariable(): String? {
20 | if (variableMap.isEmpty()) {
21 | return null
22 | }
23 | return GSON.toJson(variableMap)
24 | }
25 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/model/analyzeRule/RuleDataInterface.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.model.analyzeRule
2 |
3 | interface RuleDataInterface {
4 |
5 | val variableMap: HashMap
6 |
7 | fun putVariable(key: String, value: String?)
8 |
9 | fun getVariable(key: String): String? {
10 | return variableMap[key]
11 | }
12 |
13 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/model/rss/Rss.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.model.rss
2 |
3 | import io.legado.app.data.entities.RssArticle
4 | import io.legado.app.data.entities.RssSource
5 | import io.legado.app.help.coroutine.Coroutine
6 | import io.legado.app.model.DebugLog
7 | import io.legado.app.model.analyzeRule.AnalyzeRule
8 | import io.legado.app.model.analyzeRule.AnalyzeUrl
9 | import io.legado.app.model.analyzeRule.RuleData
10 | import io.legado.app.utils.NetworkUtils
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.Dispatchers
13 | import kotlin.coroutines.CoroutineContext
14 |
15 | object Rss {
16 | suspend fun getArticles(
17 | sortName: String,
18 | sortUrl: String,
19 | rssSource: RssSource,
20 | page: Int,
21 | debugLog: DebugLog?
22 | ): Pair, String?> {
23 | val ruleData = RuleData()
24 | val analyzeUrl = AnalyzeUrl(
25 | sortUrl,
26 | page = page,
27 | source = rssSource,
28 | ruleData = ruleData,
29 | headerMapF = rssSource.getHeaderMap()
30 | )
31 | val body = analyzeUrl.getStrResponseAwait(debugLog = debugLog).body
32 | // debugLog?.log(rssSource.sourceUrl, "┌获取链接内容:${sortUrl}")
33 | // debugLog?.log(rssSource.sourceUrl, "└\n${body}")
34 | return RssParserByRule.parseXML(sortName, sortUrl, body, rssSource, ruleData, debugLog)
35 | }
36 |
37 | suspend fun getContent(
38 | rssArticle: RssArticle,
39 | ruleContent: String,
40 | rssSource: RssSource,
41 | debugLog: DebugLog?
42 | ): String {
43 | val analyzeUrl = AnalyzeUrl(
44 | rssArticle.link,
45 | baseUrl = rssArticle.origin,
46 | source = rssSource,
47 | ruleData = rssArticle,
48 | headerMapF = rssSource.getHeaderMap()
49 | )
50 | val body = analyzeUrl.getStrResponseAwait(debugLog = debugLog).body
51 | // debugLog?.log(rssSource.sourceUrl, "┌获取链接内容:${rssArticle.link}")
52 | // debugLog?.log(rssSource.sourceUrl, "└\n${body}")
53 | val analyzeRule = AnalyzeRule(rssArticle, rssSource)
54 | analyzeRule.setContent(body)
55 | .setBaseUrl(NetworkUtils.getAbsoluteURL(rssArticle.origin, rssArticle.link))
56 | return analyzeRule.getString(ruleContent)
57 | }
58 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/AnkoHelps.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | inline fun attempt(f: () -> T): AttemptResult {
4 | var value: T? = null
5 | var error: Throwable? = null
6 | try {
7 | value = f()
8 | } catch(t: Throwable) {
9 | error = t
10 | }
11 | return AttemptResult(value, error)
12 | }
13 |
14 | data class AttemptResult @PublishedApi internal constructor(val value: T?, val error: Throwable?) {
15 | inline fun then(f: (T) -> R): AttemptResult {
16 | if (isError) {
17 | @Suppress("UNCHECKED_CAST")
18 | return this as AttemptResult
19 | }
20 |
21 | return attempt { f(value as T) }
22 | }
23 |
24 | inline val isError: Boolean
25 | get() = error != null
26 |
27 | inline val hasValue: Boolean
28 | get() = error == null
29 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/EncodingDetect.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | import io.legado.app.lib.icu4j.CharsetDetector
4 | import org.jsoup.Jsoup
5 | import java.io.File
6 | import java.io.FileInputStream
7 | import java.nio.charset.StandardCharsets
8 | import java.util.*
9 |
10 | /**
11 | * 自动获取文件的编码
12 | * */
13 | @Suppress("MemberVisibilityCanBePrivate", "unused")
14 | object EncodingDetect {
15 |
16 | fun getHtmlEncode(bytes: ByteArray): String? {
17 | try {
18 | val doc = Jsoup.parse(String(bytes, StandardCharsets.UTF_8))
19 | val metaTags = doc.getElementsByTag("meta")
20 | var charsetStr: String
21 | for (metaTag in metaTags) {
22 | charsetStr = metaTag.attr("charset")
23 | if (!charsetStr.isEmpty()) {
24 | return charsetStr
25 | }
26 | val content = metaTag.attr("content")
27 | val httpEquiv = metaTag.attr("http-equiv")
28 | if (httpEquiv.lowercase(Locale.getDefault()) == "content-type") {
29 | charsetStr = if (content.lowercase(Locale.getDefault()).contains("charset")) {
30 | content.substring(
31 | content.lowercase(Locale.getDefault())
32 | .indexOf("charset") + "charset=".length
33 | )
34 | } else {
35 | content.substring(content.lowercase(Locale.getDefault()).indexOf(";") + 1)
36 | }
37 | if (!charsetStr.isEmpty()) {
38 | return charsetStr
39 | }
40 | }
41 | }
42 | } catch (ignored: Exception) {
43 | }
44 | return getEncode(bytes)
45 | }
46 |
47 | fun getEncode(bytes: ByteArray): String {
48 | val match = CharsetDetector().setText(bytes).detect()
49 | return match?.name ?: "UTF-8"
50 | }
51 |
52 | /**
53 | * 得到文件的编码
54 | */
55 | fun getEncode(filePath: String): String {
56 | return getEncode(File(filePath))
57 | }
58 |
59 | /**
60 | * 得到文件的编码
61 | */
62 | fun getEncode(file: File): String {
63 | val tempByte = getFileBytes(file)
64 | return getEncode(tempByte)
65 | }
66 |
67 | private fun getFileBytes(file: File?): ByteArray {
68 | val byteArray = ByteArray(8000)
69 | try {
70 | FileInputStream(file).use {
71 | it.read(byteArray)
72 | }
73 | } catch (e: Exception) {
74 | System.err.println("Error: $e")
75 | }
76 | return byteArray
77 | }
78 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/FileExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | import java.io.File
4 |
5 | fun File.getFile(vararg subDirFiles: String): File {
6 | val path = FileUtils.getPath(this, *subDirFiles)
7 | return File(path)
8 | }
9 |
10 | fun File.exists(vararg subDirFiles: String): Boolean {
11 | return getFile(*subDirFiles).exists()
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/HtmlFormatter.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | import io.legado.app.model.analyzeRule.AnalyzeUrl
4 | import java.net.URL
5 | import java.util.regex.Pattern
6 |
7 | object HtmlFormatter {
8 | private val wrapHtmlRegex = "?(?:div|p|br|hr|h\\d|article|dd|dl)[^>]*>".toRegex()
9 | private val commentRegex = "".toRegex() //注释
10 | private val notImgHtmlRegex = "?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
11 | private val otherHtmlRegex = "?[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
12 | private val formatImagePattern = Pattern.compile(
13 | "]*src *= *\"([^\"{]*\\{(?:[^{}]|\\{[^}]+\\})+\\})\"[^>]*>|]*data-[^=]*= *\"([^\"]*)\"[^>]*>|]*src *= *\"([^\"]*)\"[^>]*>",
14 | Pattern.CASE_INSENSITIVE
15 | )
16 |
17 | fun format(html: String?, otherRegex: Regex = otherHtmlRegex): String {
18 | html ?: return ""
19 | return html.replace(wrapHtmlRegex, "\n")
20 | .replace(commentRegex, "")
21 | .replace(otherRegex, "")
22 | .replace("\\s*\\n+\\s*".toRegex(), "\n ")
23 | .replace("^[\\n\\s]+".toRegex(), " ")
24 | .replace("[\\n\\s]+$".toRegex(), "")
25 | }
26 |
27 | fun formatKeepImg(html: String?, redirectUrl: URL? = null): String {
28 | html ?: return ""
29 | val keepImgHtml = format(html, notImgHtmlRegex)
30 |
31 | //正则的“|”处于顶端而不处于()中时,具有类似||的熔断效果,故以此机制简化原来的代码
32 | val matcher = formatImagePattern.matcher(keepImgHtml)
33 | var appendPos = 0
34 | val sb = StringBuffer()
35 | while (matcher.find()) {
36 | var param = ""
37 | sb.append(
38 | keepImgHtml.substring(appendPos, matcher.start()), ""
50 | )
51 | appendPos = matcher.end()
52 | }
53 | if (appendPos < keepImgHtml.length) sb.append(
54 | keepImgHtml.substring(
55 | appendPos,
56 | keepImgHtml.length
57 | )
58 | )
59 | return sb.toString()
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/JsonExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | import com.jayway.jsonpath.*
4 |
5 | val jsonPath: ParseContext by lazy {
6 | JsonPath.using(
7 | Configuration.builder()
8 | .options(Option.SUPPRESS_EXCEPTIONS)
9 | .build()
10 | )
11 | }
12 |
13 | fun ReadContext.readString(path: String): String? = this.read(path, String::class.java)
14 |
15 | fun ReadContext.readBool(path: String): Boolean? = this.read(path, Boolean::class.java)
16 |
17 | fun ReadContext.readInt(path: String): Int? = this.read(path, Int::class.java)
18 |
19 | fun ReadContext.readLong(path: String): Long? = this.read(path, Long::class.java)
20 |
21 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/JsoupExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | import org.jsoup.internal.StringUtil
4 | import org.jsoup.nodes.CDataNode
5 | import org.jsoup.nodes.Element
6 | import org.jsoup.nodes.Node
7 | import org.jsoup.nodes.TextNode
8 | import org.jsoup.select.NodeTraversor
9 | import org.jsoup.select.NodeVisitor
10 |
11 |
12 | fun Element.textArray(): Array {
13 | val sb = StringUtil.borrowBuilder()
14 | NodeTraversor.traverse(object : NodeVisitor {
15 | override fun head(node: Node, depth: Int) {
16 | if (node is TextNode) {
17 | appendNormalisedText(sb, node)
18 | } else if (node is Element) {
19 | if (sb.isNotEmpty() &&
20 | (node.isBlock || node.tag().name == "br") &&
21 | !lastCharIsWhitespace(sb)
22 | ) sb.append("\n")
23 | }
24 | }
25 |
26 | override fun tail(node: Node, depth: Int) {
27 | if (node is Element) {
28 | if (node.isBlock && node.nextSibling() is TextNode
29 | && !lastCharIsWhitespace(sb)
30 | ) {
31 | sb.append("\n")
32 | }
33 | }
34 | }
35 | }, this)
36 | val text = StringUtil.releaseBuilder(sb).trim { it <= ' ' }
37 | return text.splitNotBlank("\n")
38 | }
39 |
40 | private fun appendNormalisedText(sb: StringBuilder, textNode: TextNode) {
41 | val text = textNode.wholeText
42 | if (preserveWhitespace(textNode.parentNode()) || textNode is CDataNode)
43 | sb.append(text)
44 | else StringUtil.appendNormalisedWhitespace(sb, text, lastCharIsWhitespace(sb))
45 | }
46 |
47 | private fun preserveWhitespace(node: Node?): Boolean {
48 | if (node is Element) {
49 | var el = node as Element?
50 | var i = 0
51 | do {
52 | if (el!!.tag().preserveWhitespace()) return true
53 | el = el.parent()
54 | i++
55 | } while (i < 6 && el != null)
56 | }
57 | return false
58 | }
59 |
60 | private fun lastCharIsWhitespace(sb: java.lang.StringBuilder): Boolean {
61 | return sb.isNotEmpty() && sb[sb.length - 1] == ' '
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/LogUtils.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legado.app.utils
4 |
5 | fun Throwable.printOnDebug() {
6 | printStackTrace()
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/MD5Utils.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | import java.security.MessageDigest
4 | import java.security.NoSuchAlgorithmException
5 |
6 | /**
7 | * 将字符串转化为MD5
8 | */
9 |
10 | object MD5Utils {
11 |
12 | fun md5Encode(str: String?): String {
13 | if (str == null) return ""
14 | var reStr = ""
15 | try {
16 | val md5:MessageDigest = MessageDigest.getInstance("MD5")
17 | val bytes:ByteArray = md5.digest(str.toByteArray())
18 | val stringBuffer:StringBuilder = StringBuilder()
19 | for (b in bytes) {
20 | val bt:Int = b.toInt() and 0xff
21 | if (bt < 16) {
22 | stringBuffer.append(0)
23 | }
24 | stringBuffer.append(Integer.toHexString(bt))
25 | }
26 | reStr = stringBuffer.toString()
27 | } catch (e: NoSuchAlgorithmException) {
28 | e.printStackTrace()
29 | }
30 |
31 | return reStr
32 | }
33 |
34 | fun md5Encode16(str: String): String {
35 | var reStr = md5Encode(str)
36 | reStr = reStr.substring(8, 24)
37 | return reStr
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/TextUtils.java:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils;
2 |
3 | import java.util.Iterator;
4 |
5 | public class TextUtils {
6 |
7 | public static boolean isEmpty(CharSequence str) {
8 | return str == null || str.length() == 0;
9 | }
10 |
11 |
12 | /**
13 | * Returns a string containing the tokens joined by delimiters.
14 | *
15 | * @param delimiter a CharSequence that will be inserted between the tokens. If null, the string
16 | * "null" will be used as the delimiter.
17 | * @param tokens an array objects to be joined. Strings will be formed from the objects by
18 | * calling object.toString(). If tokens is null, a NullPointerException will be thrown. If
19 | * tokens is an empty array, an empty string will be returned.
20 | */
21 | public static String join(CharSequence delimiter, Object[] tokens) {
22 | final int length = tokens.length;
23 | if (length == 0) {
24 | return "";
25 | }
26 | final StringBuilder sb = new StringBuilder();
27 | sb.append(tokens[0]);
28 | for (int i = 1; i < length; i++) {
29 | sb.append(delimiter);
30 | sb.append(tokens[i]);
31 | }
32 | return sb.toString();
33 | }
34 |
35 |
36 | /**
37 | * Returns a string containing the tokens joined by delimiters.
38 | *
39 | * @param delimiter a CharSequence that will be inserted between the tokens. If null, the string
40 | * "null" will be used as the delimiter.
41 | * @param tokens an array objects to be joined. Strings will be formed from the objects by
42 | * calling object.toString(). If tokens is null, a NullPointerException will be thrown. If
43 | * tokens is empty, an empty string will be returned.
44 | */
45 | public static String join(CharSequence delimiter, Iterable tokens) {
46 | final Iterator> it = tokens.iterator();
47 | if (!it.hasNext()) {
48 | return "";
49 | }
50 | final StringBuilder sb = new StringBuilder();
51 | sb.append(it.next());
52 | while (it.hasNext()) {
53 | sb.append(delimiter);
54 | sb.append(it.next());
55 | }
56 | return sb.toString();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/ThrowableExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | val Throwable.msg: String
4 | get() {
5 | val stackTrace = stackTraceToString()
6 | val lMsg = this.localizedMessage ?: "noErrorMsg"
7 | return when {
8 | stackTrace.isNotEmpty() -> stackTrace
9 | else -> lMsg
10 | }
11 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/UTF8BOMFighter.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | object UTF8BOMFighter {
4 | private val UTF8_BOM_BYTES = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte())
5 |
6 | fun removeUTF8BOM(xmlText: String): String {
7 | val bytes = xmlText.toByteArray()
8 | val containsBOM = (bytes.size > 3
9 | && bytes[0] == UTF8_BOM_BYTES[0]
10 | && bytes[1] == UTF8_BOM_BYTES[1]
11 | && bytes[2] == UTF8_BOM_BYTES[2])
12 | if (containsBOM) {
13 | return String(bytes, 3, bytes.size - 3)
14 | }
15 | return xmlText
16 | }
17 |
18 | fun removeUTF8BOM(bytes: ByteArray): ByteArray {
19 | val containsBOM = (bytes.size > 3
20 | && bytes[0] == UTF8_BOM_BYTES[0]
21 | && bytes[1] == UTF8_BOM_BYTES[1]
22 | && bytes[2] == UTF8_BOM_BYTES[2])
23 | if (containsBOM) {
24 | val copy = ByteArray(bytes.size - 3)
25 | System.arraycopy(bytes, 3, copy, 0, bytes.size - 3)
26 | return copy
27 | }
28 | return bytes
29 | }
30 | }
--------------------------------------------------------------------------------
/src/main/java/io/legado/app/utils/Utf8BomUtils.kt:
--------------------------------------------------------------------------------
1 | package io.legado.app.utils
2 |
3 | @Suppress("unused")
4 | object Utf8BomUtils {
5 | private val UTF8_BOM_BYTES = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte())
6 |
7 | fun removeUTF8BOM(xmlText: String): String {
8 | val bytes = xmlText.toByteArray()
9 | val containsBOM = (bytes.size > 3
10 | && bytes[0] == UTF8_BOM_BYTES[0]
11 | && bytes[1] == UTF8_BOM_BYTES[1]
12 | && bytes[2] == UTF8_BOM_BYTES[2])
13 | if (containsBOM) {
14 | return String(bytes, 3, bytes.size - 3)
15 | }
16 | return xmlText
17 | }
18 |
19 | fun removeUTF8BOM(bytes: ByteArray): ByteArray {
20 | val containsBOM = (bytes.size > 3
21 | && bytes[0] == UTF8_BOM_BYTES[0]
22 | && bytes[1] == UTF8_BOM_BYTES[1]
23 | && bytes[2] == UTF8_BOM_BYTES[2])
24 | if (containsBOM) {
25 | val copy = ByteArray(bytes.size - 3)
26 | System.arraycopy(bytes, 3, copy, 0, bytes.size - 3)
27 | return copy
28 | }
29 | return bytes
30 | }
31 |
32 | fun hasBom(bytes: ByteArray): Boolean {
33 | return (bytes.size > 3
34 | && bytes[0] == UTF8_BOM_BYTES[0]
35 | && bytes[1] == UTF8_BOM_BYTES[1]
36 | && bytes[2] == UTF8_BOM_BYTES[2])
37 | }
38 | }
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/Constants.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib;
2 |
3 |
4 | public interface Constants {
5 |
6 | String CHARACTER_ENCODING = "UTF-8";
7 | String DOCTYPE_XHTML = "";
8 | String NAMESPACE_XHTML = "http://www.w3.org/1999/xhtml";
9 | String EPUB_GENERATOR_NAME = "Ag2S EpubLib";
10 | String EPUB_DUOKAN_NAME = "DK-SONGTI";
11 | char FRAGMENT_SEPARATOR_CHAR = '#';
12 | String DEFAULT_TOC_ID = "toc";
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/browsersupport/NavigationEventListener.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.browsersupport;
2 |
3 | /**
4 | * Implemented by classes that want to be notified if the user moves to
5 | * another location in the book.
6 | *
7 | * @author paul
8 | *
9 | */
10 | public interface NavigationEventListener {
11 |
12 | /**
13 | * Called whenever the user navigates to another position in the book.
14 | *
15 | * @param navigationEvent f
16 | */
17 | void navigationPerformed(NavigationEvent navigationEvent);
18 | }
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/browsersupport/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides classes that help make an epub reader application.
3 | *
4 | * These classes have no dependencies on graphic toolkits, they're purely
5 | * to help with the browsing/navigation logic.
6 | */
7 | package me.ag2s.epublib.browsersupport;
8 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/Author.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 |
4 |
5 | import me.ag2s.epublib.util.StringUtil;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * Represents one of the authors of the book
11 | *
12 | * @author paul
13 | */
14 | public class Author implements Serializable {
15 |
16 | private static final long serialVersionUID = 6663408501416574200L;
17 |
18 | private String firstname;
19 | private String lastname;
20 | private Relator relator = Relator.AUTHOR;
21 |
22 | public Author(String singleName) {
23 | this("", singleName);
24 | }
25 |
26 | public Author(String firstname, String lastname) {
27 | this.firstname = firstname;
28 | this.lastname = lastname;
29 | }
30 |
31 | public String getFirstname() {
32 | return firstname;
33 | }
34 |
35 | public void setFirstname(String firstname) {
36 | this.firstname = firstname;
37 | }
38 |
39 | public String getLastname() {
40 | return lastname;
41 | }
42 |
43 | public void setLastname(String lastname) {
44 | this.lastname = lastname;
45 | }
46 |
47 |
48 | @Override
49 | @SuppressWarnings("NullableProblems")
50 | public String toString() {
51 | return this.lastname + ", " + this.firstname;
52 | }
53 |
54 | public int hashCode() {
55 | return StringUtil.hashCode(firstname, lastname);
56 | }
57 |
58 | public boolean equals(Object authorObject) {
59 | if (!(authorObject instanceof Author)) {
60 | return false;
61 | }
62 | Author other = (Author) authorObject;
63 | return StringUtil.equals(firstname, other.firstname)
64 | && StringUtil.equals(lastname, other.lastname);
65 | }
66 |
67 | /**
68 | * 设置贡献者的角色
69 | *
70 | * @param code 角色编号
71 | */
72 |
73 | public void setRole(String code) {
74 | Relator result = Relator.byCode(code);
75 | if (result == null) {
76 | result = Relator.AUTHOR;
77 | }
78 | this.relator = result;
79 | }
80 |
81 | public Relator getRelator() {
82 | return relator;
83 | }
84 |
85 |
86 | public void setRelator(Relator relator) {
87 | this.relator = relator;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.util.zip.ZipEntry;
6 | import java.util.zip.ZipFile;
7 |
8 | /**
9 | * @author jake
10 | */
11 | public class EpubResourceProvider implements LazyResourceProvider {
12 |
13 | private final String epubFilename;
14 |
15 | /**
16 | * @param epubFilename the file name for the epub we're created from.
17 | */
18 | public EpubResourceProvider(String epubFilename) {
19 | this.epubFilename = epubFilename;
20 | }
21 |
22 | @Override
23 | public InputStream getResourceStream(String href) throws IOException {
24 | ZipFile zipFile = new ZipFile(epubFilename);
25 | ZipEntry zipEntry = zipFile.getEntry(href);
26 | if (zipEntry == null) {
27 | zipFile.close();
28 | throw new IllegalStateException(
29 | "Cannot find entry " + href + " in epub file " + epubFilename);
30 | }
31 | return new ResourceInputStream(zipFile.getInputStream(zipEntry), zipFile);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/FileResourceProvider.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 |
8 | /**
9 | * 用于创建epub,添加大文件(如大量图片)时容易OOM,使用LazyResource,避免OOM.
10 | *
11 | */
12 |
13 | public class FileResourceProvider implements LazyResourceProvider {
14 | //需要导入资源的父目录
15 | String dir;
16 |
17 | /**
18 | * 创建一个文件夹里面文件夹的LazyResourceProvider,用于LazyResource。
19 | * @param parentDir 文件的目录
20 | */
21 | public FileResourceProvider(String parentDir) {
22 | this.dir = parentDir;
23 | }
24 |
25 | /**
26 | * 创建一个文件夹里面文件夹的LazyResourceProvider,用于LazyResource。
27 | * @param parentFile 文件夹
28 | */
29 | @SuppressWarnings("unused")
30 | public FileResourceProvider(File parentFile) {
31 | this.dir = parentFile.getPath();
32 | }
33 |
34 | /**
35 | * 根据子文件名href,再父目录下读取文件获取FileInputStream
36 | * @param href 子文件名href
37 | * @return 对应href的FileInputStream
38 | * @throws IOException 抛出IOException
39 | */
40 | @Override
41 | public InputStream getResourceStream(String href) throws IOException {
42 | return new FileInputStream(new File(dir, href));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/LazyResourceProvider.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | /**
7 | * @author jake
8 | */
9 | public interface LazyResourceProvider {
10 |
11 | InputStream getResourceStream(String href) throws IOException;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/ManifestItemProperties.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 | @SuppressWarnings("unused")
3 | public enum ManifestItemProperties implements ManifestProperties {
4 | COVER_IMAGE("cover-image"),
5 | MATHML("mathml"),
6 | NAV("nav"),
7 | REMOTE_RESOURCES("remote-resources"),
8 | SCRIPTED("scripted"),
9 | SVG("svg"),
10 | SWITCH("switch");
11 |
12 | private final String name;
13 |
14 | ManifestItemProperties(String name) {
15 | this.name = name;
16 | }
17 |
18 | public String getName() {
19 | return name;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/ManifestItemRefProperties.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 | @SuppressWarnings("unused")
3 | public enum ManifestItemRefProperties implements ManifestProperties {
4 | PAGE_SPREAD_LEFT("page-spread-left"),
5 | PAGE_SPREAD_RIGHT("page-spread-right");
6 |
7 | private final String name;
8 |
9 | ManifestItemRefProperties(String name) {
10 | this.name = name;
11 | }
12 |
13 | public String getName() {
14 | return name;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/ManifestProperties.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | public interface ManifestProperties {
4 |
5 | String getName();
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/MediaType.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | import java.io.Serializable;
4 | import java.util.Arrays;
5 | import java.util.Collection;
6 |
7 | /**
8 | * MediaType is used to tell the type of content a resource is.
9 | *
10 | * Examples of mediatypes are image/gif, text/css and application/xhtml+xml
11 | *
12 | * All allowed mediaTypes are maintained bye the MediaTypeService.
13 | *
14 | * @see MediaTypes
15 | *
16 | * @author paul
17 | */
18 | public class MediaType implements Serializable {
19 |
20 | private static final long serialVersionUID = -7256091153727506788L;
21 | private final String name;
22 | private final String defaultExtension;
23 | private final Collection extensions;
24 |
25 | public MediaType(String name, String defaultExtension) {
26 | this(name, defaultExtension, new String[]{defaultExtension});
27 | }
28 |
29 | public MediaType(String name, String defaultExtension,
30 | String[] extensions) {
31 | this(name, defaultExtension, Arrays.asList(extensions));
32 | }
33 |
34 | public int hashCode() {
35 | if (name == null) {
36 | return 0;
37 | }
38 | return name.hashCode();
39 | }
40 |
41 | public MediaType(String name, String defaultExtension,
42 | Collection mextensions) {
43 | super();
44 | this.name = name;
45 | this.defaultExtension = defaultExtension;
46 | this.extensions = mextensions;
47 | }
48 |
49 | public String getName() {
50 | return name;
51 | }
52 |
53 |
54 | public String getDefaultExtension() {
55 | return defaultExtension;
56 | }
57 |
58 |
59 | public Collection getExtensions() {
60 | return extensions;
61 | }
62 |
63 | public boolean equals(Object otherMediaType) {
64 | if (!(otherMediaType instanceof MediaType)) {
65 | return false;
66 | }
67 | return name.equals(((MediaType) otherMediaType).getName());
68 | }
69 | @SuppressWarnings("NullableProblems")
70 | public String toString() {
71 | return name;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | import java.io.FilterInputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.util.zip.ZipFile;
7 |
8 | /**
9 | * A wrapper class for closing a ZipFile object when the InputStream derived
10 | * from it is closed.
11 | *
12 | * @author ttopalov
13 | */
14 | public class ResourceInputStream extends FilterInputStream {
15 |
16 | private final ZipFile zipFile;
17 |
18 | /**
19 | * Constructor.
20 | *
21 | * @param in
22 | * The InputStream object.
23 | * @param zipFile
24 | * The ZipFile object.
25 | */
26 | public ResourceInputStream(InputStream in, ZipFile zipFile) {
27 | super(in);
28 | this.zipFile = zipFile;
29 | }
30 |
31 | @Override
32 | public void close() throws IOException {
33 | super.close();
34 | zipFile.close();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/ResourceReference.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | import java.io.Serializable;
4 |
5 | public class ResourceReference implements Serializable {
6 |
7 | private static final long serialVersionUID = 2596967243557743048L;
8 |
9 | protected Resource resource;
10 |
11 | public ResourceReference(Resource resource) {
12 | this.resource = resource;
13 | }
14 |
15 |
16 | public Resource getResource() {
17 | return resource;
18 | }
19 |
20 | /**
21 | * Besides setting the resource it also sets the fragmentId to null.
22 | *
23 | * @param resource resource
24 | */
25 | public void setResource(Resource resource) {
26 | this.resource = resource;
27 | }
28 |
29 |
30 | /**
31 | * The id of the reference referred to.
32 | *
33 | * null of the reference is null or has a null id itself.
34 | *
35 | * @return The id of the reference referred to.
36 | */
37 | public String getResourceId() {
38 | if (resource != null) {
39 | return resource.getId();
40 | }
41 | return null;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/SpineReference.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | import java.io.Serializable;
4 |
5 |
6 | /**
7 | * A Section of a book.
8 | * Represents both an item in the package document and a item in the index.
9 | *
10 | * @author paul
11 | */
12 | public class SpineReference extends ResourceReference implements Serializable {
13 |
14 | private static final long serialVersionUID = -7921609197351510248L;
15 | private boolean linear;//default = true;
16 |
17 | public SpineReference(Resource resource) {
18 | this(resource, true);
19 | }
20 |
21 |
22 | public SpineReference(Resource resource, boolean linear) {
23 | super(resource);
24 | this.linear = linear;
25 | }
26 |
27 | /**
28 | * Linear denotes whether the section is Primary or Auxiliary.
29 | * Usually the cover page has linear set to false and all the other sections
30 | * have it set to true.
31 | *
32 | * It's an optional property that readers may also ignore.
33 | *
34 | *
primary or auxiliary is useful for Reading Systems which
35 | * opt to present auxiliary content differently than primary content.
36 | * For example, a Reading System might opt to render auxiliary content in
37 | * a popup window apart from the main window which presents the primary
38 | * content. (For an example of the types of content that may be considered
39 | * auxiliary, refer to the example below and the subsequent discussion.)
40 | *
41 | * @return whether the section is Primary or Auxiliary.
42 | * @see OPF Spine specification
43 | */
44 | public boolean isLinear() {
45 | return linear;
46 | }
47 |
48 | public void setLinear(boolean linear) {
49 | this.linear = linear;
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/domain/TOCReference.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.domain;
2 |
3 | import java.io.Serializable;
4 | import java.util.ArrayList;
5 | import java.util.Comparator;
6 | import java.util.List;
7 |
8 | /**
9 | * An item in the Table of Contents.
10 | *
11 | * @see TableOfContents
12 | *
13 | * @author paul
14 | */
15 | public class TOCReference extends TitledResourceReference
16 | implements Serializable {
17 |
18 | private static final long serialVersionUID = 5787958246077042456L;
19 | private List children;
20 | private static final Comparator COMPARATOR_BY_TITLE_IGNORE_CASE = (tocReference1, tocReference2) -> String.CASE_INSENSITIVE_ORDER.compare(tocReference1.getTitle(), tocReference2.getTitle());
21 | @Deprecated
22 | public TOCReference() {
23 | this(null, null, null);
24 | }
25 |
26 | public TOCReference(String name, Resource resource) {
27 | this(name, resource, null);
28 | }
29 |
30 | public TOCReference(String name, Resource resource, String fragmentId) {
31 | this(name, resource, fragmentId, new ArrayList<>());
32 | }
33 |
34 | public TOCReference(String title, Resource resource, String fragmentId,
35 | List children) {
36 | super(resource, title, fragmentId);
37 | this.children = children;
38 | }
39 | @SuppressWarnings("unused")
40 | public static Comparator getComparatorByTitleIgnoreCase() {
41 | return COMPARATOR_BY_TITLE_IGNORE_CASE;
42 | }
43 |
44 | public List getChildren() {
45 | return children;
46 | }
47 |
48 | public TOCReference addChildSection(TOCReference childSection) {
49 | this.children.add(childSection);
50 | return childSection;
51 | }
52 |
53 | public void setChildren(List children) {
54 | this.children = children;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/epub/BookProcessor.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.epub;
2 |
3 | import me.ag2s.epublib.domain.EpubBook;
4 |
5 | /**
6 | * Post-processes a book.
7 | *
8 | * Can be used to clean up a book after reading or before writing.
9 | *
10 | * @author paul
11 | */
12 | public interface BookProcessor {
13 |
14 | /**
15 | * A BookProcessor that returns the input book unchanged.
16 | */
17 | BookProcessor IDENTITY_BOOKPROCESSOR = book -> book;
18 |
19 | EpubBook processBook(EpubBook book);
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.epub;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.List;
6 |
7 | import me.ag2s.epublib.domain.EpubBook;
8 |
9 | /**
10 | * A book processor that combines several other bookprocessors
11 | *
12 | * Fixes coverpage/coverimage.
13 | * Cleans up the XHTML.
14 | *
15 | * @author paul.siegmann
16 | */
17 | @SuppressWarnings("unused declaration")
18 | public class BookProcessorPipeline implements BookProcessor {
19 |
20 | private static final String TAG= BookProcessorPipeline.class.getName();
21 | private List bookProcessors;
22 |
23 | public BookProcessorPipeline() {
24 | this(null);
25 | }
26 |
27 | public BookProcessorPipeline(List bookProcessingPipeline) {
28 | this.bookProcessors = bookProcessingPipeline;
29 | }
30 |
31 | @Override
32 | public EpubBook processBook(EpubBook book) {
33 | if (bookProcessors == null) {
34 | return book;
35 | }
36 | for (BookProcessor bookProcessor : bookProcessors) {
37 | try {
38 | book = bookProcessor.processBook(book);
39 | } catch (Exception e) {
40 | // Log.e(TAG, e.getMessage(), e);
41 | e.printStackTrace();
42 | }
43 | }
44 | return book;
45 | }
46 |
47 | public void addBookProcessor(BookProcessor bookProcessor) {
48 | if (this.bookProcessors == null) {
49 | bookProcessors = new ArrayList<>();
50 | }
51 | this.bookProcessors.add(bookProcessor);
52 | }
53 |
54 | public void addBookProcessors(Collection bookProcessors) {
55 | if (this.bookProcessors == null) {
56 | this.bookProcessors = new ArrayList<>();
57 | }
58 | this.bookProcessors.addAll(bookProcessors);
59 | }
60 |
61 |
62 | public List getBookProcessors() {
63 | return bookProcessors;
64 | }
65 |
66 |
67 | public void setBookProcessingPipeline(
68 | List bookProcessingPipeline) {
69 | this.bookProcessors = bookProcessingPipeline;
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/epub/HtmlProcessor.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.epub;
2 |
3 | import me.ag2s.epublib.domain.Resource;
4 | import java.io.OutputStream;
5 | @SuppressWarnings("unused")
6 | public interface HtmlProcessor {
7 |
8 | void processHtmlResource(Resource resource, OutputStream out);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/util/CollectionUtil.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.util;
2 |
3 | import java.util.Collection;
4 | import java.util.Enumeration;
5 | import java.util.Iterator;
6 | import java.util.List;
7 |
8 | public class CollectionUtil {
9 |
10 | /**
11 | * Wraps an Enumeration around an Iterator
12 | * @author paul.siegmann
13 | *
14 | * @param
15 | */
16 | private static class IteratorEnumerationAdapter implements Enumeration {
17 |
18 | private final Iterator iterator;
19 |
20 | public IteratorEnumerationAdapter(Iterator iter) {
21 | this.iterator = iter;
22 | }
23 |
24 | @Override
25 | public boolean hasMoreElements() {
26 | return iterator.hasNext();
27 | }
28 |
29 | @Override
30 | public T nextElement() {
31 | return iterator.next();
32 | }
33 | }
34 |
35 | /**
36 | * Creates an Enumeration out of the given Iterator.
37 | * @param g
38 | * @param it g
39 | * @return an Enumeration created out of the given Iterator.
40 | */
41 | @SuppressWarnings("unused")
42 | public static Enumeration createEnumerationFromIterator(
43 | Iterator it) {
44 | return new IteratorEnumerationAdapter<>(it);
45 | }
46 |
47 |
48 | /**
49 | * Returns the first element of the list, null if the list is null or empty.
50 | *
51 | * @param f
52 | * @param list f
53 | * @return the first element of the list, null if the list is null or empty.
54 | */
55 | public static T first(List list) {
56 | if (list == null || list.isEmpty()) {
57 | return null;
58 | }
59 | return list.get(0);
60 | }
61 |
62 | /**
63 | * Whether the given collection is null or has no elements.
64 | *
65 | * @param collection g
66 | * @return Whether the given collection is null or has no elements.
67 | */
68 | public static boolean isEmpty(Collection> collection) {
69 | return collection == null || collection.isEmpty();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/util/NoCloseOutputStream.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.util;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 |
6 | /**
7 | * OutputStream with the close() disabled.
8 | * We write multiple documents to a ZipOutputStream.
9 | * Some of the formatters call a close() after writing their data.
10 | * We don't want them to do that, so we wrap regular OutputStreams in this NoCloseOutputStream.
11 | *
12 | * @author paul
13 | */
14 | @SuppressWarnings("unused")
15 | public class NoCloseOutputStream extends OutputStream {
16 |
17 | private final OutputStream outputStream;
18 |
19 | public NoCloseOutputStream(OutputStream outputStream) {
20 | this.outputStream = outputStream;
21 | }
22 |
23 | @Override
24 | public void write(int b) throws IOException {
25 | outputStream.write(b);
26 | }
27 |
28 | /**
29 | * A close() that does not call it's parent's close()
30 | */
31 | public void close() {
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/util/NoCloseWriter.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.epublib.util;
2 |
3 | import java.io.IOException;
4 | import java.io.Writer;
5 |
6 | /**
7 | * Writer with the close() disabled.
8 | * We write multiple documents to a ZipOutputStream.
9 | * Some of the formatters call a close() after writing their data.
10 | * We don't want them to do that, so we wrap regular Writers in this NoCloseWriter.
11 | *
12 | * @author paul
13 | */
14 | @SuppressWarnings("unused")
15 | public class NoCloseWriter extends Writer {
16 |
17 | private final Writer writer;
18 |
19 | public NoCloseWriter(Writer writer) {
20 | this.writer = writer;
21 | }
22 |
23 | @Override
24 | public void close() {
25 | }
26 |
27 | @Override
28 | public void flush() throws IOException {
29 | writer.flush();
30 | }
31 |
32 | @Override
33 | public void write(char[] cbuf, int off, int len) throws IOException {
34 | writer.write(cbuf, off, len);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/epublib/util/commons/io/IOConsumer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package me.ag2s.epublib.util.commons.io;
19 |
20 | import java.io.IOException;
21 | import java.util.Objects;
22 | import java.util.function.Consumer;
23 |
24 | /**
25 | * Like {@link Consumer} but throws {@link IOException}.
26 | *
27 | * @param the type of the input to the operations.
28 | * @since 2.7
29 | */
30 | @FunctionalInterface
31 | public interface IOConsumer {
32 |
33 | /**
34 | * Performs this operation on the given argument.
35 | *
36 | * @param t the input argument
37 | * @throws IOException if an I/O error occurs.
38 | */
39 | void accept(T t) throws IOException;
40 |
41 | /**
42 | * Returns a composed {@code IoConsumer} that performs, in sequence, this operation followed by the {@code after}
43 | * operation. If performing either operation throws an exception, it is relayed to the caller of the composed
44 | * operation. If performing this operation throws an exception, the {@code after} operation will not be performed.
45 | *
46 | * @param after the operation to perform after this operation
47 | * @return a composed {@code Consumer} that performs in sequence this operation followed by the {@code after}
48 | * operation
49 | * @throws NullPointerException if {@code after} is null
50 | */
51 | @SuppressWarnings("unused")
52 | default IOConsumer andThen(final IOConsumer super T> after) {
53 | Objects.requireNonNull(after);
54 | return (final T t) -> {
55 | accept(t);
56 | after.accept(t);
57 | };
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/umdlib/domain/UmdBook.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.umdlib.domain;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 |
6 | import me.ag2s.umdlib.tool.WrapOutputStream;
7 |
8 | public class UmdBook {
9 |
10 | public int getNum() {
11 | return num;
12 | }
13 |
14 | public void setNum(int num) {
15 | this.num = num;
16 | }
17 |
18 | private int num;
19 |
20 |
21 | /** Header Part of UMD book */
22 | private UmdHeader header = new UmdHeader();
23 | /**
24 | * Detail chapters Part of UMD book
25 | * (include Titles & Contents of each chapter)
26 | */
27 | private UmdChapters chapters = new UmdChapters();
28 |
29 | /** Cover Part of UMD book (for example, and JPEG file) */
30 | private UmdCover cover = new UmdCover();
31 |
32 | /** End Part of UMD book */
33 | private UmdEnd end = new UmdEnd();
34 |
35 | /**
36 | * Build the UMD file.
37 | * @param os
38 | * @throws IOException
39 | */
40 | public void buildUmd(OutputStream os) throws IOException {
41 | WrapOutputStream wos = new WrapOutputStream(os);
42 |
43 | header.buildHeader(wos);
44 | chapters.buildChapters(wos);
45 | cover.buildCover(wos);
46 | end.buildEnd(wos);
47 | }
48 |
49 | public UmdHeader getHeader() {
50 | return header;
51 | }
52 |
53 | public void setHeader(UmdHeader header) {
54 | this.header = header;
55 | }
56 |
57 | public UmdChapters getChapters() {
58 | return chapters;
59 | }
60 |
61 | public void setChapters(UmdChapters chapters) {
62 | this.chapters = chapters;
63 | }
64 |
65 | public UmdCover getCover() {
66 | return cover;
67 | }
68 |
69 | public void setCover(UmdCover cover) {
70 | this.cover = cover;
71 | }
72 |
73 | public UmdEnd getEnd() {
74 | return end;
75 | }
76 |
77 | public void setEnd(UmdEnd end) {
78 | this.end = end;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/umdlib/domain/UmdEnd.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.umdlib.domain;
2 |
3 | import java.io.IOException;
4 |
5 | import me.ag2s.umdlib.tool.WrapOutputStream;
6 |
7 | /**
8 | * End part of UMD book, nothing to be special
9 | *
10 | * @author Ray Liang (liangguanhui@qq.com)
11 | * 2009-12-20
12 | */
13 | public class UmdEnd {
14 |
15 | public void buildEnd(WrapOutputStream wos) throws IOException {
16 | wos.writeBytes('#', 0x0C, 0, 0x01, 0x09);
17 | wos.writeInt(wos.getWritten() + 4);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/me/ag2s/umdlib/tool/WrapOutputStream.java:
--------------------------------------------------------------------------------
1 | package me.ag2s.umdlib.tool;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 |
6 | public class WrapOutputStream extends OutputStream {
7 |
8 | private OutputStream os;
9 | private int written;
10 |
11 | public WrapOutputStream(OutputStream os) {
12 | this.os = os;
13 | }
14 |
15 | private void incCount(int value) {
16 | int temp = written + value;
17 | if (temp < 0) {
18 | temp = Integer.MAX_VALUE;
19 | }
20 | written = temp;
21 | }
22 |
23 | // it is different from the writeInt of DataOutputStream
24 | public void writeInt(int v) throws IOException {
25 | os.write((v >>> 0) & 0xFF);
26 | os.write((v >>> 8) & 0xFF);
27 | os.write((v >>> 16) & 0xFF);
28 | os.write((v >>> 24) & 0xFF);
29 | incCount(4);
30 | }
31 |
32 | public void writeByte(byte b) throws IOException {
33 | write(b);
34 | }
35 |
36 | public void writeByte(int n) throws IOException {
37 | write(n);
38 | }
39 |
40 | public void writeBytes(byte ... bytes) throws IOException {
41 | write(bytes);
42 | }
43 |
44 | public void writeBytes(int ... vals) throws IOException {
45 | for (int v : vals) {
46 | write(v);
47 | }
48 | }
49 |
50 | public void write(byte[] b, int off, int len) throws IOException {
51 | os.write(b, off, len);
52 | incCount(len);
53 | }
54 |
55 | public void write(byte[] b) throws IOException {
56 | os.write(b);
57 | incCount(b.length);
58 | }
59 |
60 | public void write(int b) throws IOException {
61 | os.write(b);
62 | incCount(1);
63 | }
64 |
65 | /////////////////////////////////////////////////
66 |
67 | public void close() throws IOException {
68 | os.close();
69 | }
70 |
71 | public void flush() throws IOException {
72 | os.flush();
73 | }
74 |
75 | public boolean equals(Object obj) {
76 | return os.equals(obj);
77 | }
78 |
79 | public int hashCode() {
80 | return os.hashCode();
81 | }
82 |
83 | public String toString() {
84 | return os.toString();
85 | }
86 |
87 | public int getWritten() {
88 | return written;
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/org/kxml2/wap/Wbxml.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 | * sell copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 | * IN THE SOFTWARE. */
20 |
21 | package org.kxml2.wap;
22 |
23 |
24 | /** contains the WBXML constants */
25 |
26 |
27 | public interface Wbxml {
28 |
29 | static public final int SWITCH_PAGE = 0;
30 | static public final int END = 1;
31 | static public final int ENTITY = 2;
32 | static public final int STR_I = 3;
33 | static public final int LITERAL = 4;
34 | static public final int EXT_I_0 = 0x40;
35 | static public final int EXT_I_1 = 0x41;
36 | static public final int EXT_I_2 = 0x42;
37 | static public final int PI = 0x43;
38 | static public final int LITERAL_C = 0x44;
39 | static public final int EXT_T_0 = 0x80;
40 | static public final int EXT_T_1 = 0x81;
41 | static public final int EXT_T_2 = 0x82;
42 | static public final int STR_T = 0x83;
43 | static public final int LITERAL_A = 0x084;
44 | static public final int EXT_0 = 0x0c0;
45 | static public final int EXT_1 = 0x0c1;
46 | static public final int EXT_2 = 0x0c2;
47 | static public final int OPAQUE = 0x0c3;
48 | static public final int LITERAL_AC = 0x0c4;
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/org.xmlpull.v1.XmlPullParserFactory:
--------------------------------------------------------------------------------
1 | org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer
2 |
--------------------------------------------------------------------------------
/src/main/resources/application-prod.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | profiles:
3 | active: prod
4 |
5 | reader:
6 | app:
7 | storagePath: 'storage'
8 | showUI: false
9 | debug: false
10 | packaged: false
11 | secure: false
12 | inviteCode: ""
13 | secureKey: ""
14 | proxy: false
15 | proxyType: "HTTP"
16 | proxyHost: ""
17 | proxyPort: ""
18 | proxyUsername: ""
19 | proxyPassword: ""
20 | cacheChapterContent: true
21 | userLimit: 50
22 | userBookLimit: 200
23 | debugLog: false
24 | autoClearInactiveUser: 0
25 |
26 | server:
27 | port: 8080
28 | webUrl: http://localhost:${reader.server.port}
29 |
30 | logging:
31 | path: "./logs"
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | reader:
2 | app:
3 | storagePath: storage
4 | showUI: false
5 | debug: false
6 | packaged: false
7 | secure: false
8 | inviteCode: ""
9 | secureKey: ""
10 | proxy: false
11 | proxyType: "HTTP"
12 | proxyHost: ""
13 | proxyPort: ""
14 | proxyUsername: ""
15 | proxyPassword: ""
16 | cacheChapterContent: true
17 | userLimit: 50
18 | userBookLimit: 200
19 | debugLog: false
20 | autoClearInactiveUser: 0
21 |
22 | server:
23 | port: 8080
24 | webUrl: http://localhost:${reader.server.port}
25 |
26 | logging:
27 | path: "./logs"
--------------------------------------------------------------------------------
/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ██████ ███████ █████ ██████ ███████ ██████
2 | ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
3 | ██████ █████ ███████ ██ ██ █████ ██████
4 | ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
5 | ██ ██ ███████ ██ ██ ██████ ███████ ██ ██
6 |
7 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
35 |
36 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
29 |
30 |
31 |
32 |
33 |
35 |
36 |
37 | ]]>
38 |
39 |
40 |
45 | ]]>
46 |
47 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
28 |
29 |
30 |
34 |
35 |
36 | ]]>
37 |
38 |
39 |
45 | ]]>
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
25 |
26 |
27 |
29 |
30 |
31 | ]]>
32 |
33 |
34 |
38 | ]]>
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
25 |
26 |
27 |
31 |
32 |
33 | ]]>
34 |
35 |
36 |
40 | ]]>
41 |
42 |
43 |
46 |
47 |
48 | ]]>
49 |
50 |
51 |
55 | ]]>
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
23 |
24 |
27 | %xhtml-lat1;
28 |
29 |
32 | %xhtml-symbol;
33 |
34 |
37 | %xhtml-special;
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 | ]]>
36 |
37 |
38 |
44 | ]]>
45 |
46 |
47 |
48 |
49 |
53 |
54 |
55 | ]]>
56 |
57 |
58 |
64 | ]]>
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 | ]]>
38 |
39 |
40 |
52 | ]]>
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
24 |
25 |
30 |
31 |
32 |
34 |
35 |
36 | ]]>
37 |
38 |
39 |
49 | ]]>
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 | ]]>
36 |
37 |
38 |
42 | ]]>
43 |
44 |
45 |
46 |
47 |
51 |
52 |
53 | ]]>
54 |
55 |
56 |
60 | ]]>
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
24 |
25 |
28 |
29 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
35 |
36 |
37 |
38 |
39 |
41 |
42 |
43 | ]]>
44 |
45 |
46 |
57 | ]]>
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 | ]]>
34 |
35 |
36 |
45 | ]]>
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 | ]]>
40 |
41 |
42 |
58 | ]]>
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
34 | ]]>
35 |
36 |
37 |
46 | ]]>
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
23 |
24 |
25 |
29 | %xhtml-inlpres.mod;]]>
30 |
31 |
32 |
36 | %xhtml-blkpres.mod;]]>
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
34 | ]]>
35 |
36 |
37 |
47 | ]]>
48 |
49 |
50 |
51 |
52 |
56 |
57 |
58 | ]]>
59 |
60 |
61 |
65 | ]]>
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
23 |
24 |
27 |
28 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 | ]]>
34 |
35 |
36 |
46 | ]]>
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
23 |
24 |
25 |
29 | %xhtml-inlstruct.mod;]]>
30 |
31 |
32 |
36 | %xhtml-inlphras.mod;]]>
37 |
38 |
39 |
43 | %xhtml-blkstruct.mod;]]>
44 |
45 |
46 |
50 | %xhtml-blkphras.mod;]]>
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/main/resources/epub/chapter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Chapter
6 |
7 |
8 |
9 |
10 |