dav/tags.c

changeset 400
90c6bfa94fa2
parent 393
438c8fe7d62f
child 403
8e1948eebef5
--- a/dav/tags.c	Fri Jun 01 08:52:05 2018 +0200
+++ b/dav/tags.c	Fri Jun 01 09:01:00 2018 +0200
@@ -28,6 +28,8 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
 
 #include <ucx/string.h>
 #include <ucx/utils.h>
@@ -324,3 +326,281 @@
 }
 #endif
 
+
+/* ----------- ----------- tag filter  ---------------------- */
+
+// TODO: use scstr_t after update to UCX 2.0
+static size_t rtrimskip(sstr_t str, size_t skip) {
+    while (skip < str.length && isspace(str.ptr[skip])) skip++;
+    return skip;
+}
+
+static size_t parse_tagfilter_taglist(sstr_t fs, SyncTagFilter* tagfilter) {
+    size_t csvlen;
+    for (csvlen = 0 ; csvlen < fs.length ; ++csvlen) {
+        if (fs.ptr[csvlen] == ')') break;
+    }
+    fs.length = csvlen;
+        
+    tagfilter->tags = parse_csv_taglist(fs.ptr, fs.length);
+        
+    return csvlen;
+}
+
+static size_t parse_tagfilter_subfilters(sstr_t fs, SyncTagFilter* tagfilter);
+
+static size_t parse_tagfilter_filter(sstr_t fs, SyncTagFilter* tagfilter) {
+    
+    size_t consumed = rtrimskip(fs, 0);
+    fs = sstrsubs(fs, consumed);
+    
+    if (fs.length == 0) {
+        return consumed;
+    } else {
+
+        // optional operator
+        int hasop = 0;
+        if (fs.ptr[0] == '&') {
+            tagfilter->mode = DAV_SYNC_TAGFILTER_AND;
+            hasop = 1;
+        } else if (fs.ptr[0] == '|') {
+            tagfilter->mode = DAV_SYNC_TAGFILTER_OR;
+            hasop = 1;
+        } else if (fs.ptr[0] == '0') {
+            tagfilter->mode = DAV_SYNC_TAGFILTER_NONE;
+            hasop = 1;
+        } else if (fs.ptr[0] == '1') {
+            tagfilter->mode = DAV_SYNC_TAGFILTER_ONE;
+            hasop = 1;
+        } else {
+            // default operator is AND
+            tagfilter->mode = DAV_SYNC_TAGFILTER_AND;
+        }
+        
+        if (hasop) {
+            size_t skip = rtrimskip(fs, 1);
+            consumed += skip;
+            fs = sstrsubs(fs, skip);
+        }
+
+        if (fs.length > 0 && fs.ptr[0] == '(') {
+            size_t c = parse_tagfilter_subfilters(fs, tagfilter);
+            if (c) {
+                return consumed + c;
+            } else {
+                return 0;
+            }
+        } else {
+            tagfilter->subfilter_count = 0;
+            tagfilter->subfilters = NULL;
+            return consumed + parse_tagfilter_taglist(fs, tagfilter);
+        }
+    }
+}
+
+/*
+ * Parses:  ( "(" , filter , ")" )+
+ */
+static size_t parse_tagfilter_subfilters(sstr_t fs, SyncTagFilter* f) {
+    
+    // strategy: allocate much and give back later (instead of reallocs in loop)
+    size_t subfilter_cap = 8;
+    f->subfilters = calloc(subfilter_cap, sizeof(SyncTagFilter*));
+    f->subfilter_count = 0;
+    
+    size_t total_consumed = 0;
+    size_t c;
+    do {
+        // skip leading parenthesis (and white spaces)
+        c = rtrimskip(fs, 1);
+        fs = sstrsubs(fs, c);
+        total_consumed += c;
+    
+        // increase array capacity, if necessary
+        if (f->subfilter_count >= subfilter_cap) {
+            subfilter_cap *= 2;
+            SyncTagFilter** newarr = realloc(f->subfilters,
+                    subfilter_cap * sizeof(SyncTagFilter*));
+            if (newarr) {
+                f->subfilters = newarr;
+            } else {
+                abort(); // no error handling reachable, so we are fucked
+            }
+        }
+        
+        // allocate space for a new filter
+        SyncTagFilter* subf = calloc(1, sizeof(SyncTagFilter));
+        
+        // parse that filter
+        c = parse_tagfilter_filter(fs, subf);
+        
+        // sanity check: we must end with a closing parenthesis
+        if (c > 0 && fs.ptr[c] == ')') {
+            f->subfilters[f->subfilter_count++] = subf;
+            
+            // consume ')' and find the next parenthesis or the end-of-string
+            c = rtrimskip(fs, 1+c);
+            fs = sstrsubs(fs, c);
+            total_consumed += c;
+
+            if (fs.length == 0 || fs.ptr[0] == ')') {
+                // our job is done
+                break;
+            } else if (fs.ptr[0] != '(') {
+                // anything else than a parenthesis or end-of-string is an error
+                return 0;
+            }
+        } else {
+            free(subf);
+            break;
+        }
+        
+    } while(1);
+    
+    // try to shrink the array
+    if (f->subfilter_count > 0) {
+        SyncTagFilter** shrinked_array = realloc(f->subfilters,
+                f->subfilter_count * sizeof(SyncTagFilter*));
+        if (shrinked_array) {
+            f->subfilters = shrinked_array;
+        }
+    } else {
+        free(f->subfilters);
+        f->subfilters = NULL;
+    }
+        
+    return total_consumed;
+}
+
+SyncTagFilter* parse_tagfilter_string(const char* filterstring) {
+    SyncTagFilter* tagfilter = calloc(1, sizeof(SyncTagFilter));
+    if (!filterstring) {
+        return tagfilter;
+    }
+    
+    // TODO: use scstr_t after update to UCX 2.0
+    sstr_t fs = sstr((char*) filterstring);
+    size_t consumed = parse_tagfilter_filter(fs, tagfilter);
+    if (!consumed) {
+        free_tagfilter(tagfilter);
+        return NULL;
+    }
+    
+    // consume trailing white spaces
+    consumed = rtrimskip(fs, consumed);
+    
+    // sanity check: have we consumed the whole string?
+    if (consumed != fs.length) {
+        free_tagfilter(tagfilter);
+        return NULL;
+    }
+        
+    return tagfilter;
+}
+
+void free_tagfilter(SyncTagFilter* filter) {
+    for (size_t i = 0 ; i < filter->subfilter_count ; i++) {
+        free_tagfilter(filter->subfilters[i]);
+    }
+    free(filter->subfilters);
+    free(filter);
+}
+
+
+static int matches_tags_and(UcxList *dav_tags, UcxList *tags, int ignorecase) {
+    UCX_FOREACH(e, tags) {
+        if (!ucx_list_contains(dav_tags, e->data,
+                (cmp_func) compare_tagname, &ignorecase)) {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+static int matches_tags_or(UcxList *dav_tags, UcxList *tags, int ignorecase) {
+    UCX_FOREACH(e, tags) {
+        if (ucx_list_contains(dav_tags, e->data,
+                (cmp_func) compare_tagname, &ignorecase)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int matches_tags_one(UcxList *dav_tags, UcxList *tags, int ignorecase) {
+    int matches_exactly_one = 0;
+    UCX_FOREACH(e, tags) {
+        if (ucx_list_contains(dav_tags, e->data,
+                (cmp_func) compare_tagname, &ignorecase)) {
+            if (matches_exactly_one) {
+                return 0;
+            } else {
+                matches_exactly_one = 1;
+            }
+        }
+    }
+    return matches_exactly_one;
+}
+
+static int matches_subfilters_and(UcxList *dav_tags, SyncTagFilter *filter) {
+    int ret = 1;
+    for (size_t i = 0 ; i < filter->subfilter_count ; i++) {
+        ret &= matches_tagfilter(dav_tags, filter->subfilters[i]);
+    }
+    return ret;
+}
+
+static int matches_subfilters_or(UcxList *dav_tags, SyncTagFilter *filter) {
+    int ret = 0;
+    for (size_t i = 0 ; i < filter->subfilter_count ; i++) {
+        ret |= matches_tagfilter(dav_tags, filter->subfilters[i]);
+    }
+    return ret;
+}
+
+static int matches_subfilters_one(UcxList *dav_tags, SyncTagFilter *filter) {
+    int one = 0;
+    for (size_t i = 0 ; i < filter->subfilter_count ; i++) {
+        if (matches_tagfilter(dav_tags, filter->subfilters[i])) {
+            if (one) {
+                return 0;
+            } else {
+                one = 1;
+            }
+        }
+    }
+    return one;
+}
+
+int matches_tagfilter(UcxList *dav_tags, SyncTagFilter *tagfilter) {
+
+    if (tagfilter->subfilter_count > 0) {
+        switch (tagfilter->mode) {
+        case DAV_SYNC_TAGFILTER_OR:
+            return matches_subfilters_or(dav_tags, tagfilter);
+        case DAV_SYNC_TAGFILTER_AND:
+            return matches_subfilters_and(dav_tags, tagfilter);
+        case DAV_SYNC_TAGFILTER_NONE:
+            return !matches_subfilters_or(dav_tags, tagfilter);
+        case DAV_SYNC_TAGFILTER_ONE:
+            return matches_subfilters_one(dav_tags, tagfilter);
+        default:
+            abort();
+        }
+    } else {
+        int ignorecase = 0; // TODO: maybe add support later
+        switch (tagfilter->mode) {
+        case DAV_SYNC_TAGFILTER_OR:
+            return matches_tags_or(dav_tags, tagfilter->tags, ignorecase);
+        case DAV_SYNC_TAGFILTER_AND:
+            return matches_tags_and(dav_tags, tagfilter->tags, ignorecase);
+        case DAV_SYNC_TAGFILTER_NONE:
+            return !matches_tags_or(dav_tags, tagfilter->tags, ignorecase);
+        case DAV_SYNC_TAGFILTER_ONE:
+            return matches_tags_one(dav_tags, tagfilter->tags, ignorecase);
+        default:
+            abort();
+        }
+    }
+}
+

mercurial