source: tags/ms_r16q2/ARBDB/adquery.cxx

Last change on this file was 14682, checked in by westram, 8 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 76.1 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : adquery.cxx                                       //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "gb_aci.h"
12#include "gb_comm.h"
13#include "gb_index.h"
14#include "gb_key.h"
15#include "gb_localdata.h"
16#include "gb_ta.h"
17#include <algorithm>
18
19#include <arb_strbuf.h>
20#include <arb_match.h>
21
22#include <cctype>
23
24#define GB_PATH_MAX 1024
25
26static void build_GBDATA_path(GBDATA *gbd, char **buffer) {
27    GBCONTAINER *gbc = GB_FATHER(gbd);
28    if (gbc) {
29        build_GBDATA_path(gbc, buffer);
30
31        const char *key = GB_KEY(gbd);
32        char       *bp  = *buffer;
33
34        *bp++ = '/';
35        while (*key) *bp++ = *key++;
36        *bp = 0;
37
38        *buffer = bp;
39    }
40}
41
42#define BUFFERSIZE 1024
43
44static const char *GB_get_GBDATA_path(GBDATA *gbd) {
45    static char *orgbuffer = NULL;
46    char        *buffer;
47
48    if (!orgbuffer) orgbuffer = (char*)malloc(BUFFERSIZE);
49    buffer                    = orgbuffer;
50
51    build_GBDATA_path(gbd, &buffer);
52    assert_or_exit((buffer-orgbuffer) < BUFFERSIZE); // buffer overflow
53
54    return orgbuffer;
55}
56
57// ----------------
58//      QUERIES
59
60static bool gb_find_value_equal(GBDATA *gb, GB_TYPES type, const char *val, GB_CASE case_sens) {
61    bool equal = false;
62
63#if defined(DEBUG)
64    GB_TYPES realtype = gb->type();
65    gb_assert(val);
66    if (type == GB_STRING) {
67        gb_assert(gb->is_a_string()); // gb_find_internal called with wrong type
68    }
69    else {
70        gb_assert(realtype == type); // gb_find_internal called with wrong type
71    }
72#endif // DEBUG
73
74    switch (type) {
75        case GB_STRING:
76        case GB_LINK:
77            equal = GBS_string_matches(GB_read_char_pntr(gb), val, case_sens);
78            break;
79
80        case GB_INT: {
81            int i                      = GB_read_int(gb);
82            if (i == *(int*)val) equal = true;
83            break;
84        }
85        default: {
86            const char *err = GBS_global_string("Value search not supported for data type %i (%i)", gb->type(), type);
87            GB_internal_error(err);
88            break;
89        }
90    }
91
92    return equal;
93}
94
95static GBDATA *find_sub_by_quark(GBCONTAINER *father, GBQUARK key_quark, GB_TYPES type, const char *val, GB_CASE case_sens, GBDATA *after, size_t skip_over) {
96    /* search an entry with a key 'key_quark' below a container 'father'
97       after position 'after'
98
99       if 'skip_over' > 0 search skips 'skip_over' entries
100
101       if (val != NULL) search for entry with value 'val':
102
103       GB_STRING/GB_LINK: compares string (case_sensitive or not)
104       GB_INT: compares values
105       GB_FLOAT: ditto (val MUST be a 'double*')
106       others: not implemented yet
107
108       Note: to search for non-char*-values use GB_find_int()
109             for other types write a new similar function
110
111             if key_quark<0 search everything
112    */
113
114    int             end    = father->d.nheader;
115    gb_header_list *header = GB_DATA_LIST_HEADER(father->d);
116
117    int index;
118    if (after) index = (int)after->index+1; else index = 0;
119
120    if (key_quark<0) { // unspecific key quark (i.e. search all)
121        gb_assert(!val);        // search for val not possible if searching all keys!
122        if (!val) {
123            for (; index < end; index++) {
124                if (header[index].flags.key_quark != 0) {
125                    if (header[index].flags.changed >= GB_DELETED) continue;
126                    GBDATA *gb = GB_HEADER_LIST_GBD(header[index]);
127                    if (!gb) {
128                        // @@@ DRY here versus below
129                        gb_unfold(father, 0, index);
130                        header = GB_DATA_LIST_HEADER(father->d);
131                        gb     = GB_HEADER_LIST_GBD(header[index]);
132                        if (!gb) {
133                            const char *err = GBS_global_string("Database entry #%u is missing (in '%s')", index, GB_get_GBDATA_path(father));
134                            GB_internal_error(err);
135                            continue;
136                        }
137                    }
138                    if (!skip_over--) return gb;
139                }
140            }
141        }
142    }
143    else { // specific key quark
144        for (; index < end; index++) {
145            if (key_quark == header[index].flags.key_quark) {
146                if (header[index].flags.changed >= GB_DELETED) continue;
147                GBDATA *gb = GB_HEADER_LIST_GBD(header[index]);
148                if (!gb) {
149                    // @@@ DRY here versus section above
150                    gb_unfold(father, 0, index);
151                    header = GB_DATA_LIST_HEADER(father->d);
152                    gb     = GB_HEADER_LIST_GBD(header[index]);
153                    if (!gb) {
154                        const char *err = GBS_global_string("Database entry #%u is missing (in '%s')", index, GB_get_GBDATA_path(father));
155                        GB_internal_error(err);
156                        continue;
157                    }
158                }
159                if (val) {
160                    if (!gb) {
161                        GB_internal_error("Cannot unfold data");
162                        continue;
163                    }
164                    else {
165                        if (!gb_find_value_equal(gb, type, val, case_sens)) continue;
166                    }
167                }
168                if (!skip_over--) return gb;
169            }
170        }
171    }
172    return NULL;
173}
174
175GBDATA *GB_find_sub_by_quark(GBDATA *father, GBQUARK key_quark, GBDATA *after, size_t skip_over) {
176    return find_sub_by_quark(father->expect_container(), key_quark, GB_NONE, NULL, GB_MIND_CASE, after, skip_over);
177}
178
179static GBDATA *GB_find_subcontent_by_quark(GBDATA *father, GBQUARK key_quark, GB_TYPES type, const char *val, GB_CASE case_sens, GBDATA *after, size_t skip_over) {
180    return find_sub_by_quark(father->expect_container(), key_quark, type, val, case_sens, after, skip_over);
181}
182
183static GBDATA *find_sub_sub_by_quark(GBCONTAINER *const father, const char *key, GBQUARK sub_key_quark, GB_TYPES type, const char *val, GB_CASE case_sens, GBDATA *after) {
184    gb_index_files *ifs    = NULL;
185    GB_MAIN_TYPE   *Main   = GBCONTAINER_MAIN(father);
186    int             end    = father->d.nheader;
187    gb_header_list *header = GB_DATA_LIST_HEADER(father->d);
188
189    int index;
190    if (after) index = (int)after->index+1; else index = 0;
191
192    GBDATA *res;
193    // ****** look for any hash index tables ********
194    // ****** no wildcards allowed       *******
195    if (Main->is_client()) {
196        if (father->flags2.folded_container) {
197            // do the query in the server
198            if (GB_ARRAY_FLAGS(father).changed) {
199                if (!father->flags2.update_in_server) {
200                    GB_ERROR error = Main->send_update_to_server(father);
201                    if (error) {
202                        GB_export_error(error);
203                        return NULL;
204                    }
205                }
206            }
207        }
208        if (father->d.size > GB_MAX_LOCAL_SEARCH && val) {
209            if (after) res = GBCMC_find(after,  key, type, val, case_sens, SEARCH_CHILD_OF_NEXT);
210            else       res = GBCMC_find(father, key, type, val, case_sens, SEARCH_GRANDCHILD);
211            return res;
212        }
213    }
214    if (val &&
215        (ifs=GBCONTAINER_IFS(father))!=NULL &&
216        (!strchr(val, '*')) &&
217        (!strchr(val, '?')))
218    {
219        for (; ifs; ifs = GB_INDEX_FILES_NEXT(ifs)) {
220            if (ifs->key != sub_key_quark) continue;
221            // ***** We found the index table *****
222            res = gb_index_find(father, ifs, sub_key_quark, val, case_sens, index);
223            return res;
224        }
225    }
226
227    GBDATA *gb = after ? after : NULL;
228    for (; index < end; index++) {
229        GBDATA *gbn = GB_HEADER_LIST_GBD(header[index]);
230
231        if (header[index].flags.changed >= GB_DELETED) continue;
232        if (!gbn) {
233            if (Main->is_client()) {
234                if (gb) res = GBCMC_find(gb,     key, type, val, case_sens, SEARCH_CHILD_OF_NEXT);
235                else    res = GBCMC_find(father, key, type, val, case_sens, SEARCH_GRANDCHILD);
236                return res;
237            }
238            GB_internal_error("Empty item in server");
239            continue;
240        }
241        gb = gbn;
242        if (gb->is_container()) {
243            res = GB_find_subcontent_by_quark(gb, sub_key_quark, type, val, case_sens, NULL, 0);
244            if (res) return res;
245        }
246    }
247    return NULL;
248}
249
250
251static GBDATA *gb_find_internal(GBDATA *gbd, const char *key, GB_TYPES type, const char *val, GB_CASE case_sens, GB_SEARCH_TYPE gbs) {
252    GBDATA *result = NULL;
253
254    if (gbd) {
255        GBDATA      *after = NULL;
256        GBCONTAINER *gbc   = NULL;
257
258        switch (gbs) {
259            case SEARCH_NEXT_BROTHER:
260                after = gbd;
261            case SEARCH_BROTHER:
262                gbs   = SEARCH_CHILD;
263                gbc   = GB_FATHER(gbd);
264                break;
265
266            case SEARCH_CHILD:
267            case SEARCH_GRANDCHILD:
268                if (gbd->is_container()) gbc = gbd->as_container();
269                break;
270
271            case SEARCH_CHILD_OF_NEXT:
272                after = gbd;
273                gbs   = SEARCH_GRANDCHILD;
274                gbc   = GB_FATHER(gbd);
275                break;
276        }
277
278        if (gbc) {
279            GBQUARK key_quark = GB_find_existing_quark(gbd, key);
280
281            if (key_quark) { // only search if 'key' is known to db
282                if (gbs == SEARCH_CHILD) {
283                    result = GB_find_subcontent_by_quark(gbc, key_quark, type, val, case_sens, after, 0);
284                }
285                else {
286                    gb_assert(gbs == SEARCH_GRANDCHILD);
287                    result = find_sub_sub_by_quark(gbc, key, key_quark, type, val, case_sens, after);
288                }
289            }
290        }
291    }
292    return result;
293}
294
295GBDATA *GB_find(GBDATA *gbd, const char *key, GB_SEARCH_TYPE gbs) {
296    // normally you should not need to use GB_find!
297    // better use one of the replacement functions
298    // (GB_find_string, GB_find_int, GB_child, GB_nextChild, GB_entry, GB_nextEntry, GB_brother)
299    return gb_find_internal(gbd, key, GB_NONE, NULL, GB_CASE_UNDEFINED, gbs);
300}
301
302GBDATA *GB_find_string(GBDATA *gbd, const char *key, const char *str, GB_CASE case_sens, GB_SEARCH_TYPE gbs) {
303    // search for a subentry of 'gbd' that has
304    // - fieldname 'key'
305    // - type GB_STRING and
306    // - content matching 'str'
307    // if 'case_sensitive' is true, content is matched case sensitive.
308    // GBS_string_matches is used to compare (supports wildcards)
309    return gb_find_internal(gbd, key, GB_STRING, str, case_sens, gbs);
310}
311NOT4PERL GBDATA *GB_find_int(GBDATA *gbd, const char *key, long val, GB_SEARCH_TYPE gbs) {
312    // search for a subentry of 'gbd' that has
313    // - fieldname 'key'
314    // - type GB_INT
315    // - and value 'val'
316    return gb_find_internal(gbd, key, GB_INT, (const char *)&val, GB_CASE_UNDEFINED, gbs);
317}
318
319// ----------------------------------------------------
320//      iterate over ALL subentries of a container
321
322GBDATA *GB_child(GBDATA *father) {
323    // return first child (or NULL if no children)
324    return GB_find(father, NULL, SEARCH_CHILD);
325}
326GBDATA *GB_nextChild(GBDATA *child) {
327    // return next child after 'child' (or NULL if no more children)
328    return GB_find(child, NULL, SEARCH_NEXT_BROTHER);
329}
330
331// ------------------------------------------------------------------------------
332//      iterate over all subentries of a container that have a specified key
333
334GBDATA *GB_entry(GBDATA *father, const char *key) { 
335    // return first child of 'father' that has fieldname 'key'
336    // (or NULL if none found)
337    return GB_find(father, key, SEARCH_CHILD);
338}
339GBDATA *GB_nextEntry(GBDATA *entry) {
340    // return next child after 'entry', that has the same fieldname
341    // (or NULL if 'entry' is last one)
342    return GB_find_sub_by_quark(GB_FATHER(entry), GB_get_quark(entry), entry, 0);
343}
344GBDATA *GB_followingEntry(GBDATA *entry, size_t skip_over) {
345    // return following child after 'entry', that has the same fieldname
346    // (or NULL if no such entry)
347    // skips 'skip_over' entries (skip_over == 0 behaves like GB_nextEntry)
348    return GB_find_sub_by_quark(GB_FATHER(entry), GB_get_quark(entry), entry, skip_over);
349}
350
351GBDATA *GB_brother(GBDATA *entry, const char *key) {
352    // searches (first) brother (before or after) of 'entry' which has field 'key'
353    // i.e. does same as GB_entry(GB_get_father(entry), key)
354    return GB_find(entry, key, SEARCH_BROTHER);
355}
356
357GBDATA *gb_find_by_nr(GBCONTAINER *father, int index) {
358    /* get a subentry by its internal number:
359       Warning: This subentry must exists, otherwise internal error */
360
361    gb_header_list *header = GB_DATA_LIST_HEADER(father->d);
362    if (index >= father->d.nheader || index <0) {
363        GB_internal_errorf("Index '%i' out of range [%i:%i[", index, 0, father->d.nheader);
364        return NULL;
365    }
366    if (header[index].flags.changed >= GB_DELETED || !header[index].flags.key_quark) {
367        GB_internal_error("Entry already deleted");
368        return NULL;
369    }
370
371    GBDATA *gb = GB_HEADER_LIST_GBD(header[index]);
372    if (!gb) {
373        gb_unfold(father, 0, index);
374        header = GB_DATA_LIST_HEADER(father->d);
375        gb = GB_HEADER_LIST_GBD(header[index]);
376        if (!gb) {
377            GB_internal_error("Could not unfold data");
378            return NULL;
379        }
380    }
381    return gb;
382}
383
384class keychar_table {
385    bool table[256];
386public:
387    keychar_table() {
388        for (int i=0; i<256; i++) {
389            table[i] = islower(i) || isupper(i) || isdigit(i) || i=='_' || i=='@';
390        }
391    }
392    const char *first_non_key_character(const char *str) const {
393        while (1) {
394            int c = *str;
395            if (!table[c]) {
396                if (c == 0) break;
397                return str;
398            }
399            str++;
400        }
401        return NULL;
402    }
403};
404static keychar_table keychars;
405
406const char *GB_first_non_key_char(const char *str) {
407    return keychars.first_non_key_character(str);
408}
409
410inline GBDATA *find_or_create(GBCONTAINER *gb_parent, const char *key, GB_TYPES create, bool internflag) {
411    gb_assert(!keychars.first_non_key_character(key));
412
413    GBDATA *gbd = GB_entry(gb_parent, key);
414    if (create) {
415        if (gbd) {
416            GB_TYPES oldType = gbd->type();
417            if (create != oldType) { // type mismatch
418                GB_export_errorf("Inconsistent type for field '%s' (existing=%i, expected=%i)", key, oldType, create);
419                gbd = NULL;
420            }
421        }
422        else {
423            if (create == GB_CREATE_CONTAINER) {
424                gbd = internflag ? gb_create_container(gb_parent, key) : GB_create_container(gb_parent, key);
425            }
426            else {
427                gbd = gb_create(gb_parent, key, create);
428            }
429            gb_assert(gbd || GB_have_error());
430        }
431    }
432    return gbd;
433}
434
435GBDATA *gb_search(GBCONTAINER *gbc, const char *key, GB_TYPES create, int internflag) {
436    /* finds a hierarchical key,
437     * if create != GB_FIND(==0), then create the key
438     * force types if ! internflag
439     */
440
441    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
442
443    GB_test_transaction(gbc);
444
445    if (!key) return NULL; // was allowed in the past (and returned the 1st child). now returns NULL
446   
447    if (key[0] == '/') {
448        gbc = gb_get_root(gbc);
449        key++;
450    }
451
452    if (!key[0]) {
453        return gbc;
454    }
455
456    GBDATA     *gb_result     = NULL;
457    const char *separator     = keychars.first_non_key_character(key);
458    if (!separator) gb_result = find_or_create(gbc, key, create, internflag);
459    else {
460        int  len = separator-key;
461        char firstKey[len+1];
462        memcpy(firstKey, key, len);
463        firstKey[len] = 0;
464
465        char invalid_char = 0;
466
467        switch (separator[0]) {
468            case '/': {
469                GBDATA *gb_sub = find_or_create(gbc, firstKey, create ? GB_CREATE_CONTAINER : GB_FIND, internflag);
470                if (gb_sub) {
471                    if (gb_sub->is_container()) {
472                        if (separator[1] == '/') {
473                            GB_export_errorf("Invalid '//' in key '%s'", key);
474                        }
475                        else {
476                            gb_result = gb_search(gb_sub->as_container(), separator+1, create, internflag);
477                        }
478                    }
479                    else {
480                        GB_export_errorf("terminal entry '%s' cannot be used as container", firstKey);
481                    }
482                }
483                break;
484            }
485            case '.': {
486                if (separator[1] != '.') invalid_char = separator[0];
487                else {
488                    GBCONTAINER *gb_parent = gbc->get_father();
489                    if (gb_parent) {
490                        switch (separator[2]) {
491                            case 0:   gb_result    = gb_parent; break;
492                            case '/': gb_result    = gb_search(gb_parent, separator+3, create, internflag); break;
493                            default:
494                                GB_export_errorf("Expected '/' after '..' in key '%s'", key);
495                                break;
496                        }
497                    }
498                    else { // ".." at root-node
499                        if (create) {
500                            GB_export_error("cannot use '..' at root node");
501                        }
502                    }
503                }
504                break;
505            }
506            case '-':
507                if (separator[1] != '>') invalid_char = separator[0];
508                else {
509                    if (firstKey[0]) {
510                        GBDATA *gb_link = GB_entry(gbc, firstKey);
511                        if (!gb_link) {
512                            if (create) {
513                                GB_export_error("Cannot create links on the fly in gb_search");
514                            }
515                        }
516                        else {
517                            if (gb_link->type() == GB_LINK) {
518                                GBDATA *gb_target = GB_follow_link(gb_link);
519                                if (!gb_target) GB_export_errorf("Link '%s' points nowhere", firstKey);
520                                else    gb_result = gb_search(gb_target->as_container(), separator+2, create, internflag);
521                            }
522                            else GB_export_errorf("'%s' exists, but is not a link", firstKey);
523                        }
524                    }
525                    else GB_export_errorf("Missing linkname before '->' in '%s'", key);
526                }
527                break;
528
529            default:
530                invalid_char = separator[0];
531                break;
532        }
533
534        if (invalid_char) {
535            gb_assert(!gb_result);
536            GB_export_errorf("Invalid char '%c' in key '%s'", invalid_char, key);
537        }
538    }
539    gb_assert(!(gb_result && GB_have_error()));
540    return gb_result;
541}
542
543
544GBDATA *GB_search(GBDATA *gbd, const char *fieldpath, GB_TYPES create) {
545    return gb_search(gbd->expect_container(), fieldpath, create, 0);
546}
547
548static GBDATA *gb_expect_type(GBDATA *gbd, GB_TYPES expected_type, const char *fieldname) {
549    gb_assert(expected_type != GB_FIND); // impossible
550
551    GB_TYPES type = gbd->type();
552    if (type != expected_type) {
553        GB_export_errorf("Field '%s' has wrong type (found=%i, expected=%i)", fieldname, type, expected_type);
554        gbd = 0;
555    }
556    return gbd;
557}
558
559GBDATA *GB_searchOrCreate_string(GBDATA *gb_container, const char *fieldpath, const char *default_value) {
560    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
561
562    GBDATA *gb_str = GB_search(gb_container, fieldpath, GB_FIND);
563    if (!gb_str) {
564        GB_clear_error();
565        gb_str = GB_search(gb_container, fieldpath, GB_STRING);
566        GB_ERROR error;
567
568        if (!gb_str) error = GB_await_error();
569        else error         = GB_write_string(gb_str, default_value);
570
571        if (error) {
572            gb_str = 0;
573            GB_export_error(error);
574        }
575    }
576    else {
577        gb_str = gb_expect_type(gb_str, GB_STRING, fieldpath);
578    }
579    return gb_str;
580}
581
582GBDATA *GB_searchOrCreate_int(GBDATA *gb_container, const char *fieldpath, long default_value) {
583    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
584
585    GBDATA *gb_int = GB_search(gb_container, fieldpath, GB_FIND);
586    if (!gb_int) {
587        gb_int = GB_search(gb_container, fieldpath, GB_INT);
588        GB_ERROR error;
589
590        if (!gb_int) error = GB_await_error();
591        else error         = GB_write_int(gb_int, default_value);
592
593        if (error) { // @@@ in case GB_search returned 0, gb_int already is 0 and error is exported. just assert error is exported
594            gb_int = 0;
595            GB_export_error(error);
596        }
597    }
598    else {
599        gb_int = gb_expect_type(gb_int, GB_INT, fieldpath);
600    }
601    return gb_int;
602}
603
604GBDATA *GB_searchOrCreate_float(GBDATA *gb_container, const char *fieldpath, float default_value) {
605    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
606
607    GBDATA *gb_float = GB_search(gb_container, fieldpath, GB_FIND);
608    if (!gb_float) {
609        gb_float = GB_search(gb_container, fieldpath, GB_FLOAT);
610        GB_ERROR error;
611
612        if (!gb_float) error = GB_await_error();
613        else error           = GB_write_float(gb_float, default_value);
614
615        if (error) {
616            gb_float = 0;
617            GB_export_error(error);
618        }
619    }
620    else {
621        gb_float = gb_expect_type(gb_float, GB_FLOAT, fieldpath);
622    }
623    return gb_float;
624}
625
626static GBDATA *gb_search_marked(GBCONTAINER *gbc, GBQUARK key_quark, int firstindex, size_t skip_over) {
627    int             userbit = GBCONTAINER_MAIN(gbc)->users[0]->userbit;
628    int             index;
629    int             end     = gbc->d.nheader;
630    gb_header_list *header  = GB_DATA_LIST_HEADER(gbc->d);
631
632    for (index = firstindex; index<end; index++) {
633        GBDATA *gb;
634
635        if (! (userbit & header[index].flags.flags)) continue;
636        if ((key_quark>=0) && (header[index].flags.key_quark  != key_quark)) continue;
637        if (header[index].flags.changed >= GB_DELETED) continue;
638        if ((gb=GB_HEADER_LIST_GBD(header[index]))==NULL) {
639            gb_unfold(gbc, 0, index);
640            header = GB_DATA_LIST_HEADER(gbc->d);
641            gb = GB_HEADER_LIST_GBD(header[index]);
642        }
643        if (!skip_over--) return gb;
644    }
645    return NULL;
646}
647
648long GB_number_of_marked_subentries(GBDATA *gbd) {
649    long count = 0;
650    if (gbd->is_container()) {
651        GBCONTAINER    *gbc    = gbd->as_container();
652        gb_header_list *header = GB_DATA_LIST_HEADER(gbc->d);
653
654        int userbit = GBCONTAINER_MAIN(gbc)->users[0]->userbit;
655        int end     = gbc->d.nheader;
656
657        for (int index = 0; index<end; index++) {
658            if (!(userbit & header[index].flags.flags)) continue;
659            if (header[index].flags.changed >= GB_DELETED) continue;
660            count++;
661        }
662    }
663    return count;
664}
665
666
667
668GBDATA *GB_first_marked(GBDATA *gbd, const char *keystring) {
669    GBCONTAINER *gbc       = gbd->expect_container();
670    GBQUARK      key_quark = GB_find_existing_quark(gbd, keystring);
671    GB_test_transaction(gbc);
672    return key_quark ? gb_search_marked(gbc, key_quark, 0, 0) : NULL;
673}
674
675
676GBDATA *GB_following_marked(GBDATA *gbd, const char *keystring, size_t skip_over) {
677    GBCONTAINER *gbc       = GB_FATHER(gbd);
678    GBQUARK      key_quark = GB_find_existing_quark(gbd, keystring);
679    GB_test_transaction(gbc);
680    return key_quark ? gb_search_marked(gbc, key_quark, (int)gbd->index+1, skip_over) : NULL;
681}
682
683GBDATA *GB_next_marked(GBDATA *gbd, const char *keystring) {
684    return GB_following_marked(gbd, keystring, 0);
685}
686
687// ----------------------------
688//      Command interpreter
689
690void gb_install_command_table(GBDATA *gb_main, struct GBL_command_table *table, size_t table_size) {
691    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
692    if (!Main->command_hash) Main->command_hash = GBS_create_hash(table_size, GB_IGNORE_CASE);
693
694    for (; table->command_identifier; table++) {
695        GBS_write_hash(Main->command_hash, table->command_identifier, (long)table->function);
696    }
697
698    gb_assert((GBS_hash_elements(Main->command_hash)+1) == table_size);
699}
700
701static char *gbs_search_second_x(const char *str) {
702    int c;
703    for (; (c=*str); str++) {
704        if (c=='\\') {      // escaped characters
705            str++;
706            if (!(c=*str)) return NULL;
707            continue;
708        }
709        if (c=='"') return (char *)str;
710    }
711    return NULL;
712}
713
714char *gbs_search_second_bracket(const char *source) {
715    int c;
716    int deep = 0;
717    if (*source != '(') deep --;    // first bracket
718    for (; (c=*source); source++) {
719        if (c=='\\') {      // escaped characters
720            source++;
721            if (!*source) break;
722            continue;
723        }
724        if (c=='(') deep--;
725        else if (c==')') deep++;
726        if (!deep) return (char *)source;
727        if (c=='"') {       // search the second "
728            source = gbs_search_second_x(source);
729            if (!source) return NULL;
730        }
731    }
732    if (!c) return NULL;
733    return (char *)source;
734}
735
736
737static char *gbs_search_next_separator(const char *source, const char *seps) {
738    // search the next separator
739    static char tab[256];
740    static int flag = 0;
741    int c;
742    const char *p;
743    if (!flag) {
744        flag = 1;
745        memset(tab, 0, 256);
746    }
747    for (p = seps; (c=*p); p++) tab[c] = 1; // tab[seps[x]] = 1
748    tab['('] = 1;               // exclude () pairs
749    tab['"'] = 1;               // exclude " pairs
750    tab['\\'] = 1;              // exclude \-escaped chars
751
752    for (; (c=*source); source++) {
753        if (tab[c]) {
754            if (c=='\\') {
755                source++;
756                continue;
757            }
758            if (c=='(') {
759                source = gbs_search_second_bracket(source);
760                if (!source) break;
761                continue;
762            }
763            if (c=='"') {
764                source = gbs_search_second_x(source+1);
765                if (!source) break;
766                continue;
767            }
768            for (p = seps; (c=*p); p++) tab[c] = 0;
769            return (char *)source;
770        }
771    }
772    for (p = seps; (c=*p); p++) tab[c] = 0; // clear tab
773    return NULL;
774}
775
776static void dumpStreams(const char *name, const GBL_streams& streams) {
777    int count = streams.size();
778    printf("%s=%i\n", name, count);
779    if (count > 0) {
780        for (int c = 0; c<count; c++) {
781            printf("  %02i='%s'\n", c, streams.get(c));
782        }
783    }
784}
785
786static const char *shortenLongString(const char *str, size_t wanted_len) {
787    // shortens the string 'str' to 'wanted_len' (appends '[..]' if string was shortened)
788
789    const char *result;
790    size_t      len = strlen(str);
791
792    gb_assert(wanted_len>4);
793
794    if (len>wanted_len) {
795        static char   *shortened_str;
796        static size_t  short_len = 0;
797
798        if (short_len >= wanted_len) {
799            memcpy(shortened_str, str, wanted_len-4);
800        }
801        else {
802            freeset(shortened_str, GB_strpartdup(str, str+wanted_len));
803            short_len = wanted_len;
804        }
805        strcpy(shortened_str+wanted_len-4, "[..]");
806        result = shortened_str;
807    }
808    else {
809        result = str;
810    }
811    return result;
812}
813
814static char *apply_ACI(GBDATA *gb_main, const char *commands, const char *str, GBDATA *gbd, const char *default_tree_name) {
815    int trace = GB_get_ACISRT_trace();
816
817    GB_MAIN_TYPE *Main    = GB_MAIN(gb_main);
818    gb_local->gbl.gb_main = gb_main;
819
820    char *buffer = strdup(commands);
821
822    // ********************** remove all spaces and tabs *******************
823    {
824        const char *s1;
825        char *s2;
826        s1 = commands;
827        s2 = buffer;
828        {
829            int c;
830            for (; (c = *s1); s1++) {
831                if (c=='\\') {
832                    *(s2++) = c;
833                    if (!(c=*++s1)) { break; }
834                    *(s2++) = c;
835                    continue;
836                }
837
838                if (c=='"') {       // search the second "
839                    const char *hp = gbs_search_second_x(s1+1);
840                    if (!hp) {
841                        GB_export_errorf("unbalanced '\"' in '%s'", commands);
842                        free(buffer);
843                        return NULL;
844                    }
845                    while (s1 <= hp) *(s2++) = *(s1++);
846                    s1--;
847                    continue;
848                }
849                if (c!=' ' && c!='\t') *(s2++) = c;
850            }
851        }
852        *s2 = 0;
853    }
854
855    GBL_streams orig;
856
857    orig.insert(strdup(str));
858
859    GB_ERROR error = NULL;
860    GBL_streams out;
861    {
862        char *s1, *s2;
863        s1 = buffer;
864        if (*s1 == '|') s1++;
865
866        // ** loop over all commands **
867        for (; s1;  s1 = s2) {
868            int separator;
869            GBL_COMMAND command;
870            s2 = gbs_search_next_separator(s1, "|;,");
871            if (s2) {
872                separator = *(s2);
873                *(s2++) = 0;
874            }
875            else {
876                separator = 0;
877            }
878            // collect the parameters
879            GBL_streams in;
880            if (*s1 == '"') {           // copy "text" to out
881                char *end = gbs_search_second_x(s1+1);
882                if (!end) {
883                    UNCOVERED(); // seems unreachable (balancing is already ensured by gbs_search_next_separator)
884                    error = "Missing second '\"'";
885                    break;
886                }
887                *end = 0;
888
889                out.insert(strdup(s1+1));
890            }
891            else {
892                char *bracket   = strchr(s1, '(');
893
894                if (bracket) {      // I got the parameter list
895                    int slen;
896                    *(bracket++) = 0;
897                    slen  = strlen(bracket);
898                    if (bracket[slen-1] != ')') {
899                        error = "Missing ')'";
900                    }
901                    else {
902                        // go through the parameters
903                        char *p1, *p2;
904                        bracket[slen-1] = 0;
905                        for (p1 = bracket; p1;  p1 = p2) {
906                            p2 = gbs_search_next_separator(p1, ";,");
907                            if (p2) {
908                                *(p2++) = 0;
909                            }
910                            if (p1[0] == '"') { // remove "" pairs
911                                int len2;
912                                p1++;
913                                len2 = strlen(p1)-1;
914
915                                if (p1[len2] != '\"') {
916                                    error = "Missing '\"'";
917                                }
918                                else {
919                                    p1[len2] = 0;
920                                }
921                            }
922                            in.insert(strdup(p1));
923                        }
924                    }
925                }
926                if (!error && (bracket || *s1)) {
927                    char *p = s1;
928                    int c;
929                    while ((c = *p)) {          // command to lower case
930                        if (c>='A' && c<='Z') {
931                            c += 'a'-'A';
932                            *p = c;
933                        }
934                        p++;
935                    }
936
937                    command = (GBL_COMMAND)GBS_read_hash(Main->command_hash, s1);
938                    if (!command) {
939                        error = GBS_global_string("Unknown command '%s'", s1);
940                    }
941                    else {
942                        GBL_command_arguments args(gbd, default_tree_name, orig, in, out);
943
944                        args.command = s1;
945
946                        if (trace) {
947                            printf("-----------------------\nExecution of command '%s':\n", args.command);
948                            dumpStreams("Arguments", args.param);
949                            dumpStreams("InputStreams", args.input);
950                        }
951
952                        error = command(&args); // execute the command
953
954                        if (!error && trace) dumpStreams("OutputStreams", args.output);
955
956                        if (error) {
957                            char *dup_error = strdup(error);
958
959#define MAX_PRINT_LEN 200
960
961                            char *paramlist = 0;
962                            for (int j = 0; j<args.param.size(); ++j) {
963                                const char *param       = args.param.get(j);
964                                const char *param_short = shortenLongString(param, MAX_PRINT_LEN);
965
966                                if (!paramlist) paramlist = strdup(param_short);
967                                else freeset(paramlist, GBS_global_string_copy("%s,%s", paramlist, param_short));
968                            }
969                            char *inputstreams = 0;
970                            for (int j = 0; j<args.input.size(); ++j) {
971                                const char *input       = args.input.get(j);
972                                const char *input_short = shortenLongString(input, MAX_PRINT_LEN);
973
974                                if (!inputstreams) inputstreams = strdup(input_short);
975                                else freeset(inputstreams, GBS_global_string_copy("%s;%s", inputstreams, input_short));
976                            }
977#undef MAX_PRINT_LEN
978                            if (paramlist) {
979                                error = GBS_global_string("while applying '%s(%s)'\nto '%s':\n%s", s1, paramlist, inputstreams, dup_error);
980                            }
981                            else {
982                                error = GBS_global_string("while applying '%s'\nto '%s':\n%s", s1, inputstreams, dup_error);
983                            }
984
985                            free(inputstreams);
986                            free(paramlist);
987                            free(dup_error);
988                        }
989                    }
990                }
991            }
992
993            if (error) break;
994
995            if (separator == '|') { // out -> in pipe; clear in
996                out.swap(orig);
997                out.erase();
998            }
999        }
1000    }
1001
1002    {
1003        char *s1 = out.concatenated();
1004        free(buffer);
1005
1006        if (!error) {
1007            if (trace) printf("GB_command_interpreter: result='%s'\n", s1);
1008            return s1;
1009        }
1010        free(s1);
1011    }
1012
1013    GB_export_errorf("Command '%s' failed:\nReason: %s", commands, error);
1014    return NULL;
1015}
1016// --------------------------------------------------------------------------------
1017
1018char *GBL_streams::concatenated() const {
1019    int count = size();
1020    if (!count) return strdup("");
1021    if (count == 1) return strdup(get(0));
1022
1023    GBS_strstruct *strstruct = GBS_stropen(1000);
1024    for (int i=0; i<count; i++) {
1025        const char *s = get(i);
1026        if (s) GBS_strcat(strstruct, s);
1027    }
1028    return GBS_strclose(strstruct);
1029}
1030
1031char *GB_command_interpreter(GBDATA *gb_main, const char *str, const char *commands, GBDATA *gbd, const char *default_tree_name) {
1032    /* simple command interpreter returns NULL on error (which should be exported in that case)
1033     * if first character is == ':' run string parser
1034     * if first character is == '/' run regexpr
1035     * else run ACI
1036     */
1037
1038    int trace = GB_get_ACISRT_trace();
1039    SmartMallocPtr(char) heapstr;
1040
1041    if (!str) {
1042        if (!gbd) {
1043            GB_export_error("ACI: no input streams found");
1044            return NULL;
1045        }
1046
1047        if (GB_read_type(gbd) == GB_STRING) {
1048            str = GB_read_char_pntr(gbd);
1049        }
1050        else {
1051            char *asstr = GB_read_as_string(gbd);
1052            if (!asstr) {
1053                GB_export_error("Can't read this DB entry as string");
1054                return NULL;
1055            }
1056
1057            heapstr = asstr;
1058            str     = &*heapstr;
1059        }
1060    }
1061
1062    if (trace) {
1063        printf("GB_command_interpreter: str='%s'\n"
1064               "                        command='%s'\n", str, commands);
1065    }
1066
1067    if (!commands || !commands[0]) { // empty command -> do not modify string
1068        return strdup(str);
1069    }
1070
1071    if (commands[0] == ':') { // ':' -> string parser
1072        return GBS_string_eval(str, commands+1, gbd);
1073    }
1074
1075    if (commands[0] == '/') { // regular expression
1076        GB_ERROR  err    = 0;
1077        char     *result = GBS_regreplace(str, commands, &err);
1078
1079        if (!result) {
1080            if (strcmp(err, "Missing '/' between search and replace string") == 0) {
1081                // if GBS_regreplace didn't find a third '/' -> silently use GBS_regmatch:
1082                size_t matchlen;
1083                err    = 0;
1084                const char *matched = GBS_regmatch(str, commands, &matchlen, &err);
1085
1086                if (matched) result   = GB_strndup(matched, matchlen);
1087                else if (!err) result = strdup("");
1088            }
1089
1090            if (!result && err) result = GBS_global_string_copy("<Error: %s>", err);
1091        }
1092        return result;
1093    }
1094
1095    return apply_ACI(gb_main, commands, str, gbd, default_tree_name);
1096}
1097
1098// --------------------------------------------------------------------------------
1099
1100#ifdef UNIT_TESTS
1101#include <test_unit.h>
1102
1103#define TEST_CI__INTERNAL(input,cmd,expected,got,TEST_RESULT) do {                                                      \
1104        char *result;                                                                                                   \
1105        TEST_EXPECT_RESULT__NOERROREXPORTED(result = GB_command_interpreter(gb_main, input, cmd, gb_data, NULL));       \
1106        TEST_RESULT(result,expected,got);                                                                               \
1107        free(result);                                                                                                   \
1108    } while(0)
1109
1110#define TEST_CI(input,cmd,expected)              TEST_CI__INTERNAL(input,cmd,expected,narg, TEST_EXPECT_EQUAL__IGNARG)
1111#define TEST_CI__BROKEN(input,cmd,expected,regr) TEST_CI__INTERNAL(input,cmd,expected,regr, TEST_EXPECT_EQUAL__BROKEN)
1112#define TEST_CI_NOOP(inandout,cmd)               TEST_CI__INTERNAL(inandout,cmd,inandout,narg,TEST_EXPECT_EQUAL__IGNARG)
1113#define TEST_CI_NOOP__BROKEN(inandout,regr,cmd)  TEST_CI__INTERNAL(inandout,cmd,inandout,regr,TEST_EXPECT_EQUAL__BROKEN)
1114
1115#define TEST_CI_INVERSE(in,cmd,inv_cmd,out) do {        \
1116        TEST_CI(in,  cmd,     out);                     \
1117        TEST_CI(out, inv_cmd, in);                      \
1118    } while(0)
1119
1120#define TEST_CI_ERROR_CONTAINS(input,cmd,errorpart_expected) \
1121    TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_command_interpreter(gb_main, input, cmd, gb_data, NULL), errorpart_expected)
1122
1123#define ACI_SPLIT          "|split(\",\",0)"
1124#define ACI_MERGE          "|merge(\",\")"
1125#define WITH_SPLITTED(aci) ACI_SPLIT aci ACI_MERGE
1126
1127void TEST_GB_command_interpreter() {
1128    GB_shell  shell;
1129    GBDATA   *gb_main = GB_open("TEST_nuc.arb", "rw"); // ../UNIT_TESTER/run/TEST_nuc.arb
1130
1131    GBT_set_default_alignment(gb_main, "ali_16s"); // for sequence ACI command (@@@ really needed ? )
1132
1133    int old_trace = GB_get_ACISRT_trace();
1134#if 0
1135    GB_set_ACISRT_trace(1); // used to detect coverage and for debugging purposes
1136#endif
1137    {
1138        GB_transaction  ta(gb_main);
1139        GBDATA         *gb_data = GBT_find_species(gb_main, "LcbReu40");
1140
1141        TEST_CI_NOOP("bla", "");
1142
1143        TEST_CI("bla", ":a=u", "blu"); // simple SRT
1144
1145        TEST_CI("bla",    "/a/u/",   "blu"); // simple regExp replace
1146        TEST_CI("blabla", "/l.*b/",  "lab"); // simple regExp match
1147        TEST_CI("blabla", "/b.b/",   "");    // simple regExp match (failing)
1148
1149        // escape / quote
1150        TEST_CI_INVERSE("ac", "|quote",        "|unquote",          "\"ac\"");
1151        TEST_CI_INVERSE("ac", "|escape",       "|unescape",         "ac");
1152        TEST_CI_INVERSE("ac", "|escape|quote", "|unquote|unescape", "\"ac\"");
1153        TEST_CI_INVERSE("ac", "|quote|escape", "|unescape|unquote", "\\\"ac\\\"");
1154
1155        TEST_CI_INVERSE("a\"b\\c", "|quote",        "|unquote",          "\"a\"b\\c\"");
1156        TEST_CI_INVERSE("a\"b\\c", "|escape",       "|unescape",         "a\\\"b\\\\c");
1157        TEST_CI_INVERSE("a\"b\\c", "|escape|quote", "|unquote|unescape", "\"a\\\"b\\\\c\"");
1158        TEST_CI_INVERSE("a\"b\\c", "|quote|escape", "|unescape|unquote", "\\\"a\\\"b\\\\c\\\"");
1159
1160        TEST_CI_NOOP("ac", "|unquote");
1161        TEST_CI_NOOP("\"ac", "|unquote");
1162        TEST_CI_NOOP("ac\"", "|unquote");
1163
1164        TEST_CI("blabla", "|coUNT(ab)",         "4");   // simple ACI
1165        TEST_CI("l",      "|\"b\";dd;\"a\"|dd", "bla"); // ACI with muliple streams
1166        TEST_CI("bla",    "|count()",           "0");   // one empty parameter
1167        TEST_CI("bla",    "|count(\"\")",       "0");   // empty parameter
1168        TEST_CI("b a",    "|count(\" \")",      "1");   // space in quotes
1169        TEST_CI("b\\a",   "|count(\\a)",        "2");   // count '\\' and 'a' (ok)
1170        TEST_CI__BROKEN("b\\a",   "|count(\"\\a\")",    "1", "2"); // should only count 'a' (which is escaped in param)
1171        TEST_CI("b\\a",   "|count(\"\a\")",     "0");   // does not contain '\a'
1172        TEST_CI("b\a",    "|count(\"\a\")",     "1");   // counts '\a'
1173
1174        // escaping (@@@ wrong behavior?)
1175        TEST_CI("b\\a",   "|count(\\a)",         "2"); // i would expect '1' as result (the 'a'), but it counts '\\' and 'a'
1176        TEST_CI("b\\a",   "|contains(\"\\\\\")", "0"); // searches for 2 backslashes, finds none
1177        TEST_CI__BROKEN("b\\a",   "|contains(\"\")",     "0", "1"); // search for nothing,              but reports 1 hit
1178        TEST_CI__BROKEN("b\\a",   "|contains(\\)",       "1", "2"); // searches for 1 backslash (ok),   but reports two hits instead of one
1179        TEST_CI__BROKEN("b\\\\a", "|contains(\"\\\\\")", "1", "2"); // searches for 2 backslashes (ok), but reports two hits instead of one
1180        TEST_CI_ERROR_CONTAINS("b\\a", "|contains(\"\\\")", "ARB ERROR: unbalanced '\"' in '|contains(\"\\\")'"); // raises error (should search for 1 backslash)
1181
1182        // test binary ops
1183        TEST_CI("", "\"5\";\"7\"|minus",            "-2");
1184        TEST_CI("", "\"5\"|minus(\"7\")",           "-2");
1185        TEST_CI("", "|minus(\"\"5\"\", \"\"7\"\")", "-2"); // @@@ syntax needed here 'minus(""5"", ""7"")' is broken - need to unescape correctly after parsing parameters!
1186
1187
1188        TEST_CI_NOOP("ab,bcb,abac", WITH_SPLITTED(""));
1189        TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|len"),                       "2,3,4");
1190        TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|count(a)"),                  "1,0,2");
1191        TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|minus(len,count(a))"),       "1,3,2");
1192        TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|minus(\"\"5\"\",count(a))"), "4,5,3"); // @@@ escaping broken here as well
1193
1194        // test other recursive uses of GB_command_interpreter
1195        TEST_CI("one",   "|dd;\"two\";dd|command(\"dd;\"_\";dd;\"-\"\")",                          "one_one-two_two-one_one-");
1196        TEST_CI("",      "|sequence|command(\"/^([\\\\.-]*)[A-Z].*/\\\\1/\")|len",                 "9"); // count gaps at start of sequence
1197        TEST_CI("one",   "|dd;dd|eval(\"\"up\";\"per\"|merge\")",                                  "ONEONE");
1198        TEST_CI("1,2,3", WITH_SPLITTED("|select(\"\"\"\"one\"\", \"\"two\"\", \"\"three\"\")"), "one,two,three");
1199        TEST_CI_ERROR_CONTAINS("1,4", WITH_SPLITTED("|select(\"\"\"\"one\"\", \"\"two\"\", \"\"three\"\")"), "Illegal param number '4' (allowed [0..3])");
1200
1201        // test define and do (@@@ SLOW)
1202        TEST_CI("ignored", "define(d4, \"dd;dd;dd;dd\")",        "");
1203        TEST_CI("ignored", "define(d16, \"do(d4)|do(d4)\")",     "");
1204        TEST_CI("ignored", "define(d64, \"do(d4)|do(d16)\")",    "");
1205        TEST_CI("ignored", "define(d4096, \"do(d64)|do(d64)\")", "");
1206
1207        TEST_CI("x",  "do(d4)",        "xxxx");
1208        TEST_CI("xy", "do(d4)",        "xyxyxyxy");
1209        TEST_CI("x",  "do(d16)",       "xxxxxxxxxxxxxxxx");
1210        TEST_CI("x",  "do(d64)|len",   "64");
1211        TEST_CI("xy", "do(d4)|len",    "8");
1212        TEST_CI("xy", "do(d4)|len()",  "8");
1213        TEST_CI("xy", "do(d4)|len(x)", "4");
1214        TEST_CI("x",  "do(d4096)|len", "4096");
1215
1216        {
1217            int prev_trace = GB_get_ACISRT_trace();
1218            GB_set_ACISRT_trace(0); // do not trace here
1219            // create 4096 streams:
1220            TEST_CI("x",  "dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|streams", "4096");
1221            GB_set_ACISRT_trace(prev_trace);
1222        }
1223
1224        // other commands
1225
1226        // streams
1227        TEST_CI("x", "dd;dd|streams",             "2");
1228        TEST_CI("x", "dd;dd|dd;dd|streams",       "4");
1229        TEST_CI("x", "dd;dd|dd;dd|dd;dd|streams", "8");
1230        TEST_CI("x", "do(d4)|streams",            "1"); // stream is merged when do() returns
1231
1232        TEST_CI("", "ali_name", "ali_16s");  // ask for default-alignment name
1233        TEST_CI("", "sequence_type", "rna"); // ask for sequence_type of default-alignment
1234
1235        // format
1236        TEST_CI("acgt", "format", "          acgt");
1237        TEST_CI("acgt", "format(firsttab=1)", " acgt");
1238        TEST_CI("acgt", "format(firsttab=1, width=2)",
1239                " ac\n"
1240                "          gt");
1241        TEST_CI("acgt", "format(firsttab=1,tab=1,width=2)",
1242                " ac\n"
1243                " gt");
1244        TEST_CI("acgt", "format(firsttab=0,tab=0,width=2)",
1245                "ac\n"
1246                "gt");
1247        TEST_CI("acgt", "format(firsttab=0,tab=0,width=1)",
1248                "a\n"
1249                "c\n"
1250                "g\n"
1251                "t");
1252
1253        TEST_CI_ERROR_CONTAINS("acgt", "format(gap=0)",   "Unknown Parameter 'gap=0' in command 'format'");
1254        TEST_CI_ERROR_CONTAINS("acgt", "format(numleft)", "Unknown Parameter 'numleft' in command 'format'");
1255
1256        // format_sequence
1257        TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(numright=5, numleft)", "You may only specify 'numleft' OR 'numright',  not both");
1258
1259        TEST_CI("acgtacgtacgtacg", "format_sequence(firsttab=5,tab=5,width=4,numleft=1)",
1260                "1    acgt\n"
1261                "5    acgt\n"
1262                "9    acgt\n"
1263                "13   acg");
1264
1265        TEST_CI("acgtacgtacgtacg", "format_sequence(firsttab=5,tab=5,width=4,numright=9)", // test EMBL sequence formatting
1266                "     acgt         4\n"
1267                "     acgt         8\n"
1268                "     acgt        12\n"
1269                "     acg         15");
1270
1271        TEST_CI("acgtacgtacgtac", "format_sequence(firsttab=5,tab=5,width=4,gap=2,numright=-1)", // autodetect width for 'numright'
1272                "     ac gt  4\n"
1273                "     ac gt  8\n"
1274                "     ac gt 12\n"
1275                "     ac    14");
1276
1277        TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,width=2,gap=1)",
1278                "a c\n"
1279                "g t");
1280        TEST_CI("acgt",     "format_sequence(firsttab=0,tab=0,width=4,gap=1)", "a c g t");
1281        TEST_CI("acgt",     "format_sequence(firsttab=0,tab=0,width=4,gap=2)", "ac gt");
1282        TEST_CI("acgtacgt", "format_sequence(firsttab=0,width=10,gap=4)",      "acgt acgt");
1283        TEST_CI("acgtacgt", "format_sequence(firsttab=1,width=10,gap=4)",      " acgt acgt");
1284
1285        TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(nl=c)",     "Unknown Parameter 'nl=c' in command 'format_sequence'");
1286        TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(forcenl=)", "Unknown Parameter 'forcenl=' in command 'format_sequence'");
1287
1288        TEST_CI_ERROR_CONTAINS("acgt", "format(width=0)",          "Illegal zero width");
1289        TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(width=0)", "Illegal zero width");
1290
1291        // remove + keep
1292        TEST_CI_NOOP("acgtacgt",         "remove(-.)");
1293        TEST_CI     ("..acg--ta-cgt...", "remove(-.)", "acgtacgt");
1294        TEST_CI     ("..acg--ta-cgt...", "remove(acgt)", "..---...");
1295
1296        TEST_CI_NOOP("acgtacgt",         "keep(acgt)");
1297        TEST_CI     ("..acg--ta-cgt...", "keep(-.)", "..---...");
1298        TEST_CI     ("..acg--ta-cgt...", "keep(acgt)", "acgtacgt");
1299
1300        // compare + icompare
1301        TEST_CI("x,z,y,y,z,x,x,Z,y,Y,Z,x", WITH_SPLITTED("|compare"),  "-1,0,1,1,1,-1");
1302        TEST_CI("x,z,y,y,z,x,x,Z,y,Y,Z,x", WITH_SPLITTED("|icompare"), "-1,0,1,-1,0,1");
1303
1304        TEST_CI("x,y,z", WITH_SPLITTED("|compare(\"y\")"), "-1,0,1");
1305
1306        // equals + iequals
1307        TEST_CI("a,b,a,a,a,A", WITH_SPLITTED("|equals"),  "0,1,0");
1308        TEST_CI("a,b,a,a,a,A", WITH_SPLITTED("|iequals"), "0,1,1");
1309
1310        // contains + icontains
1311        TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|contains(\"bc\")"),   "2,1,0");
1312        TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|icontains(\"bc\")"),  "2,1,1");
1313        TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|icontains(\"d\")"),   "0,3,3");
1314
1315        // partof + ipartof
1316        TEST_CI("abc,BCD,def,deg", WITH_SPLITTED("|partof(\"abcdefg\")"),   "1,0,4,0");
1317        TEST_CI("abc,BCD,def,deg", WITH_SPLITTED("|ipartof(\"abcdefg\")"),  "1,2,4,0");
1318
1319        // translate
1320        TEST_CI("abcdefgh", "translate(abc,cba)",     "cbadefgh");
1321        TEST_CI("abcdefgh", "translate(cba,abc)",     "cbadefgh");
1322        TEST_CI("abcdefgh", "translate(hcba,abch,-)", "hcb----a");
1323        TEST_CI("abcdefgh", "translate(aceg,aceg,-)", "a-c-e-g-");
1324        TEST_CI("abbaabba", "translate(ab,ba,-)",     "baabbaab");
1325        TEST_CI("abbaabba", "translate(a,x,-)",       "x--xx--x");
1326        TEST_CI("abbaabba", "translate(,,-)",         "--------");
1327
1328        // echo
1329        TEST_CI("", "echo", "");
1330        TEST_CI("", "echo(x,y,z)", "xyz");
1331        TEST_CI("", "echo(x;y,z)", "xyz"); // check ';' as param-separator
1332        TEST_CI("", "echo(x;y;z)", "xyz");
1333        TEST_CI("", "echo(x,y,z)|streams", "3");
1334
1335        // upper, lower + caps
1336        TEST_CI("the QUICK brOwn Fox", "lower", "the quick brown fox");
1337        TEST_CI("the QUICK brOwn Fox", "upper", "THE QUICK BROWN FOX");
1338        TEST_CI("the QUICK brOwn FoX", "caps",  "The Quick Brown Fox");
1339
1340        // head, tail + mid/mid0
1341        TEST_CI     ("1234567890", "head(3)", "123");
1342        TEST_CI     ("1234567890", "head(9)", "123456789");
1343        TEST_CI_NOOP("1234567890", "head(10)");
1344        TEST_CI_NOOP("1234567890", "head(20)");
1345
1346        TEST_CI     ("1234567890", "tail(4)", "7890");
1347        TEST_CI     ("1234567890", "tail(9)", "234567890");
1348        TEST_CI_NOOP("1234567890", "tail(10)");
1349        TEST_CI_NOOP("1234567890", "tail(20)");
1350
1351        TEST_CI("1234567890", "tail(0)", "");
1352        TEST_CI("1234567890", "head(0)", "");
1353        TEST_CI("1234567890", "tail(-2)", "");
1354        TEST_CI("1234567890", "head(-2)", "");
1355
1356        TEST_CI("1234567890", "mid(3,5)", "345");
1357        TEST_CI("1234567890", "mid(2,2)", "2");
1358
1359        TEST_CI("1234567890", "mid0(3,5)", "456");
1360
1361        TEST_CI("1234567890", "mid(9,20)", "90");
1362        TEST_CI("1234567890", "mid(20,20)", "");
1363
1364        TEST_CI("1234567890", "tail(3)",     "890"); // example from ../HELP_SOURCE/oldhelp/commands.hlp@mid0
1365        TEST_CI("1234567890", "mid(-2,0)",   "890");
1366        TEST_CI("1234567890", "mid0(-3,-1)", "890");
1367
1368        // tab + pretab
1369        TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(2)"),    "x ,xx,xxx");
1370        TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(3)"),    "x  ,xx ,xxx");
1371        TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(4)"),    "x   ,xx  ,xxx ");
1372        TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(2)"), " x,xx,xxx");
1373        TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(3)"), "  x, xx,xxx");
1374        TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(4)"), "   x,  xx, xxx");
1375
1376        // crop
1377        TEST_CI("   x  x  ",         "crop(\" \")",     "x  x");
1378        TEST_CI("\n \t  x  x \n \t", "crop(\"\t\n \")", "x  x");
1379
1380        // cut, drop, dropempty and dropzero
1381        TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|cut(2,3,5)"),        "two,three,five");
1382        TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|drop(2,3,5)"),       "one,four,six");
1383
1384        TEST_CI_ERROR_CONTAINS("a", "drop(2)", "Illegal stream number '2' (allowed [1..1])");
1385        TEST_CI_ERROR_CONTAINS("a", "drop(0)", "Illegal stream number '0' (allowed [1..1])");
1386        TEST_CI_ERROR_CONTAINS("a", "cut(2)",  "Illegal stream number '2' (allowed [1..1])");
1387        TEST_CI_ERROR_CONTAINS("a", "cut(0)",  "Illegal stream number '0' (allowed [1..1])");
1388
1389        TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|dropempty|streams"), "6");
1390        TEST_CI("one,two,,,five,six",          WITH_SPLITTED("|dropempty|streams"), "4");
1391        TEST_CI(",,,,,",                       WITH_SPLITTED("|dropempty"),         "");
1392        TEST_CI(",,,,,",                       WITH_SPLITTED("|dropempty|streams"), "0");
1393
1394        TEST_CI("1,0,0,2,3,0",                 WITH_SPLITTED("|dropzero"),          "1,2,3");
1395        TEST_CI("0,0,0,0,0,0",                 WITH_SPLITTED("|dropzero"),          "");
1396        TEST_CI("0,0,0,0,0,0",                 WITH_SPLITTED("|dropzero|streams"),  "0");
1397
1398        // swap
1399        TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap"),                "1,2,3,four,six,five");
1400        TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)"),           "1,3,2,four,five,six");
1401        TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)|swap(4,3)"), "1,3,four,2,five,six");
1402        TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,3)"));
1403        TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(2,3)"));
1404        TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(3,1)|swap(2,1)|swap(1,3)"));
1405
1406        TEST_CI_ERROR_CONTAINS("a",   "swap",                      "need at least two input streams");
1407        TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(2,3)"), "Illegal stream number '3' (allowed [1..2])");
1408        TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(3,2)"), "Illegal stream number '3' (allowed [1..2])");
1409
1410        // toback + tofront
1411        TEST_CI     ("front,mid,back", WITH_SPLITTED("|toback(2)"),  "front,back,mid");
1412        TEST_CI     ("front,mid,back", WITH_SPLITTED("|tofront(2)"), "mid,front,back");
1413        TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|toback(3)"));
1414        TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|tofront(1)"));
1415        TEST_CI_NOOP("a",              WITH_SPLITTED("|tofront(1)"));
1416        TEST_CI_NOOP("a",              WITH_SPLITTED("|toback(1)"));
1417
1418        TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|tofront(3)"), "Illegal stream number '3' (allowed [1..2])");
1419        TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|toback(3)"),  "Illegal stream number '3' (allowed [1..2])");
1420
1421        // split (default)
1422        TEST_CI("a\nb", "|split" ACI_MERGE, "a,b");
1423
1424        // extract_words + extract_sequence
1425        TEST_CI("1,2,3,four,five,six", "extract_words(\"0123456789\",1)",                  "1 2 3");
1426        TEST_CI("1,2,3,four,five,six", "extract_words(\"abcdefghijklmnopqrstuvwxyz\", 3)", "five four six");
1427        TEST_CI("1,2,3,four,five,six", "extract_words(\"abcdefghijklmnopqrstuvwxyz\", 4)", "five four");
1428        TEST_CI("1,2,3,four,five,six", "extract_words(\"abcdefghijklmnopqrstuvwxyz\", 5)", "");
1429
1430        TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 1.0)",   "");
1431        TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 0.5)",   "");
1432        TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 0.0)",   "four five six");
1433        TEST_CI     ("..acg--ta-cgt...",       "extract_sequence(\"acgtu\", 1.0)",   "");
1434        TEST_CI_NOOP("..acg--ta-cgt...",       "extract_sequence(\"acgtu-.\", 1.0)");
1435        TEST_CI_NOOP("..acg--ta-ygt...",       "extract_sequence(\"acgtu-.\", 0.7)");
1436        TEST_CI     ("70 ..acg--ta-cgt... 70", "extract_sequence(\"acgtu-.\", 1.0)", "..acg--ta-cgt...");
1437
1438        // checksum + gcgchecksum
1439        TEST_CI("", "sequence|checksum",      "4C549A5F");
1440        TEST_CI("", "sequence | gcgchecksum", "4308");
1441
1442        // SRT
1443        TEST_CI(        "The quick brown fox", "srt(\"quick=lazy:brown fox=dog\")", "The lazy dog");
1444        TEST_CI__BROKEN("The quick brown fox", "srt(quick=lazy:brown fox=dog)",
1445                        "The lazy dog",        // @@@ parsing problem ?
1446                        "The lazy brown fox"); // document current (unwanted behavior)
1447        TEST_CI_ERROR_CONTAINS("x", "srt(x=y,z)", "SRT ERROR: no '=' found in command");
1448
1449        // REG
1450        TEST_CI("stars*to*stripes", "/\\*/--/", "stars--to--stripes");
1451
1452        TEST_CI("sImILaRWIllBE,GonEEASIly", WITH_SPLITTED("|command(/[A-Z]//)"),   "small,only");
1453        TEST_CI("sthBIGinside,FATnotCAP",   WITH_SPLITTED("|command(/([A-Z])+/)"), "BIG,FAT");
1454
1455        // calculator
1456        TEST_CI("", "echo(9,3)|plus",     "12");
1457        TEST_CI("", "echo(9,3)|minus",    "6");
1458        TEST_CI("", "echo(9,3)|mult",     "27");
1459        TEST_CI("", "echo(9,3)|div",      "3");
1460        TEST_CI("", "echo(9,3)|rest",     "0");
1461        TEST_CI("", "echo(9,3)|per_cent", "300");
1462
1463        TEST_CI("", "echo(1,2,3)|plus(1)" ACI_MERGE,     "2,3,4");
1464        TEST_CI("", "echo(1,2,3)|minus(2)" ACI_MERGE,    "-1,0,1");
1465        TEST_CI("", "echo(1,2,3)|mult(42)" ACI_MERGE,    "42,84,126");
1466        TEST_CI("", "echo(1,2,3)|div(2)" ACI_MERGE,      "0,1,1");
1467        TEST_CI("", "echo(1,2,3)|rest(2)" ACI_MERGE,     "1,0,1");
1468        TEST_CI("", "echo(1,2,3)|per_cent(3)" ACI_MERGE, "33,66,100");
1469
1470        // readdb
1471        TEST_CI("", "readdb(name)", "LcbReu40");
1472        TEST_CI("", "readdb(acc)",  "X76328");
1473
1474        // taxonomy
1475        TEST_CI("", "taxonomy(1)",           "No default tree");
1476        TEST_CI("", "taxonomy(tree_nuc, 1)", "group1");
1477        TEST_CI("", "taxonomy(tree_nuc, 5)", "top/lower-red/group1");
1478        TEST_CI_ERROR_CONTAINS("", "taxonomy", "syntax: taxonomy([tree_name,]count)");
1479
1480        // diff, filter + change
1481        TEST_CI("..acg--ta-cgt..." ","
1482                "..acg--ta-cgt...", WITH_SPLITTED("|diff(pairwise=1)"),
1483                "................");
1484        TEST_CI("..acg--ta-cgt..." ","
1485                "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,equal==)"),
1486                "==cgt=====acg===");
1487        TEST_CI("..acg--ta-cgt..." ","
1488                "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,differ=X)"),
1489                "..XXX.....XXX...");
1490        TEST_CI("", "sequence|diff(species=LcbFruct)|checksum", "645E3107");
1491
1492        TEST_CI("..XXX.....XXX..." ","
1493                "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,exclude=X)"),
1494                "..--ta-...");
1495        TEST_CI("..XXX.....XXX..." ","
1496                "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,include=X)"),
1497                "acgcgt");
1498        TEST_CI("", "sequence|filter(species=LcbFruct,include=.-)", "-----------T----T-------G----------C-----T----T...");
1499
1500        TEST_CI("...XXX....XXX..." ","
1501                "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=C,change=100)"),
1502                "..aCC--ta-CCC...");
1503        TEST_CI("...XXXXXXXXX...." ","
1504                "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=-,change=100)"),
1505                "..a---------t...");
1506
1507
1508        // exec
1509        TEST_CI("c,b,c,b,a,a", WITH_SPLITTED("|exec(\"(sort|uniq)\")|split|dropempty"),              "a,b,c");
1510        TEST_CI("a,aba,cac",   WITH_SPLITTED("|exec(\"perl\",-pe,s/([bc])/$1$1/g)|split|dropempty"), "a,abba,ccacc");
1511
1512         // error cases
1513        TEST_CI_ERROR_CONTAINS("", "nocmd",          "Unknown command 'nocmd'");
1514        TEST_CI_ERROR_CONTAINS("", "|nocmd",         "Unknown command 'nocmd'");
1515        TEST_CI_ERROR_CONTAINS("", "caps(x)",        "syntax: caps (no parameters)");
1516        TEST_CI_ERROR_CONTAINS("", "trace",          "syntax: trace(0|1)");
1517        TEST_CI_ERROR_CONTAINS("", "count",          "syntax: count(\"characters to count\")");
1518        TEST_CI_ERROR_CONTAINS("", "count(a,b)",     "syntax: count(\"characters to count\")");
1519        TEST_CI_ERROR_CONTAINS("", "len(a,b)",       "syntax: len[(\"characters not to count\")]");
1520        TEST_CI_ERROR_CONTAINS("", "plus(a,b,c)",    "syntax: plus[(Expr1[,Expr2])]");
1521        TEST_CI_ERROR_CONTAINS("", "count(a,b",      "Reason: Missing ')'");
1522        TEST_CI_ERROR_CONTAINS("", "count(a,\"b)",   "unbalanced '\"' in 'count(a,\"b)'");
1523        TEST_CI_ERROR_CONTAINS("", "count(a,\"b)\"", "Reason: Missing ')'");
1524        TEST_CI_ERROR_CONTAINS("", "dd;dd|count",    "syntax: count(\"characters to count\")");
1525        TEST_CI_ERROR_CONTAINS("", "|count(\"a\"x)", "Reason: Missing '\"'");
1526        TEST_CI_ERROR_CONTAINS("", "|count(\"a)",    "unbalanced '\"' in '|count(\"a)'");
1527
1528        TEST_CI_ERROR_CONTAINS("", "translate(a)",       "syntax: translate(old,new[,other])");
1529        TEST_CI_ERROR_CONTAINS("", "translate(a,b,c,d)", "syntax: translate(old,new[,other])");
1530
1531        TEST_CI_ERROR_CONTAINS(NULL, "whatever", "ARB ERROR: Can't read this DB entry as string"); // here gb_data is the species container
1532
1533        gb_data = GB_entry(gb_data, "full_name"); // execute ACI on 'full_name' from here on ------------------------------
1534
1535        TEST_CI(NULL, "", "Lactobacillus reuteri"); // noop
1536        TEST_CI(NULL, "|len", "21");
1537        TEST_CI(NULL, ":tobac=", "Lacillus reuteri");
1538        TEST_CI(NULL, "/ba.*us/B/", "LactoB reuteri");
1539
1540        TEST_CI(NULL, "|taxonomy(1)", "No default tree");
1541        TEST_CI_ERROR_CONTAINS(NULL, "|taxonomy(tree_nuc,2)", "Container has neither 'name' nor 'group_name' entry - can't detect container type");
1542
1543        gb_data = NULL;
1544        TEST_CI_ERROR_CONTAINS(NULL, "", "no input streams found");
1545    }
1546
1547    GB_set_ACISRT_trace(old_trace); // avoid side effect of TEST_GB_command_interpreter
1548    GB_close(gb_main);
1549}
1550
1551const char *GBT_get_name(GBDATA *gb_item); 
1552
1553struct TestDB : virtual Noncopyable {
1554    GB_shell  shell;
1555    GBDATA   *gb_main;
1556    GB_ERROR  error;
1557
1558    GBDATA *gb_cont1;
1559    GBDATA *gb_cont2;
1560    GBDATA *gb_cont_empty;
1561    GBDATA *gb_cont_misc;
1562
1563    GB_ERROR create_many_items(GBDATA *gb_parent, const char **item_key_list, int item_count) {
1564        int k = 0;
1565        for (int i = 0; i<item_count && !error; i++) {
1566            const char *item_key    = item_key_list[k++];
1567            if (!item_key) { item_key = item_key_list[0]; k = 1; }
1568
1569            GBDATA *gb_child = GB_create_container(gb_parent, item_key);
1570            if (!gb_child) {
1571                error = GB_await_error();
1572            }
1573            else {
1574                if ((i%7) == 0) GB_write_flag(gb_child, 1); // mark some
1575               
1576                GBDATA *gb_name = GB_create(gb_child, "name", GB_STRING);
1577                error           = GB_write_string(gb_name, GBS_global_string("%s %i", item_key, i));
1578            }
1579        }
1580        return error;
1581    }
1582
1583    TestDB() {
1584        gb_main = GB_open("nosuch.arb", "c");
1585        error   = gb_main ? NULL : GB_await_error();
1586
1587        if (!error) {
1588            GB_transaction ta(gb_main);
1589            gb_cont1      = GB_create_container(gb_main, "container1");
1590            gb_cont2      = GB_create_container(gb_main, "container2");
1591            gb_cont_empty = GB_create_container(gb_main, "empty");
1592            gb_cont_misc  = GB_create_container(gb_main, "misc");
1593
1594            if (!gb_cont1 || !gb_cont2) error = GB_await_error();
1595
1596            const char *single_key[] = { "entry", NULL };
1597            const char *mixed_keys[] = { "item", "other", NULL };
1598
1599            if (!error) error = create_many_items(gb_cont1, single_key, 100);
1600            if (!error) error = create_many_items(gb_cont2, mixed_keys, 20);
1601        }
1602        TEST_EXPECT_NO_ERROR(error);
1603    }
1604    ~TestDB() {
1605        GB_close(gb_main);
1606    }
1607};
1608
1609void TEST_DB_search() {
1610    TestDB db;
1611    TEST_EXPECT_NO_ERROR(db.error);
1612
1613    {
1614        GB_transaction ta(db.gb_main);
1615
1616        TEST_EXPECT_EQUAL(GB_number_of_subentries(db.gb_cont1), 100);
1617        TEST_EXPECT_EQUAL(GB_number_of_subentries(db.gb_cont2), 20);
1618
1619        {
1620            GBDATA *gb_any_child = GB_child(db.gb_cont1);
1621            TEST_REJECT_NULL(gb_any_child);
1622            TEST_EXPECT_EQUAL(gb_any_child, GB_entry(db.gb_cont1, "entry"));
1623            TEST_EXPECT_EQUAL(gb_any_child, GB_search(db.gb_main, "container1/entry", GB_FIND));
1624
1625            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "zic-zac", GB_FIND), "Invalid char '-' in key 'zic-zac'");
1626
1627            // check link-syntax
1628            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "->entry", GB_FIND), "Missing linkname before '->' in '->entry'");
1629            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_main, "entry->bla", GB_FIND));
1630            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "container1/entry->nowhere", GB_FIND), "'entry' exists, but is not a link");
1631            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "container1/nosuchentry->nowhere", GB_STRING), "Cannot create links on the fly in gb_search");
1632            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_main, "entry->", GB_FIND)); // valid ? just deref link
1633
1634            // check ..
1635            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "..", GB_FIND), db.gb_cont1);
1636            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "../..", GB_FIND), db.gb_main);
1637            // above main entry
1638            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(gb_any_child, "../../..", GB_FIND));
1639            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(gb_any_child, "../../../impossible", GB_STRING), "cannot use '..' at root node");
1640
1641            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "", GB_FIND), gb_any_child); // return self
1642            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/container1/", GB_FIND), db.gb_cont1); // accept trailing slash for container ..
1643            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(gb_any_child, "/container1/entry/name/", GB_FIND), "terminal entry 'name' cannot be used as container"); // .. but not for normal entries
1644
1645            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/", GB_FIND), db.gb_main);
1646            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/container1/..", GB_FIND), db.gb_main);
1647            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(gb_any_child, "/..", GB_FIND)); // main has no parent
1648        }
1649
1650        {
1651            GBDATA *gb_child1 = GB_child(db.gb_cont2);   TEST_REJECT_NULL(gb_child1);
1652            GBDATA *gb_child2 = GB_nextChild(gb_child1); TEST_REJECT_NULL(gb_child2);
1653            GBDATA *gb_child3 = GB_nextChild(gb_child2); TEST_REJECT_NULL(gb_child3);
1654            GBDATA *gb_child4 = GB_nextChild(gb_child3); TEST_REJECT_NULL(gb_child4);
1655
1656            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child1), "item");
1657            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child2), "other");
1658            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child3), "item");
1659            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child4), "other");
1660
1661            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_entry(db.gb_cont2, "entry"));
1662            TEST_EXPECT_EQUAL(GB_entry(db.gb_cont2, "item"),  gb_child1);
1663            TEST_EXPECT_EQUAL(GB_entry(db.gb_cont2, "other"), gb_child2);
1664
1665            TEST_EXPECT_EQUAL(GB_nextEntry(gb_child1), gb_child3);
1666            TEST_EXPECT_EQUAL(GB_nextEntry(gb_child2), gb_child4);
1667
1668            // check ..
1669            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../item", GB_FIND), gb_child1);
1670            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../other", GB_FIND), gb_child2);
1671            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../other/../item", GB_FIND), gb_child1);
1672        }
1673
1674        // ------------------------
1675        //      single entries
1676
1677        {
1678            GBDATA *gb_str      = GB_searchOrCreate_string(db.gb_cont_misc, "str", "bla");   TEST_REJECT_NULL(gb_str);
1679            GBDATA *gb_str_same = GB_searchOrCreate_string(db.gb_cont_misc, "str", "blub");
1680
1681            TEST_EXPECT_EQUAL(gb_str, gb_str_same);
1682            TEST_EXPECT_EQUAL(GB_read_char_pntr(gb_str), "bla");
1683
1684            GBDATA *gb_int      = GB_searchOrCreate_int(db.gb_cont_misc, "int", 4711);   TEST_REJECT_NULL(gb_int);
1685            GBDATA *gb_int_same = GB_searchOrCreate_int(db.gb_cont_misc, "int", 2012);
1686
1687            TEST_EXPECT_EQUAL(gb_int, gb_int_same);
1688            TEST_EXPECT_EQUAL(GB_read_int(gb_int), 4711);
1689
1690            GBDATA *gb_float      = GB_searchOrCreate_float(db.gb_cont_misc, "float", 0.815);   TEST_REJECT_NULL(gb_float);
1691            GBDATA *gb_float_same = GB_searchOrCreate_float(db.gb_cont_misc, "float", 3.1415);
1692
1693            TEST_EXPECT_EQUAL(gb_float, gb_float_same);
1694            TEST_EXPECT_SIMILAR(GB_read_float(gb_float), 0.815, 0.0001);
1695
1696            TEST_EXPECT_EQUAL  (GB_read_char_pntr(GB_searchOrCreate_string(db.gb_cont_misc, "sub1/str",    "blub")), "blub");
1697            TEST_EXPECT_EQUAL  (GB_read_int      (GB_searchOrCreate_int   (db.gb_cont_misc, "sub2/int",    2012)),   2012);
1698            TEST_EXPECT_SIMILAR(GB_read_float    (GB_searchOrCreate_float (db.gb_cont_misc, "sub3/float", 3.1415)), 3.1415, 0.00001);
1699
1700            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_float (db.gb_cont_misc, "int",   0.815), "has wrong type");
1701            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_float (db.gb_cont_misc, "str",   0.815), "has wrong type");
1702            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_int   (db.gb_cont_misc, "float", 4711),  "has wrong type");
1703            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_int   (db.gb_cont_misc, "str",   4711),  "has wrong type");
1704            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "float", "bla"), "has wrong type");
1705            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "int",   "bla"), "has wrong type");
1706
1707            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "*", "bla"), "Invalid char '*' in key '*'");
1708            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "int*", "bla"), "Invalid char '*' in key 'int*'");
1709            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "sth_else*", "bla"), "Invalid char '*' in key 'sth_else*'");
1710
1711            GBDATA *gb_entry;
1712            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_cont_misc, "subcont/entry", GB_FIND));
1713            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_entry = GB_search(db.gb_cont_misc, "subcont/entry", GB_INT));
1714            TEST_EXPECT_EQUAL(GB_read_int(gb_entry), 0);
1715
1716            GBDATA *gb_cont1;
1717            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont1 = GB_search(db.gb_cont_misc, "subcont", GB_CREATE_CONTAINER));
1718            TEST_EXPECT_EQUAL(GB_child(gb_cont1), gb_entry); // test GB_search found the container created implicitely above
1719
1720            GBDATA *gb_cont2;
1721            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont2 = GB_search(db.gb_cont_misc, "subcont2", GB_CREATE_CONTAINER)); // create new container
1722
1723            // -----------------------
1724            //      search values
1725
1726            GBDATA *gb_4711;
1727            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_4711 = GB_find_int(db.gb_cont_misc, "int", 4711, SEARCH_CHILD));
1728            TEST_EXPECT_EQUAL(gb_4711, gb_int);
1729
1730            TEST_EXPECT_NULL(GB_find_int(db.gb_cont_misc, "int", 2012, SEARCH_CHILD));
1731
1732            GBDATA *gb_bla;
1733            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bla = GB_find_string(db.gb_cont_misc, "str", "bla", GB_MIND_CASE, SEARCH_CHILD));
1734            TEST_EXPECT_EQUAL(gb_bla, gb_str);
1735            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bla = GB_find_string(gb_4711, "str", "bla", GB_MIND_CASE, SEARCH_BROTHER));
1736            TEST_EXPECT_EQUAL(gb_bla, gb_str);
1737
1738            TEST_EXPECT_NULL(GB_find_string(db.gb_cont_misc, "str", "blub", GB_MIND_CASE, SEARCH_CHILD));
1739
1740            GBDATA *gb_name;
1741            TEST_REJECT_NULL                  (GB_find_string          (db.gb_cont1, "name", "entry 77",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
1742            TEST_REJECT_NULL                  (GB_find_string          (db.gb_cont1, "name", "entry 99",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
1743            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_find_string          (db.gb_cont1, "name", "entry 100", GB_MIND_CASE,   SEARCH_GRANDCHILD));
1744            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_find_string          (db.gb_cont1, "name", "ENTRY 13",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
1745            TEST_REJECT_NULL                  (gb_name = GB_find_string(db.gb_cont1, "name", "ENTRY 13",  GB_IGNORE_CASE, SEARCH_GRANDCHILD));
1746
1747            GBDATA *gb_sub;
1748            TEST_REJECT_NULL(gb_sub = GB_get_father(gb_name));        TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 13");
1749            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 0));  TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 14");
1750            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 1));  TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 16");
1751            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 10)); TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 27");
1752            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_followingEntry(gb_sub, -1U));
1753            TEST_REJECT_NULL(gb_sub = GB_brother(gb_sub, "entry"));   TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 0");
1754
1755            TEST_EXPECT_EQUAL(gb_bla = GB_search(gb_cont1, "/misc/str", GB_FIND), gb_str); // fullpath (ignores passed GBDATA)
1756
1757            // keyless search
1758            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_cont_misc, NULL, GB_FIND));
1759
1760            // ----------------------------
1761            //      GB_get_GBDATA_path
1762
1763            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_int),   "/main/misc/int");
1764            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_str),   "/main/misc/str");
1765            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_entry), "/main/misc/subcont/entry");
1766            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_cont2),  "/main/misc/subcont2");
1767
1768            // -----------------------------------------
1769            //      search/create with changed type
1770
1771            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "str", GB_INT), "Inconsistent type for field");
1772            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont", GB_STRING), "Inconsistent type for field");
1773
1774            // ---------------------------------------------
1775            //      search containers with trailing '/'
1776
1777            GBDATA *gb_cont2_slash;
1778            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont2_slash = GB_search(db.gb_cont_misc, "subcont2/", GB_FIND));
1779            TEST_EXPECT_EQUAL(gb_cont2_slash, gb_cont2);
1780
1781            GBDATA *gb_rootcont;
1782            GBDATA *gb_rootcont_slash;
1783            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_rootcont = GB_search(db.gb_main, "/container1", GB_FIND));
1784            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_rootcont_slash = GB_search(db.gb_main, "/container1/", GB_FIND));
1785            TEST_EXPECT_EQUAL(gb_rootcont_slash, gb_rootcont);
1786        }
1787
1788        {
1789            // check invalid searches
1790            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/inva*lid",   GB_INT),  "Invalid char '*' in key 'inva*lid'");
1791            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/1 3",        GB_INT),  "Invalid char ' ' in key '1 3'");
1792            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub//sub",       GB_INT),  "Invalid '//' in key 'sub//sub'");
1793            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont2//",     GB_FIND), "Invalid '//' in key 'subcont2//'");
1794            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/..sub",      GB_INT),  "Expected '/' after '..' in key '..sub'");
1795            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont/entry/", GB_FIND), "terminal entry 'entry' cannot be used as container");
1796        }
1797
1798        // ---------------
1799        //      marks
1800
1801        TEST_EXPECT_EQUAL(GB_number_of_marked_subentries(db.gb_cont1), 15);
1802        TEST_EXPECT_EQUAL(GB_number_of_marked_subentries(db.gb_cont2), 3);
1803
1804        TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_first_marked(db.gb_cont2, "entry"));
1805
1806        GBDATA *gb_marked;
1807        TEST_REJECT_NULL(gb_marked = GB_first_marked(db.gb_cont2, "item"));
1808        TEST_EXPECT_EQUAL(GBT_get_name(gb_marked), "item 0");
1809
1810        TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_following_marked(gb_marked, "item", 1)); // skip over last
1811       
1812        TEST_REJECT_NULL(gb_marked = GB_next_marked(gb_marked, "item")); // find last
1813        TEST_EXPECT_EQUAL(GBT_get_name(gb_marked), "item 14");
1814    }
1815
1816    // @@@ delete some species, then search again
1817
1818    TEST_EXPECT_NO_ERROR(db.error);
1819}
1820
1821
1822#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.