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
|
package be.simplenotes.domain.usecases.search
|
||||||
|
|
||||||
import be.simplenotes.search.SearchTerms
|
import be.simplenotes.search.SearchTerms
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
private fun innerRegex(name: String) =
|
private enum class Quote { SingleQuote, DoubleQuote, }
|
||||||
"""$name:['"](.*?)['"]""".toRegex()
|
|
||||||
private fun outerRegex(name: String) =
|
|
||||||
"""($name:['"].*?['"])""".toRegex()
|
|
||||||
|
|
||||||
private val titleRe = innerRegex("title")
|
data class ParsedSearchInput(val global: List<String>, val entries: Map<String, String>)
|
||||||
private val outerTitleRe = outerRegex("title")
|
|
||||||
|
|
||||||
private val tagRe = innerRegex("tag")
|
object SearchInputParser {
|
||||||
private val outerTagRe = outerRegex("tag")
|
fun parseInput(input: String): ParsedSearchInput {
|
||||||
|
val tokenizer = StringTokenizer(input, ":\"' ", true)
|
||||||
|
|
||||||
private val contentRe = innerRegex("content")
|
val tokens = ArrayList<String>()
|
||||||
private val outerContentRe = outerRegex("content")
|
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 {
|
internal fun parseSearchTerms(input: String): SearchTerms {
|
||||||
var c: String = input
|
val parsedInput = SearchInputParser.parseInput(input)
|
||||||
|
|
||||||
fun extract(innerRegex: Regex, outerRegex: Regex): String? {
|
val title: String? = parsedInput.entries["title"]
|
||||||
val match = innerRegex.find(input)?.groups?.get(1)?.value
|
val tag: String? = parsedInput.entries["tag"]
|
||||||
if (match != null) {
|
val content: String? = parsedInput.entries["content"]
|
||||||
val group = outerRegex.find(input)?.groups?.get(1)?.value
|
|
||||||
group?.let { c = c.replace(it, "") }
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
val title: String? = extract(titleRe, outerTitleRe)
|
val all = parsedInput.global.takeIf { it.isNotEmpty() }?.joinToString(" ")
|
||||||
val tag: String? = extract(tagRe, outerTagRe)
|
|
||||||
val content: String? = extract(contentRe, outerContentRe)
|
|
||||||
|
|
||||||
val all = c.trim().ifEmpty { null }
|
|
||||||
|
|
||||||
return SearchTerms(
|
return SearchTerms(
|
||||||
title = title,
|
title = title,
|
||||||
|
|||||||
@ -35,6 +35,14 @@ internal class SearchTermsParserKtTest {
|
|||||||
tag = "example abc",
|
tag = "example abc",
|
||||||
all = "this is the end"
|
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
|
@ParameterizedTest
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user