better UI, +windows1251 encoding support
This commit is contained in:
1 parent
ddf68c1e6a
commit
da88a6e221
15 files changed
+407
-14
No files matched your search
@@ -10,7 +10,7 @@ object Fb2Format {
|
||||
val xml = if (looksLikeZip(input) || fileName?.endsWith(".zip", ignoreCase = true) == true) {
|
||||
Fb2Zip.extractFb2Xml(input)
|
||||
} else {
|
||||
input.decodeToString()
|
||||
Fb2XmlEncoding.decodeXml(input)
|
||||
}
|
||||
return parseXml(xml)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package net.sergeych.toread.fb2
|
||||
|
||||
internal object Fb2XmlEncoding {
|
||||
private val EncodingPattern = Regex("""encoding\s*=\s*["']([^"']+)["']""", RegexOption.IGNORE_CASE)
|
||||
|
||||
fun decodeXml(bytes: ByteArray): String =
|
||||
when (declaredEncoding(bytes)?.lowercase()) {
|
||||
"windows-1251" -> decodeWindows1251(bytes)
|
||||
else -> bytes.decodeToString()
|
||||
}
|
||||
|
||||
private fun declaredEncoding(bytes: ByteArray): String? {
|
||||
val probe = bytes
|
||||
.copyOfRange(0, minOf(bytes.size, 256))
|
||||
.map { byte ->
|
||||
val value = byte.toInt() and 0xff
|
||||
if (value in 0x20..0x7e || value == '\n'.code || value == '\r'.code || value == '\t'.code) {
|
||||
value.toChar()
|
||||
} else {
|
||||
' '
|
||||
}
|
||||
}
|
||||
.joinToString("")
|
||||
return EncodingPattern.find(probe)?.groupValues?.getOrNull(1)?.trim()
|
||||
}
|
||||
|
||||
private fun decodeWindows1251(bytes: ByteArray): String = buildString(bytes.size) {
|
||||
bytes.forEach { byte ->
|
||||
append(windows1251Char(byte.toInt() and 0xff))
|
||||
}
|
||||
}
|
||||
|
||||
private fun windows1251Char(value: Int): Char =
|
||||
when (value) {
|
||||
in 0x00..0x7f -> value.toChar()
|
||||
0x80 -> '\u0402'
|
||||
0x81 -> '\u0403'
|
||||
0x82 -> '\u201a'
|
||||
0x83 -> '\u0453'
|
||||
0x84 -> '\u201e'
|
||||
0x85 -> '\u2026'
|
||||
0x86 -> '\u2020'
|
||||
0x87 -> '\u2021'
|
||||
0x88 -> '\u20ac'
|
||||
0x89 -> '\u2030'
|
||||
0x8a -> '\u0409'
|
||||
0x8b -> '\u2039'
|
||||
0x8c -> '\u040a'
|
||||
0x8d -> '\u040c'
|
||||
0x8e -> '\u040b'
|
||||
0x8f -> '\u040f'
|
||||
0x90 -> '\u0452'
|
||||
0x91 -> '\u2018'
|
||||
0x92 -> '\u2019'
|
||||
0x93 -> '\u201c'
|
||||
0x94 -> '\u201d'
|
||||
0x95 -> '\u2022'
|
||||
0x96 -> '\u2013'
|
||||
0x97 -> '\u2014'
|
||||
0x98 -> '\uFFFD'
|
||||
0x99 -> '\u2122'
|
||||
0x9a -> '\u0459'
|
||||
0x9b -> '\u203a'
|
||||
0x9c -> '\u045a'
|
||||
0x9d -> '\u045c'
|
||||
0x9e -> '\u045b'
|
||||
0x9f -> '\u045f'
|
||||
0xa0 -> '\u00a0'
|
||||
0xa1 -> '\u040e'
|
||||
0xa2 -> '\u045e'
|
||||
0xa3 -> '\u0408'
|
||||
0xa4 -> '\u00a4'
|
||||
0xa5 -> '\u0490'
|
||||
0xa6 -> '\u00a6'
|
||||
0xa7 -> '\u00a7'
|
||||
0xa8 -> '\u0401'
|
||||
0xa9 -> '\u00a9'
|
||||
0xaa -> '\u0404'
|
||||
0xab -> '\u00ab'
|
||||
0xac -> '\u00ac'
|
||||
0xad -> '\u00ad'
|
||||
0xae -> '\u00ae'
|
||||
0xaf -> '\u0407'
|
||||
0xb0 -> '\u00b0'
|
||||
0xb1 -> '\u00b1'
|
||||
0xb2 -> '\u0406'
|
||||
0xb3 -> '\u0456'
|
||||
0xb4 -> '\u0491'
|
||||
0xb5 -> '\u00b5'
|
||||
0xb6 -> '\u00b6'
|
||||
0xb7 -> '\u00b7'
|
||||
0xb8 -> '\u0451'
|
||||
0xb9 -> '\u2116'
|
||||
0xba -> '\u0454'
|
||||
0xbb -> '\u00bb'
|
||||
0xbc -> '\u0458'
|
||||
0xbd -> '\u0405'
|
||||
0xbe -> '\u0455'
|
||||
0xbf -> '\u0457'
|
||||
in 0xc0..0xff -> (0x0410 + value - 0xc0).toChar()
|
||||
else -> '\uFFFD'
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ internal object Fb2Zip {
|
||||
val entry = entries.firstOrNull { it.name.endsWith(".fb2", ignoreCase = true) }
|
||||
?: entries.firstOrNull { !it.name.endsWith("/") }
|
||||
?: throw Fb2ParseException("ZIP archive does not contain an FB2 entry")
|
||||
return readEntry(zip, entry).decodeToString()
|
||||
return Fb2XmlEncoding.decodeXml(readEntry(zip, entry))
|
||||
}
|
||||
|
||||
fun createStoredZip(entryName: String, content: ByteArray): ByteArray {
|
||||
|
||||
Reference in new issue
Block a user