diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5f53aad4..c72bc2f5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -137,9 +137,6 @@ dependencies { implementation("com.tom-roush:pdfbox-android:2.0.27.0") // Epub parser - implementation("com.positiondev.epublib:epublib-core:3.1") { - exclude("xmlpull") - } implementation("org.jsoup:jsoup:1.18.1") // Fb2 parser diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt index 1c051501..8d2d5201 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt @@ -1,16 +1,17 @@ package ua.acclorite.book_story.data.parser.epub +import android.graphics.Bitmap import android.graphics.BitmapFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import nl.siegmann.epublib.epub.EpubReader +import org.jsoup.Jsoup 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.io.FileInputStream +import java.util.zip.ZipFile import javax.inject.Inject class EpubFileParser @Inject constructor() : FileParser { @@ -21,45 +22,83 @@ class EpubFileParser @Inject constructor() : FileParser { } try { - val epubReader = EpubReader() - val book = withContext(Dispatchers.IO) { - FileInputStream(file).use { - epubReader.readEpub(it) + var book: Pair? = 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() + val author = UIText.StringValue( + document.select("metadata > dc|creator").text().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()) { + coverImagePath = document + .select("manifest > item[media-type*=image]") + .firstOrNull()?.attr("href") + } + + if (coverImagePath == null) { + return@withContext + } + + val coverImage = extractCoverImageBitmap(file, coverImagePath) + + 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 coverImage } } - - val metadata = book.metadata - val author = UIText.StringValue(metadata.authors.joinToString(", ")) - val title = metadata.titles.joinToString(", ") - val coverImage = book.coverImage?.let { BitmapFactory.decodeStream(it.inputStream) } - - var description: StringBuilder? = StringBuilder() - metadata.descriptions.forEach { - description?.append(it)?.append(" ") - } - - if (description != null) { - if (description.isBlank()) { - description = null - } - } - - return Book( - title = title, - author = author, - description = description?.toString(), - textPath = "", - scrollIndex = 0, - scrollOffset = 0, - progress = 0f, - filePath = file.path, - lastOpened = null, - category = Category.entries[0], - coverImage = null - ) to coverImage + return book } catch (e: Exception) { e.printStackTrace() return null } } +} + +private fun extractCoverImageBitmap(file: File, coverImagePath: String): Bitmap? { + 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 } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt index 3930ddd4..3f8290d2 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt @@ -2,19 +2,13 @@ package ua.acclorite.book_story.data.parser.epub import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import nl.siegmann.epublib.epub.EpubReader import org.jsoup.Jsoup -import org.jsoup.nodes.Document.OutputSettings -import org.jsoup.safety.Safelist 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.BufferedReader import java.io.File -import java.io.FileInputStream -import java.io.InputStreamReader -import java.nio.charset.Charset +import java.util.zip.ZipFile import javax.inject.Inject @@ -26,66 +20,41 @@ class EpubTextParser @Inject constructor() : TextParser { } try { - val book = withContext(Dispatchers.IO) { - EpubReader().readEpub(FileInputStream(file)) - } - val unformattedText = StringBuilder() + val lines = mutableListOf() - for (spineReference in book.spine.spineReferences) { - val resource = spineReference.resource - val inputStream = resource.inputStream - val reader = BufferedReader( - InputStreamReader( - inputStream, - Charset.forName("UTF-8") - ) - ) - var line: 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() + } - withContext(Dispatchers.IO) { - inputStream.close() - } - - while (withContext(Dispatchers.IO) { reader.readLine() } - .also { line = it } != null) { - unformattedText.append(line).append("\n") - } - - withContext(Dispatchers.IO) { - reader.close() - } - } - - val strings = mutableListOf() - - val parsedText = Jsoup.parse(unformattedText.toString()) - parsedText.outputSettings(OutputSettings().prettyPrint(false)) - parsedText.select("br").append("\n") - parsedText.select("p").prepend("\n") - parsedText.select("em").append(" ").prepend("") - - val formattedText = Jsoup.clean( - parsedText.html(), - "", - Safelist.none(), - OutputSettings().prettyPrint(false) - ) - - formattedText - .replace(" ", "") - .replace("\u00a0", "") - .split("\n") - .forEach { - if (it.isNotBlank()) { - strings.add(it.trim()) + val document = Jsoup.parse(content) + document + .wholeText() + .lines() + .forEach { element -> + if (element.isNotBlank()) { + lines.add(element.trim()) + } + } + } } } + } - if (strings.isEmpty()) { + if (lines.isEmpty()) { return Resource.Error(UIText.StringResource(R.string.error_file_empty)) } - return Resource.Success(strings) + return Resource.Success(lines) } catch (e: Exception) { e.printStackTrace() return Resource.Error(