You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
5.5 KiB
169 lines
5.5 KiB
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include "prefix_match.h"
|
|
|
|
int prefix_match(const char *value, const char **options, int flags) {
|
|
flags &= ~PREFIXMATCH_MULTI_PARTIAL; // this doesn't make sense here
|
|
bool case_sensitive = PREFIXMATCH_CASE_SENSITIVE == (flags & PREFIXMATCH_CASE_SENSITIVE);
|
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
|
|
|
|
int (*cmpfn) (const char *, const char *) = case_sensitive ? strcmp : strcasecmp;
|
|
int (*ncmpfn) (const char *, const char *, size_t) = case_sensitive ? strncmp : strncasecmp;
|
|
|
|
if (!value || !options) return -1;
|
|
size_t input_len = strlen(value);
|
|
const char *option = NULL;
|
|
int counter = 0;
|
|
int result = -1;
|
|
while (NULL != (option = options[counter])) {
|
|
if (cmpfn(option, value) == 0) {
|
|
return counter; // full exact match
|
|
} else {
|
|
// Test for partial match
|
|
if (can_abbrev && ncmpfn(value, option, input_len) == 0) {
|
|
if (result == -1) {
|
|
result = counter; // first partial match
|
|
} else {
|
|
// ambiguous match
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
counter++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
size_t pm_word_len(const char *word, const char *delims) {
|
|
char d = 0;
|
|
const char *dp = delims;
|
|
size_t word_len = 0;
|
|
while ('\0' != (d = *dp++)) {
|
|
char *end = strchr(word, d);
|
|
if (NULL == end) continue;
|
|
size_t len = end - word;
|
|
if (!word_len || len < word_len) {
|
|
word_len = len;
|
|
}
|
|
}
|
|
if (!word_len) {
|
|
word_len = strlen(word);
|
|
}
|
|
return word_len;
|
|
}
|
|
|
|
size_t pm_count_words(const char *sentence, const char *delims) {
|
|
char c;
|
|
size_t n = 0;
|
|
bool in_word = false;
|
|
if (!sentence || !delims) return 0;
|
|
while (0 != (c = *sentence++)) {
|
|
bool is_delim = NULL != strchr(delims, c);
|
|
if (is_delim && in_word) {
|
|
in_word = false;
|
|
} else if (!in_word && !is_delim) {
|
|
n++;
|
|
in_word = true;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int pm_multipart_test(const char *a, const char* b, const char *delims, int flags) {
|
|
bool case_sensitive = PREFIXMATCH_CASE_SENSITIVE == (flags & PREFIXMATCH_CASE_SENSITIVE);
|
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
|
|
|
|
int (*ncmpfn) (const char *, const char *, size_t) = case_sensitive ? strncmp : strncasecmp;
|
|
// lazy shortcut first...
|
|
if ((case_sensitive && 0 == strcmp(a, b)) || (!case_sensitive && 0 == strcasecmp(a, b))) {
|
|
return 1; // full match
|
|
}
|
|
|
|
const char *word_a = a;
|
|
const char *word_b = b;
|
|
size_t word_a_len = 0;
|
|
size_t word_b_len = 0;
|
|
while (1) {
|
|
word_a += word_a_len;
|
|
word_b += word_b_len;
|
|
|
|
// advance past leading delims, if any
|
|
while (*word_a != '\0' && NULL != strchr(delims, *word_a)) word_a++;
|
|
while (*word_b != '\0' && NULL != strchr(delims, *word_b)) word_b++;
|
|
|
|
// test for terminator
|
|
if (*word_a == '\0' && *word_b == '\0') {
|
|
// both ended at the same number of words
|
|
return 1; // full match
|
|
}
|
|
|
|
if (*word_a == '\0' || *word_b == '\0') {
|
|
// sentences ended at different length
|
|
if (0 != (flags & PREFIXMATCH_MULTI_PARTIAL) && *word_b != '\0') { // word prefix match (a is a prefix of b)
|
|
return 2; // partial word match
|
|
} else {
|
|
return 0; // no match
|
|
}
|
|
}
|
|
|
|
// find end of the words
|
|
word_a_len = pm_word_len(word_a, delims);
|
|
word_b_len = pm_word_len(word_b, delims);
|
|
|
|
if (word_a_len > word_b_len || (!can_abbrev && word_a_len != word_b_len)) {
|
|
return 0; // no match
|
|
}
|
|
|
|
int cmp = ncmpfn(word_a, word_b, word_a_len);
|
|
if (0 != cmp) { // words differ
|
|
return 0; // no match
|
|
}
|
|
}
|
|
}
|
|
|
|
int prefix_multipart_match(const char *value, const char **options, const char* delims, int flags) {
|
|
bool multi_partial = 0 != (flags & PREFIXMATCH_MULTI_PARTIAL);
|
|
flags &= ~PREFIXMATCH_MULTI_PARTIAL; // turn it off for passing the to test fn
|
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV);
|
|
|
|
if (!value || !options) return -1;
|
|
const char *option = NULL;
|
|
int counter = 0;
|
|
int result = -1;
|
|
int result_partial = -1;
|
|
int result_partial_nwords = 0;
|
|
while (NULL != (option = options[counter])) {
|
|
if (pm_multipart_test(value, option, delims, flags | PREFIXMATCH_NOABBREV)) {
|
|
return counter; // full exact match
|
|
} else if (can_abbrev) {
|
|
// Test for partial match
|
|
if (pm_multipart_test(value, option, delims, flags)) {
|
|
if (result == -1) {
|
|
result = counter; // first partial match in all words
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (multi_partial && 2 == pm_multipart_test(value, option, delims, flags | PREFIXMATCH_MULTI_PARTIAL)) {
|
|
int nwords = pm_count_words(option, delims);
|
|
if (result_partial == -1 || result_partial_nwords < nwords) {
|
|
result_partial = counter; // first partial match
|
|
result_partial_nwords = nwords;
|
|
} else {
|
|
result_partial = -2;
|
|
}
|
|
}
|
|
}
|
|
counter++;
|
|
}
|
|
|
|
if (result != -1) {
|
|
return result;
|
|
}
|
|
|
|
if (result_partial >= 0) {
|
|
return result_partial;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|