🚀 Add and Improve Parsers
Added new supported file extensions: ".zip", ".html" and ".htm". Improved parsers: ".epub". Partly Resolves(no ".cbz"): #48
This commit is contained in:
parent
5e2cc28c92
commit
e1fdac0f39
13 changed files with 987 additions and 536 deletions
|
|
@ -2,8 +2,11 @@ package ua.acclorite.book_story.data.parser
|
||||||
|
|
||||||
import ua.acclorite.book_story.data.parser.epub.EpubFileParser
|
import ua.acclorite.book_story.data.parser.epub.EpubFileParser
|
||||||
import ua.acclorite.book_story.data.parser.fb2.Fb2FileParser
|
import ua.acclorite.book_story.data.parser.fb2.Fb2FileParser
|
||||||
|
import ua.acclorite.book_story.data.parser.htm.HtmFileParser
|
||||||
|
import ua.acclorite.book_story.data.parser.html.HtmlFileParser
|
||||||
import ua.acclorite.book_story.data.parser.pdf.PdfFileParser
|
import ua.acclorite.book_story.data.parser.pdf.PdfFileParser
|
||||||
import ua.acclorite.book_story.data.parser.txt.TxtFileParser
|
import ua.acclorite.book_story.data.parser.txt.TxtFileParser
|
||||||
|
import ua.acclorite.book_story.data.parser.zip.ZipFileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -13,32 +16,48 @@ class FileParserImpl @Inject constructor(
|
||||||
private val txtFileParser: TxtFileParser,
|
private val txtFileParser: TxtFileParser,
|
||||||
private val pdfFileParser: PdfFileParser,
|
private val pdfFileParser: PdfFileParser,
|
||||||
private val epubFileParser: EpubFileParser,
|
private val epubFileParser: EpubFileParser,
|
||||||
private val fb2FileParser: Fb2FileParser
|
private val fb2FileParser: Fb2FileParser,
|
||||||
|
private val zipFileParser: ZipFileParser,
|
||||||
|
private val htmlFileParser: HtmlFileParser,
|
||||||
|
private val htmFileParser: HtmFileParser,
|
||||||
) : FileParser {
|
) : FileParser {
|
||||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileFormat = ".${file.name.substringAfterLast(".")}".lowercase().trim()
|
val fileFormat = ".${file.extension}".lowercase().trim()
|
||||||
|
return when (fileFormat) {
|
||||||
if (fileFormat == ".pdf") {
|
".pdf" -> {
|
||||||
return pdfFileParser.parse(file)
|
pdfFileParser.parse(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileFormat == ".epub") {
|
".epub" -> {
|
||||||
return epubFileParser.parse(file)
|
epubFileParser.parse(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileFormat == ".txt") {
|
".txt" -> {
|
||||||
return txtFileParser.parse(file)
|
txtFileParser.parse(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileFormat == ".fb2") {
|
".fb2" -> {
|
||||||
return fb2FileParser.parse(file)
|
fb2FileParser.parse(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
".zip" -> {
|
||||||
|
zipFileParser.parse(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
".html" -> {
|
||||||
|
htmlFileParser.parse(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
".htm" -> {
|
||||||
|
htmFileParser.parse(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package ua.acclorite.book_story.data.parser
|
package ua.acclorite.book_story.data.parser
|
||||||
|
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.epub.EpubTextParser
|
|
||||||
import ua.acclorite.book_story.data.parser.fb2.Fb2TextParser
|
import ua.acclorite.book_story.data.parser.fb2.Fb2TextParser
|
||||||
|
import ua.acclorite.book_story.data.parser.htm.HtmTextParser
|
||||||
|
import ua.acclorite.book_story.data.parser.html.HtmlTextParser
|
||||||
import ua.acclorite.book_story.data.parser.pdf.PdfTextParser
|
import ua.acclorite.book_story.data.parser.pdf.PdfTextParser
|
||||||
import ua.acclorite.book_story.data.parser.txt.TxtTextParser
|
import ua.acclorite.book_story.data.parser.txt.TxtTextParser
|
||||||
|
import ua.acclorite.book_story.data.parser.zip.ZipTextParser
|
||||||
import ua.acclorite.book_story.domain.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -13,8 +15,11 @@ import javax.inject.Inject
|
||||||
class TextParserImpl @Inject constructor(
|
class TextParserImpl @Inject constructor(
|
||||||
private val txtTextParser: TxtTextParser,
|
private val txtTextParser: TxtTextParser,
|
||||||
private val pdfTextParser: PdfTextParser,
|
private val pdfTextParser: PdfTextParser,
|
||||||
private val epubTextParser: EpubTextParser,
|
private val epubTextParser: HtmlTextParser,
|
||||||
private val fb2TextParser: Fb2TextParser
|
private val fb2TextParser: Fb2TextParser,
|
||||||
|
private val zipTextParser: ZipTextParser,
|
||||||
|
private val htmlTextParser: HtmlTextParser,
|
||||||
|
private val htmTextParser: HtmTextParser,
|
||||||
) : TextParser {
|
) : TextParser {
|
||||||
override suspend fun parse(file: File): Resource<List<String>> {
|
override suspend fun parse(file: File): Resource<List<String>> {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
|
|
@ -23,25 +28,38 @@ class TextParserImpl @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileFormat = ".${file.name.substringAfterLast(".")}".lowercase().trim()
|
val fileFormat = ".${file.extension}".lowercase().trim()
|
||||||
|
return when (fileFormat) {
|
||||||
if (fileFormat == ".pdf") {
|
".pdf" -> {
|
||||||
return pdfTextParser.parse(file)
|
pdfTextParser.parse(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileFormat == ".epub") {
|
".epub" -> {
|
||||||
return epubTextParser.parse(file)
|
epubTextParser.parse(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileFormat == ".txt") {
|
".txt" -> {
|
||||||
return txtTextParser.parse(file)
|
txtTextParser.parse(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileFormat == ".fb2") {
|
".fb2" -> {
|
||||||
return fb2TextParser.parse(file)
|
fb2TextParser.parse(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
".zip" -> {
|
||||||
|
zipTextParser.parse(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
".html" -> {
|
||||||
|
htmlTextParser.parse(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
".htm" -> {
|
||||||
|
htmTextParser.parse(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.graphics.BitmapFactory
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.model.Category
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
|
|
@ -36,27 +37,40 @@ class EpubFileParser @Inject constructor() : FileParser {
|
||||||
.use { it.readText() }
|
.use { it.readText() }
|
||||||
val document = Jsoup.parse(opfContent)
|
val document = Jsoup.parse(opfContent)
|
||||||
|
|
||||||
val title = document.select("metadata > dc|title").text().trim()
|
val title = document.select("metadata > dc|title").text().trim().run {
|
||||||
val author = UIText.StringValue(
|
ifBlank {
|
||||||
document.select("metadata > dc|creator").text().trim()
|
file.nameWithoutExtension.trim()
|
||||||
)
|
}
|
||||||
val description = Jsoup.parse(
|
|
||||||
document.select("metadata > dc|description").text()
|
|
||||||
).text()
|
|
||||||
|
|
||||||
var coverImagePath: String? = null
|
|
||||||
|
|
||||||
val coverId = document
|
|
||||||
.select("metadata > meta[name=cover]")
|
|
||||||
.attr("content")
|
|
||||||
if (coverId.isNotBlank()) {
|
|
||||||
coverImagePath = document
|
|
||||||
.select("manifest > item[id=$coverId]")
|
|
||||||
.attr("href")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coverImagePath.isNullOrBlank()) {
|
val author = document.select("metadata > dc|creator").text().trim().run {
|
||||||
coverImagePath = document
|
if (isBlank()) {
|
||||||
|
UIText.StringResource(R.string.unknown_author)
|
||||||
|
} else {
|
||||||
|
UIText.StringValue(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val description = Jsoup.parse(
|
||||||
|
document.select("metadata > dc|description").text()
|
||||||
|
).text().run {
|
||||||
|
ifBlank {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val coverImage = document
|
||||||
|
.select("metadata > meta[name=cover]")
|
||||||
|
.attr("content")
|
||||||
|
.run {
|
||||||
|
if (isNotBlank()) {
|
||||||
|
document
|
||||||
|
.select("manifest > item[id=$this]")
|
||||||
|
.attr("href")
|
||||||
|
.apply { if (isNotBlank()) return@run this }
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
.select("manifest > item[media-type*=image]")
|
.select("manifest > item[media-type*=image]")
|
||||||
.firstOrNull()?.attr("href")
|
.firstOrNull()?.attr("href")
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +87,7 @@ class EpubFileParser @Inject constructor() : FileParser {
|
||||||
lastOpened = null,
|
lastOpened = null,
|
||||||
category = Category.entries[0],
|
category = Category.entries[0],
|
||||||
coverImage = null
|
coverImage = null
|
||||||
) to extractCoverImageBitmap(file, coverImagePath)
|
) to extractCoverImageBitmap(file, coverImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return book
|
return book
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class Fb2FileParser @Inject constructor() : FileParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
val titleFromFile = extractElementContent(document, "book-title")
|
val titleFromFile = extractElementContent(document, "book-title")
|
||||||
val title = titleFromFile ?: file.name.dropLast(4).trim()
|
val title = titleFromFile ?: file.nameWithoutExtension.trim()
|
||||||
|
|
||||||
val authorFirstName = extractElementContent(document, "first-name")
|
val authorFirstName = extractElementContent(document, "first-name")
|
||||||
val authorLastName = extractElementContent(document, "last-name")
|
val authorLastName = extractElementContent(document, "last-name")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package ua.acclorite.book_story.data.parser.htm
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class HtmFileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
|
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||||
|
if (!file.name.endsWith(".htm", true) || !file.exists()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val document = Jsoup.parse(file)
|
||||||
|
|
||||||
|
val title = document.select("head > title").text().trim().run {
|
||||||
|
ifBlank {
|
||||||
|
file.nameWithoutExtension.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Book(
|
||||||
|
title = title,
|
||||||
|
author = UIText.StringResource(R.string.unknown_author),
|
||||||
|
description = null,
|
||||||
|
textPath = "",
|
||||||
|
scrollIndex = 0,
|
||||||
|
scrollOffset = 0,
|
||||||
|
progress = 0f,
|
||||||
|
filePath = file.path,
|
||||||
|
lastOpened = null,
|
||||||
|
category = Category.entries[0],
|
||||||
|
coverImage = null
|
||||||
|
) to null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package ua.acclorite.book_story.data.parser.htm
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
class HtmTextParser @Inject constructor() : TextParser {
|
||||||
|
|
||||||
|
override suspend fun parse(file: File): Resource<List<String>> {
|
||||||
|
if (!file.name.endsWith(".htm", true) || !file.exists()) {
|
||||||
|
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val lines = mutableListOf<String>()
|
||||||
|
|
||||||
|
val document = Jsoup.parse(file)
|
||||||
|
document.select("p").append("\n")
|
||||||
|
document
|
||||||
|
.wholeText()
|
||||||
|
.lines()
|
||||||
|
.forEach { line ->
|
||||||
|
if (line.isNotBlank()) {
|
||||||
|
lines.add(line.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
return Resource.Error(UIText.StringResource(R.string.error_file_empty))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resource.Success(lines)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return Resource.Error(
|
||||||
|
UIText.StringResource(
|
||||||
|
R.string.error_query,
|
||||||
|
e.message?.take(40)?.trim() ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package ua.acclorite.book_story.data.parser.html
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class HtmlFileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
|
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||||
|
if (!file.name.endsWith(".html", true) || !file.exists()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val document = Jsoup.parse(file)
|
||||||
|
|
||||||
|
val title = document.select("head > title").text().trim().run {
|
||||||
|
ifBlank {
|
||||||
|
file.nameWithoutExtension.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Book(
|
||||||
|
title = title,
|
||||||
|
author = UIText.StringResource(R.string.unknown_author),
|
||||||
|
description = null,
|
||||||
|
textPath = "",
|
||||||
|
scrollIndex = 0,
|
||||||
|
scrollOffset = 0,
|
||||||
|
progress = 0f,
|
||||||
|
filePath = file.path,
|
||||||
|
lastOpened = null,
|
||||||
|
category = Category.entries[0],
|
||||||
|
coverImage = null
|
||||||
|
) to null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package ua.acclorite.book_story.data.parser.html
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
class HtmlTextParser @Inject constructor() : TextParser {
|
||||||
|
|
||||||
|
override suspend fun parse(file: File): Resource<List<String>> {
|
||||||
|
if (!file.name.endsWith(".html", true) || !file.exists()) {
|
||||||
|
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val lines = mutableListOf<String>()
|
||||||
|
|
||||||
|
val document = Jsoup.parse(file)
|
||||||
|
document.select("p").append("\n")
|
||||||
|
document
|
||||||
|
.wholeText()
|
||||||
|
.lines()
|
||||||
|
.forEach { line ->
|
||||||
|
if (line.isNotBlank()) {
|
||||||
|
lines.add(line.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
return Resource.Error(UIText.StringResource(R.string.error_file_empty))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resource.Success(lines)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return Resource.Error(
|
||||||
|
UIText.StringResource(
|
||||||
|
R.string.error_query,
|
||||||
|
e.message?.take(40)?.trim() ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,10 +24,11 @@ class PdfFileParser @Inject constructor(private val application: Application) :
|
||||||
|
|
||||||
val document = PDDocument.load(file)
|
val document = PDDocument.load(file)
|
||||||
|
|
||||||
val title = document.documentInformation.title ?: file.name.dropLast(4).trim()
|
val title = document.documentInformation.title ?: file.nameWithoutExtension.trim()
|
||||||
val fileAuthor = document.documentInformation.author
|
val author = document.documentInformation.author.run {
|
||||||
val author = if (fileAuthor != null) UIText.StringValue(fileAuthor)
|
if (isNullOrBlank()) UIText.StringResource(R.string.unknown_author)
|
||||||
else UIText.StringResource(R.string.unknown_author)
|
else UIText.StringValue(this)
|
||||||
|
}
|
||||||
val description = document.documentInformation.subject ?: null
|
val description = document.documentInformation.subject ?: null
|
||||||
|
|
||||||
document.close()
|
document.close()
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class TxtFileParser @Inject constructor() : FileParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val title = file.name.trim().dropLast(4)
|
val title = file.nameWithoutExtension.trim()
|
||||||
val author = UIText.StringResource(R.string.unknown_author)
|
val author = UIText.StringResource(R.string.unknown_author)
|
||||||
|
|
||||||
return Book(
|
return Book(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
package ua.acclorite.book_story.data.parser.zip
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
|
import ua.acclorite.book_story.domain.model.Category
|
||||||
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
import java.io.File
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ZipFileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
|
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||||
|
if (!file.name.endsWith(".zip", true) || !file.exists()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var book: Pair<Book, CoverImage?>? = null
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
ZipFile(file).use { zip ->
|
||||||
|
val opfEntry = zip.entries().asSequence().find { entry ->
|
||||||
|
entry.name.endsWith(".opf")
|
||||||
|
} ?: return@withContext
|
||||||
|
|
||||||
|
val opfContent = zip
|
||||||
|
.getInputStream(opfEntry)
|
||||||
|
.bufferedReader()
|
||||||
|
.use { it.readText() }
|
||||||
|
val document = Jsoup.parse(opfContent)
|
||||||
|
|
||||||
|
val title = document.select("metadata > dc|title").text().trim().run {
|
||||||
|
ifBlank {
|
||||||
|
file.nameWithoutExtension.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val author = document.select("metadata > dc|creator").text().trim().run {
|
||||||
|
if (isBlank()) {
|
||||||
|
UIText.StringResource(R.string.unknown_author)
|
||||||
|
} else {
|
||||||
|
UIText.StringValue(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val description = Jsoup.parse(
|
||||||
|
document.select("metadata > dc|description").text()
|
||||||
|
).text().run {
|
||||||
|
ifBlank {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val coverImage = document
|
||||||
|
.select("metadata > meta[name=cover]")
|
||||||
|
.attr("content")
|
||||||
|
.run {
|
||||||
|
if (isNotBlank()) {
|
||||||
|
document
|
||||||
|
.select("manifest > item[id=$this]")
|
||||||
|
.attr("href")
|
||||||
|
.apply { if (isNotBlank()) return@run this }
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.select("manifest > item[media-type*=image]")
|
||||||
|
.firstOrNull()?.attr("href")
|
||||||
|
}
|
||||||
|
|
||||||
|
book = Book(
|
||||||
|
title = title,
|
||||||
|
author = author,
|
||||||
|
description = description,
|
||||||
|
textPath = "",
|
||||||
|
scrollIndex = 0,
|
||||||
|
scrollOffset = 0,
|
||||||
|
progress = 0f,
|
||||||
|
filePath = file.path,
|
||||||
|
lastOpened = null,
|
||||||
|
category = Category.entries[0],
|
||||||
|
coverImage = null
|
||||||
|
) to extractCoverImageBitmap(file, coverImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return book
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractCoverImageBitmap(file: File, coverImagePath: String?): Bitmap? {
|
||||||
|
if (coverImagePath.isNullOrBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
ZipFile(file).use { zip ->
|
||||||
|
zip.entries().asSequence().forEach { entry ->
|
||||||
|
if (entry.name.endsWith(coverImagePath)) {
|
||||||
|
val imageBytes = zip.getInputStream(entry).readBytes()
|
||||||
|
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package ua.acclorite.book_story.data.parser.zip
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
import java.io.File
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
class ZipTextParser @Inject constructor() : TextParser {
|
||||||
|
|
||||||
|
override suspend fun parse(file: File): Resource<List<String>> {
|
||||||
|
if (!file.name.endsWith(".zip", true) || !file.exists()) {
|
||||||
|
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val lines = mutableListOf<String>()
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
ZipFile(file).use { zip ->
|
||||||
|
zip.entries().asSequence().forEach { entry ->
|
||||||
|
if (
|
||||||
|
entry.name.endsWith(".xhtml")
|
||||||
|
|| entry.name.endsWith(".html")
|
||||||
|
|| entry.name.endsWith(".xml")
|
||||||
|
|| entry.name.endsWith(".htm")
|
||||||
|
) {
|
||||||
|
val content = zip.getInputStream(entry).bufferedReader()
|
||||||
|
.use {
|
||||||
|
it.readText()
|
||||||
|
}
|
||||||
|
|
||||||
|
val document = Jsoup.parse(content)
|
||||||
|
document.select("p").append("\n")
|
||||||
|
document
|
||||||
|
.wholeText()
|
||||||
|
.lines()
|
||||||
|
.forEach { line ->
|
||||||
|
if (line.isNotBlank()) {
|
||||||
|
lines.add(line.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
return Resource.Error(UIText.StringResource(R.string.error_file_empty))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resource.Success(lines)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return Resource.Error(
|
||||||
|
UIText.StringResource(
|
||||||
|
R.string.error_query,
|
||||||
|
e.message?.take(40)?.trim() ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,16 +27,46 @@ object Constants {
|
||||||
const val MAIN_STATE = "main_state"
|
const val MAIN_STATE = "main_state"
|
||||||
|
|
||||||
// Supported file extensions
|
// Supported file extensions
|
||||||
val EXTENSIONS = listOf(".epub", ".pdf", ".fb2", ".txt")
|
val EXTENSIONS = provideSupportedExtensions()
|
||||||
|
|
||||||
// Supported languages
|
// Supported languages
|
||||||
val LANGUAGES = listOf(
|
val LANGUAGES = provideLanguages()
|
||||||
Pair("en", "English"),
|
|
||||||
Pair("uk", "Українська"),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Navigation items for NavigationBars.
|
// Navigation items for NavigationBars.
|
||||||
val NAVIGATION_ITEMS = listOf(
|
val NAVIGATION_ITEMS = provideNavigationItems()
|
||||||
|
|
||||||
|
// Supported themes
|
||||||
|
val THEMES = provideThemes()
|
||||||
|
|
||||||
|
// Credits
|
||||||
|
val CREDITS = provideCredits()
|
||||||
|
|
||||||
|
// Help Tips
|
||||||
|
val HELP_TIPS = provideHelpTips()
|
||||||
|
|
||||||
|
// About Badges
|
||||||
|
val ABOUT_BADGES = provideAboutBadges()
|
||||||
|
|
||||||
|
// Fonts for Reader
|
||||||
|
val FONTS = provideFonts()
|
||||||
|
|
||||||
|
// Empty Book (Used for Default Screen values)
|
||||||
|
val EMPTY_BOOK = provideEmptyBook()
|
||||||
|
|
||||||
|
// Default Color Preset (Used when creating new color presets)
|
||||||
|
val DEFAULT_COLOR_PRESET = provideDefaultColorPreset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideSupportedExtensions() = listOf(
|
||||||
|
".epub", ".pdf", ".fb2", ".txt", ".zip", ".html", ".htm"
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun provideLanguages() = listOf(
|
||||||
|
Pair("en", "English"),
|
||||||
|
Pair("uk", "Українська"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun provideNavigationItems() = listOf(
|
||||||
NavigationItem(
|
NavigationItem(
|
||||||
screen = Screen.Library,
|
screen = Screen.Library,
|
||||||
title = R.string.library_screen,
|
title = R.string.library_screen,
|
||||||
|
|
@ -58,10 +88,9 @@ object Constants {
|
||||||
selectedIcon = R.drawable.browse_screen_filled,
|
selectedIcon = R.drawable.browse_screen_filled,
|
||||||
unselectedIcon = R.drawable.browse_screen_outlined
|
unselectedIcon = R.drawable.browse_screen_outlined
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Supported themes
|
private fun provideThemes() = listOf(
|
||||||
val THEMES = listOf(
|
|
||||||
Pair(Theme.DYNAMIC, UIText.StringResource(R.string.dynamic_theme)),
|
Pair(Theme.DYNAMIC, UIText.StringResource(R.string.dynamic_theme)),
|
||||||
Pair(Theme.BLUE, UIText.StringResource(R.string.blue_theme)),
|
Pair(Theme.BLUE, UIText.StringResource(R.string.blue_theme)),
|
||||||
Pair(Theme.GREEN, UIText.StringResource(R.string.green_theme)),
|
Pair(Theme.GREEN, UIText.StringResource(R.string.green_theme)),
|
||||||
|
|
@ -70,10 +99,9 @@ object Constants {
|
||||||
Pair(Theme.PINK, UIText.StringResource(R.string.pink_theme)),
|
Pair(Theme.PINK, UIText.StringResource(R.string.pink_theme)),
|
||||||
Pair(Theme.YELLOW, UIText.StringResource(R.string.yellow_theme)),
|
Pair(Theme.YELLOW, UIText.StringResource(R.string.yellow_theme)),
|
||||||
Pair(Theme.AQUA, UIText.StringResource(R.string.aqua_theme)),
|
Pair(Theme.AQUA, UIText.StringResource(R.string.aqua_theme)),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Credits
|
private fun provideCredits() = listOf(
|
||||||
val CREDITS = listOf(
|
|
||||||
Credit(
|
Credit(
|
||||||
name = "Tachiyomi (Mihon)",
|
name = "Tachiyomi (Mihon)",
|
||||||
source = "GitHub",
|
source = "GitHub",
|
||||||
|
|
@ -99,10 +127,9 @@ object Constants {
|
||||||
),
|
),
|
||||||
website = "https://fonts.google.com/icons"
|
website = "https://fonts.google.com/icons"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Help Tips
|
private fun provideHelpTips() = listOf(
|
||||||
val HELP_TIPS = listOf(
|
|
||||||
HelpTip(
|
HelpTip(
|
||||||
title = R.string.help_title_how_to_find_books,
|
title = R.string.help_title_how_to_find_books,
|
||||||
description = { _, _ ->
|
description = { _, _ ->
|
||||||
|
|
@ -329,10 +356,9 @@ object Constants {
|
||||||
append(stringResource(id = R.string.help_desc_how_to_use_color_presets_1))
|
append(stringResource(id = R.string.help_desc_how_to_use_color_presets_1))
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// About Badges
|
private fun provideAboutBadges() = listOf(
|
||||||
val ABOUT_BADGES = listOf(
|
|
||||||
Badge(
|
Badge(
|
||||||
id = "x",
|
id = "x",
|
||||||
drawable = R.drawable.x_logo,
|
drawable = R.drawable.x_logo,
|
||||||
|
|
@ -368,10 +394,9 @@ object Constants {
|
||||||
contentDescription = R.string.github_profile_content_desc,
|
contentDescription = R.string.github_profile_content_desc,
|
||||||
url = "https://www.github.com/Acclorite"
|
url = "https://www.github.com/Acclorite"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fonts for Reader
|
private fun provideFonts() = listOf(
|
||||||
val FONTS = listOf(
|
|
||||||
FontWithName(
|
FontWithName(
|
||||||
"default",
|
"default",
|
||||||
UIText.StringResource(R.string.default_font),
|
UIText.StringResource(R.string.default_font),
|
||||||
|
|
@ -496,9 +521,9 @@ object Constants {
|
||||||
Font(R.font.lora_regular_italic, style = FontStyle.Italic)
|
Font(R.font.lora_regular_italic, style = FontStyle.Italic)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val EMPTY_BOOK = Book(
|
private fun provideEmptyBook() = Book(
|
||||||
id = -1,
|
id = -1,
|
||||||
title = "",
|
title = "",
|
||||||
author = UIText.StringValue(""),
|
author = UIText.StringValue(""),
|
||||||
|
|
@ -511,13 +536,12 @@ object Constants {
|
||||||
progress = 0f,
|
progress = 0f,
|
||||||
lastOpened = null,
|
lastOpened = null,
|
||||||
category = Category.READING
|
category = Category.READING
|
||||||
)
|
)
|
||||||
|
|
||||||
val DEFAULT_COLOR_PRESET = ColorPreset(
|
private fun provideDefaultColorPreset() = ColorPreset(
|
||||||
id = -1,
|
id = -1,
|
||||||
name = null,
|
name = null,
|
||||||
backgroundColor = Color.DarkGray,
|
backgroundColor = Color.DarkGray,
|
||||||
fontColor = Color.LightGray,
|
fontColor = Color.LightGray,
|
||||||
isSelected = false
|
isSelected = false
|
||||||
)
|
)
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue