Added ".fb2" file format. (v1.0.1)
This commit is contained in:
parent
49567dcade
commit
6e70901429
12 changed files with 252 additions and 7 deletions
|
|
@ -141,6 +141,9 @@ dependencies {
|
|||
implementation("org.slf4j:slf4j-android:latest.release")
|
||||
implementation("org.jsoup:jsoup:latest.release")
|
||||
|
||||
// Fb2 parser
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:latest.release")
|
||||
|
||||
// App Compat for Language Switcher
|
||||
implementation("androidx.appcompat:appcompat:latest.release")
|
||||
implementation("androidx.appcompat:appcompat-resources:latest.release")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package ua.acclorite.book_story.data.parser
|
||||
|
||||
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.pdf.PdfFileParser
|
||||
import ua.acclorite.book_story.data.parser.txt.TxtFileParser
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
|
|
@ -11,10 +12,11 @@ import javax.inject.Inject
|
|||
class FileParserImpl @Inject constructor(
|
||||
private val txtFileParser: TxtFileParser,
|
||||
private val pdfFileParser: PdfFileParser,
|
||||
private val epubFileParser: EpubFileParser
|
||||
private val epubFileParser: EpubFileParser,
|
||||
private val fb2FileParser: Fb2FileParser
|
||||
) : FileParser {
|
||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||
val fileFormat = ".${file.name.substringAfterLast(".")}"
|
||||
val fileFormat = ".${file.name.substringAfterLast(".")}".lowercase().trim()
|
||||
|
||||
if (fileFormat == ".pdf") {
|
||||
return pdfFileParser.parse(file)
|
||||
|
|
@ -28,6 +30,10 @@ class FileParserImpl @Inject constructor(
|
|||
return txtFileParser.parse(file)
|
||||
}
|
||||
|
||||
if (fileFormat == ".fb2") {
|
||||
return fb2FileParser.parse(file)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package ua.acclorite.book_story.data.parser
|
|||
|
||||
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.pdf.PdfTextParser
|
||||
import ua.acclorite.book_story.data.parser.txt.TxtTextParser
|
||||
import ua.acclorite.book_story.domain.util.Resource
|
||||
|
|
@ -13,9 +14,10 @@ class TextParserImpl @Inject constructor(
|
|||
private val txtTextParser: TxtTextParser,
|
||||
private val pdfTextParser: PdfTextParser,
|
||||
private val epubTextParser: EpubTextParser,
|
||||
private val fb2TextParser: Fb2TextParser
|
||||
) : TextParser {
|
||||
override suspend fun parse(file: File): Resource<List<String>> {
|
||||
val fileFormat = ".${file.name.substringAfterLast(".")}"
|
||||
val fileFormat = ".${file.name.substringAfterLast(".")}".lowercase().trim()
|
||||
|
||||
if (fileFormat == ".pdf") {
|
||||
return pdfTextParser.parse(file)
|
||||
|
|
@ -29,6 +31,10 @@ class TextParserImpl @Inject constructor(
|
|||
return txtTextParser.parse(file)
|
||||
}
|
||||
|
||||
if (fileFormat == ".fb2") {
|
||||
return fb2TextParser.parse(file)
|
||||
}
|
||||
|
||||
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ class EpubFileParser @Inject constructor() : FileParser {
|
|||
translateWhenOpen = false
|
||||
) to coverImage
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import javax.inject.Inject
|
|||
class EpubTextParser @Inject constructor() : TextParser {
|
||||
|
||||
override suspend fun parse(file: File): Resource<List<String>> {
|
||||
if (!file.name.endsWith(".epub")) {
|
||||
if (!file.name.endsWith(".epub") || !file.exists()) {
|
||||
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +87,7 @@ class EpubTextParser @Inject constructor() : TextParser {
|
|||
|
||||
return Resource.Success(strings)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return Resource.Error(
|
||||
UIText.StringResource(
|
||||
R.string.error_query,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package ua.acclorite.book_story.data.parser.fb2
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
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
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
class Fb2FileParser @Inject constructor() : FileParser {
|
||||
|
||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||
if (!file.name.endsWith(".fb2") || !file.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
val factory = DocumentBuilderFactory.newInstance()
|
||||
val builder = factory.newDocumentBuilder()
|
||||
val document = withContext(Dispatchers.IO) {
|
||||
builder.parse(file)
|
||||
}
|
||||
|
||||
val titleFromFile = extractElementContent(document, "book-title")
|
||||
val title = titleFromFile ?: file.name.dropLast(4).trim()
|
||||
|
||||
val authorFirstName = extractElementContent(document, "first-name")
|
||||
val authorLastName = extractElementContent(document, "last-name")
|
||||
val authorFromFile = StringBuilder()
|
||||
|
||||
if (authorFirstName != null) {
|
||||
authorFromFile.append(
|
||||
"$authorFirstName "
|
||||
)
|
||||
}
|
||||
if (authorLastName != null) {
|
||||
authorFromFile.append(
|
||||
authorLastName
|
||||
)
|
||||
}
|
||||
|
||||
val author = if (authorFromFile.isNotBlank()) {
|
||||
UIText.StringValue(authorFromFile.toString().trim())
|
||||
} else {
|
||||
UIText.StringResource(R.string.unknown_author)
|
||||
}
|
||||
|
||||
val descriptionFromFile = extractElementContent(document, "annotation")
|
||||
|
||||
return Book(
|
||||
title = title,
|
||||
author = author,
|
||||
description = descriptionFromFile,
|
||||
textPath = "",
|
||||
scrollIndex = 0,
|
||||
scrollOffset = 0,
|
||||
progress = 0f,
|
||||
filePath = file.path,
|
||||
lastOpened = null,
|
||||
category = Category.entries[0],
|
||||
coverImage = null,
|
||||
enableTranslator = false,
|
||||
translateFrom = "",
|
||||
translateTo = "",
|
||||
doubleClickTranslation = false,
|
||||
translateWhenOpen = false
|
||||
) to null
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractElementContent(document: Document, tagName: String): String? {
|
||||
val nodeList = document.getElementsByTagName(tagName)
|
||||
if (nodeList.length > 0) {
|
||||
val element = nodeList.item(0) as Element
|
||||
return element.textContent.trim()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
package ua.acclorite.book_story.data.parser.fb2
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.NodeList
|
||||
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
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
class Fb2TextParser @Inject constructor() : TextParser {
|
||||
|
||||
override suspend fun parse(file: File): Resource<List<String>> {
|
||||
if (!file.name.endsWith(".fb2") || !file.exists()) {
|
||||
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||
}
|
||||
|
||||
return try {
|
||||
val factory = DocumentBuilderFactory.newInstance()
|
||||
val builder = factory.newDocumentBuilder()
|
||||
val document = withContext(Dispatchers.IO) {
|
||||
builder.parse(file)
|
||||
}
|
||||
|
||||
val formattedLines = mutableListOf<String>()
|
||||
|
||||
val bodyNodes = document.getElementsByTagName("body")
|
||||
if (bodyNodes.length == 0) {
|
||||
return Resource.Error(
|
||||
UIText.StringResource(R.string.error_file_empty)
|
||||
)
|
||||
}
|
||||
|
||||
val unformattedLines = mutableListOf<String>()
|
||||
val bodyNode = bodyNodes.item(0) as Element
|
||||
val paragraphNodes = bodyNode.getElementsByTagName("p")
|
||||
for (element in paragraphNodes.asList()) {
|
||||
if (element.textContent.isBlank()) {
|
||||
continue
|
||||
}
|
||||
|
||||
unformattedLines.add(
|
||||
element.textContent.trim()
|
||||
)
|
||||
}
|
||||
|
||||
val lines = mutableListOf<String>()
|
||||
unformattedLines.forEachIndexed { index, string ->
|
||||
try {
|
||||
val line = string.trim()
|
||||
|
||||
if (index == 0) {
|
||||
lines.add(line)
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
if (
|
||||
line.all {
|
||||
if (it == ' ') {
|
||||
true
|
||||
} else if (it.isUpperCase() || it.isDigit() || it == '-') {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
if (line.all { it.isDigit() }) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
if (line.first().isLowerCase()) {
|
||||
val currentLine = lines[lines.lastIndex]
|
||||
|
||||
if (currentLine.last() == '-') {
|
||||
if (currentLine[currentLine.lastIndex - 1].isLowerCase()) {
|
||||
lines[lines.lastIndex] = currentLine.dropLast(1) + line
|
||||
return@forEachIndexed
|
||||
}
|
||||
}
|
||||
|
||||
lines[lines.lastIndex] += " $line"
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
if (line.first().isUpperCase() || line.first().isDigit()) {
|
||||
lines.add(line)
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
if (line.first().isLetter()) {
|
||||
lines[lines.lastIndex] += " $line"
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return@forEachIndexed
|
||||
}
|
||||
}
|
||||
|
||||
lines.forEach { line ->
|
||||
formattedLines.add(line.trim())
|
||||
}
|
||||
|
||||
if (formattedLines.isEmpty()) {
|
||||
return Resource.Error(UIText.StringResource(R.string.error_file_empty))
|
||||
}
|
||||
|
||||
Resource.Success(formattedLines)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Resource.Error(
|
||||
UIText.StringResource(
|
||||
R.string.error_query,
|
||||
e.message?.take(40)?.trim() ?: ""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NodeList.asList(): List<Element> {
|
||||
val list = mutableListOf<Element>()
|
||||
for (i in 0 until this.length) {
|
||||
list.add(this.item(i) as Element)
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ class PdfFileParser @Inject constructor(private val application: Application) :
|
|||
translateWhenOpen = false
|
||||
) to null
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import javax.inject.Inject
|
|||
class PdfTextParser @Inject constructor(private val application: Application) : TextParser {
|
||||
|
||||
override suspend fun parse(file: File): Resource<List<String>> {
|
||||
if (!file.name.endsWith(".pdf")) {
|
||||
if (!file.name.endsWith(".pdf") || !file.exists()) {
|
||||
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class TxtFileParser @Inject constructor() : FileParser {
|
|||
translateWhenOpen = false
|
||||
) to null
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import javax.inject.Inject
|
|||
class TxtTextParser @Inject constructor() : TextParser {
|
||||
|
||||
override suspend fun parse(file: File): Resource<List<String>> {
|
||||
if (!file.name.endsWith(".txt")) {
|
||||
if (!file.name.endsWith(".txt") || !file.exists()) {
|
||||
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +37,7 @@ class TxtTextParser @Inject constructor() : TextParser {
|
|||
|
||||
Resource.Success(formattedLines)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Resource.Error(
|
||||
UIText.StringResource(
|
||||
R.string.error_query,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ object Constants {
|
|||
const val MAIN_STATE = "main_state"
|
||||
|
||||
// Supported file extensions
|
||||
val EXTENSIONS = listOf(".txt", ".pdf", ".epub")
|
||||
val EXTENSIONS = listOf(".txt", ".pdf", ".epub", ".fb2")
|
||||
|
||||
// Supported languages
|
||||
val LANGUAGES = listOf(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue