diff --git a/app/build.gradle b/app/build.gradle index be8813009..5db9de98b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -193,6 +193,9 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.11' implementation 'org.apache.commons:commons-text:1.8' + //字符串比较 + implementation 'net.ricecode:string-similarity:1.0.0' + //MarkDown implementation 'ru.noties.markwon:core:3.1.0' diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index ae992e696..32d5262ce 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -15,10 +15,15 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -import org.apache.commons.text.similarity.JaccardSimilarity +import net.ricecode.similarity.JaroWinklerStrategy +import net.ricecode.similarity.StringSimilarityServiceImpl import org.jetbrains.anko.toast import java.io.File import java.util.concurrent.CopyOnWriteArraySet +import java.util.regex.Matcher +import java.util.regex.Pattern +import kotlin.math.abs +import kotlin.math.max import kotlin.math.min object BookHelp { @@ -202,54 +207,88 @@ object BookHelp { } /** - * 找到相似度最高的章节 + * 根据目录名获取当前章节 */ - fun getDurChapterIndexByChapterTitle( - title: String?, - index: Int, - chapters: List, + fun getDurChapter( + oldDurChapterIndex: Int, + oldChapterListSize: Int, + oldDurChapterName: String?, + newChapterList: List ): Int { - if (title.isNullOrEmpty()) { - return min(index, chapters.lastIndex) - } - if (chapters.size > index && title == chapters[index].title) { - return index - } - + if (oldChapterListSize == 0) return 0 + val oldChapterNum = getChapterNum(oldDurChapterName) + val oldName = getPureChapterName(oldDurChapterName) + val newChapterSize = newChapterList.size + val min = max( + 0, + min( + oldDurChapterIndex, + oldDurChapterIndex - oldChapterListSize + newChapterSize + ) - 10 + ) + val max = min( + newChapterSize - 1, + max( + oldDurChapterIndex, + oldDurChapterIndex - oldChapterListSize + newChapterSize + ) + 10 + ) + var nameSim = 0.0 var newIndex = 0 - val jSimilarity = JaccardSimilarity() - var similarity = if (chapters.size > index) { - jSimilarity.apply(title, chapters[index].title) - } else 0.0 - if (similarity == 1.0) { - return index - } else { - for (i in 1..50) { - if (index - i in chapters.indices) { - jSimilarity.apply(title, chapters[index - i].title).let { - if (it > similarity) { - similarity = it - newIndex = index - i - if (similarity == 1.0) { - return newIndex - } - } - } + var newNum = 0 + if (oldName.isNotEmpty()) { + val service = StringSimilarityServiceImpl(JaroWinklerStrategy()) + for (i in min..max) { + val newName = getPureChapterName(newChapterList[i].title) + val temp = service.score(oldName, newName) + if (temp > nameSim) { + nameSim = temp + newIndex = i } - if (index + i in chapters.indices) { - jSimilarity.apply(title, chapters[index + i].title).let { - if (it > similarity) { - similarity = it - newIndex = index + i - if (similarity == 1.0) { - return newIndex - } - } - } + } + } + if (nameSim < 0.96 && oldChapterNum > 0) { + for (i in min..max) { + val temp = getChapterNum(newChapterList[i].title) + if (temp == oldChapterNum) { + newNum = temp + newIndex = i + break + } else if (abs(temp - oldChapterNum) < abs(newNum - oldChapterNum)) { + newNum = temp + newIndex = i } } } - return newIndex + return if (nameSim > 0.96 || abs(newNum - oldChapterNum) < 1) { + newIndex + } else { + min(max(0, newChapterList.size - 1), oldDurChapterIndex) + } + } + + private val chapterNamePattern = + Pattern.compile("^(.*?第([\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟0-9\\s]+)[章节篇回集])[、,。 ::.\\s]*") + + private fun getChapterNum(chapterName: String?): Int { + if (chapterName != null) { + val matcher: Matcher = chapterNamePattern.matcher(chapterName) + if (matcher.find()) { + return StringUtils.stringToInt(matcher.group(2)) + } + } + return -1 + } + + private fun getPureChapterName(chapterName: String?): String { + // 所有非字母数字中日韩文字 CJK区+扩展A-F区 + return if (chapterName == null) "" else StringUtils.fullToHalf(chapterName) + .replace("\\s".toRegex(), "") + .replace("^第.*?章|[(\\[][^()\\[\\]]{2,}[)\\]]$".toRegex(), "") + .replace( + "[^\\w\\u4E00-\\u9FEF〇\\u3400-\\u4DBF\\u20000-\\u2A6DF\\u2A700-\\u2EBEF]".toRegex(), + "" + ) } private var bookName: String? = null diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt index 4c1a5a85b..5225b21ff 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt @@ -89,7 +89,9 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application) fun changeTo(book1: Book) { execute { + var oldTocSize: Int = book1.totalChapterNum AudioPlay.book?.let { + oldTocSize = it.totalChapterNum book1.order = it.order App.db.bookDao().delete(it) } @@ -99,18 +101,23 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application) AudioPlay.webBook = WebBook(it) } if (book1.tocUrl.isEmpty()) { - loadBookInfo(book1) { upChangeDurChapterIndex(book1, it) } + loadBookInfo(book1) { upChangeDurChapterIndex(book1, oldTocSize, it) } } else { - loadChapterList(book1) { upChangeDurChapterIndex(book1, it) } + loadChapterList(book1) { upChangeDurChapterIndex(book1, oldTocSize, it) } } } } - private fun upChangeDurChapterIndex(book: Book, chapters: List) { + private fun upChangeDurChapterIndex( + book: Book, + oldTocSize: Int, + chapters: List + ) { execute { - AudioPlay.durChapterIndex = BookHelp.getDurChapterIndexByChapterTitle( - book.durChapterTitle, + AudioPlay.durChapterIndex = BookHelp.getDurChapter( book.durChapterIndex, + oldTocSize, + book.durChapterTitle, chapters ) book.durChapterIndex = AudioPlay.durChapterIndex diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index aa3c12557..c4f60e92b 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -133,23 +133,36 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { fun changeTo(newBook: Book) { execute { + var oldTocSize: Int = newBook.totalChapterNum if (inBookshelf) { - bookData.value?.changeTo(newBook) + bookData.value?.let { + oldTocSize = it.totalChapterNum + it.changeTo(newBook) + } } bookData.postValue(newBook) if (newBook.tocUrl.isEmpty()) { - loadBookInfo(newBook, false) { upChangeDurChapterIndex(newBook, it) } + loadBookInfo(newBook, false) { + upChangeDurChapterIndex(newBook, oldTocSize, it) + } } else { - loadChapter(newBook) { upChangeDurChapterIndex(newBook, it) } + loadChapter(newBook) { + upChangeDurChapterIndex(newBook, oldTocSize, it) + } } } } - private fun upChangeDurChapterIndex(book: Book, chapters: List) { + private fun upChangeDurChapterIndex( + book: Book, + oldTocSize: Int, + chapters: List + ) { execute { - book.durChapterIndex = BookHelp.getDurChapterIndexByChapterTitle( - book.durChapterTitle, + book.durChapterIndex = BookHelp.getDurChapter( book.durChapterIndex, + oldTocSize, + book.durChapterTitle, chapters ) book.durChapterTitle = chapters[book.durChapterIndex].title diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 1bf00e95a..648e9d483 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -157,8 +157,12 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { fun changeTo(newBook: Book) { execute { + var oldTocSize: Int = newBook.totalChapterNum ReadBook.upMsg(null) - ReadBook.book?.changeTo(newBook) + ReadBook.book?.let { + oldTocSize = it.totalChapterNum + it.changeTo(newBook) + } ReadBook.book = newBook App.db.bookSourceDao().getBookSource(newBook.origin)?.let { ReadBook.webBook = WebBook(it) @@ -171,11 +175,11 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } if (newBook.tocUrl.isEmpty()) { loadBookInfo(newBook) { - upChangeDurChapterIndex(newBook, it) + upChangeDurChapterIndex(newBook, oldTocSize, it) } } else { loadChapterList(newBook) { - upChangeDurChapterIndex(newBook, it) + upChangeDurChapterIndex(newBook, oldTocSize, it) } } } @@ -208,11 +212,12 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } } - private fun upChangeDurChapterIndex(book: Book, chapters: List) { + private fun upChangeDurChapterIndex(book: Book, oldTocSize: Int, chapters: List) { execute { - ReadBook.durChapterIndex = BookHelp.getDurChapterIndexByChapterTitle( - book.durChapterTitle, + ReadBook.durChapterIndex = BookHelp.getDurChapter( book.durChapterIndex, + oldTocSize, + book.durChapterTitle, chapters ) book.durChapterIndex = ReadBook.durChapterIndex