Use a proper search input parser
This commit is contained in:
parent
51b682c593
commit
3e1683dfe5
@ -1,38 +1,91 @@
|
||||
package be.simplenotes.domain.usecases.search
|
||||
|
||||
import be.simplenotes.search.SearchTerms
|
||||
import java.util.*
|
||||
|
||||
private fun innerRegex(name: String) =
|
||||
"""$name:['"](.*?)['"]""".toRegex()
|
||||
private fun outerRegex(name: String) =
|
||||
"""($name:['"].*?['"])""".toRegex()
|
||||
private enum class Quote { SingleQuote, DoubleQuote, }
|
||||
|
||||
private val titleRe = innerRegex("title")
|
||||
private val outerTitleRe = outerRegex("title")
|
||||
data class ParsedSearchInput(val global: List<String>, val entries: Map<String, String>)
|
||||
|
||||
private val tagRe = innerRegex("tag")
|
||||
private val outerTagRe = outerRegex("tag")
|
||||
object SearchInputParser {
|
||||
fun parseInput(input: String): ParsedSearchInput {
|
||||
val tokenizer = StringTokenizer(input, ":\"' ", true)
|
||||
|
||||
private val contentRe = innerRegex("content")
|
||||
private val outerContentRe = outerRegex("content")
|
||||
val tokens = ArrayList<String>()
|
||||
val current = StringBuilder()
|
||||
var quoteOpen: Quote? = null
|
||||
|
||||
fun push() {
|
||||
if (current.isNotEmpty()) {
|
||||
tokens.add(current.toString())
|
||||
}
|
||||
current.setLength(0)
|
||||
quoteOpen = null
|
||||
}
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
when (val token = tokenizer.nextToken()) {
|
||||
"\"" -> when {
|
||||
Quote.DoubleQuote == quoteOpen -> push()
|
||||
quoteOpen == null -> quoteOpen = Quote.DoubleQuote
|
||||
else -> current.append(token)
|
||||
}
|
||||
"'" -> when {
|
||||
Quote.SingleQuote == quoteOpen -> push()
|
||||
quoteOpen == null -> quoteOpen = Quote.SingleQuote
|
||||
else -> current.append(token)
|
||||
}
|
||||
" " -> {
|
||||
if (quoteOpen != null) current.append(" ")
|
||||
else push()
|
||||
}
|
||||
":" -> {
|
||||
push()
|
||||
tokens.add(token)
|
||||
}
|
||||
else -> {
|
||||
current.append(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push()
|
||||
|
||||
val entries = HashMap<String, String>()
|
||||
|
||||
val colonIndexes = ArrayList<Int>()
|
||||
tokens.forEachIndexed { index, token ->
|
||||
if (token == ":") colonIndexes += index
|
||||
}
|
||||
|
||||
var changes = 0
|
||||
for (colonIndex in colonIndexes) {
|
||||
val offset = changes * 3
|
||||
|
||||
val key = tokens.getOrNull(colonIndex - 1 - offset)
|
||||
val value = tokens.getOrNull(colonIndex + 1 - offset)
|
||||
|
||||
if (key != null && value != null) {
|
||||
entries[key] = value
|
||||
tokens.removeAt(colonIndex - 1 - offset) // remove key
|
||||
tokens.removeAt(colonIndex - 1 - offset) // remove :
|
||||
tokens.removeAt(colonIndex - 1 - offset) // remove value
|
||||
changes++
|
||||
}
|
||||
}
|
||||
|
||||
return ParsedSearchInput(global = tokens, entries = entries)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parseSearchTerms(input: String): SearchTerms {
|
||||
var c: String = input
|
||||
val parsedInput = SearchInputParser.parseInput(input)
|
||||
|
||||
fun extract(innerRegex: Regex, outerRegex: Regex): String? {
|
||||
val match = innerRegex.find(input)?.groups?.get(1)?.value
|
||||
if (match != null) {
|
||||
val group = outerRegex.find(input)?.groups?.get(1)?.value
|
||||
group?.let { c = c.replace(it, "") }
|
||||
}
|
||||
return match
|
||||
}
|
||||
val title: String? = parsedInput.entries["title"]
|
||||
val tag: String? = parsedInput.entries["tag"]
|
||||
val content: String? = parsedInput.entries["content"]
|
||||
|
||||
val title: String? = extract(titleRe, outerTitleRe)
|
||||
val tag: String? = extract(tagRe, outerTagRe)
|
||||
val content: String? = extract(contentRe, outerContentRe)
|
||||
|
||||
val all = c.trim().ifEmpty { null }
|
||||
val all = parsedInput.global.takeIf { it.isNotEmpty() }?.joinToString(" ")
|
||||
|
||||
return SearchTerms(
|
||||
title = title,
|
||||
|
||||
@ -35,6 +35,14 @@ internal class SearchTermsParserKtTest {
|
||||
tag = "example abc",
|
||||
all = "this is the end"
|
||||
),
|
||||
createResult("tag:blah", tag = "blah"),
|
||||
createResult("tag:'some words'", tag = "some words"),
|
||||
createResult("tag:'some words ' global", tag = "some words ", all = "global"),
|
||||
createResult(
|
||||
"tag:'double quote inside single \" ' global",
|
||||
tag = "double quote inside single \" ",
|
||||
all = "global"
|
||||
),
|
||||
)
|
||||
|
||||
@ParameterizedTest
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user