source: tags/ms_r18q1/ARBDB/admatch.cxx

Last change on this file was 16766, checked in by westram, 6 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 24.6 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : admatch.cxx                                       //
4//   Purpose   : functions related to string match/replace         //
5//                                                                 //
6//   ReCoded for POSIX ERE                                         //
7//            by Ralf Westram (coder@reallysoft.de) in April 2009  //
8//   Institute of Microbiology (Technical University Munich)       //
9//   http://www.arb-home.de/                                       //
10//                                                                 //
11// =============================================================== //
12
13#include "gb_local.h"
14
15#include "gb_aci_impl.h"
16
17#include <arb_strbuf.h>
18#include <arb_match.h>
19
20#include <cctype>
21
22using namespace GBL_IMPL;
23
24// ------------------------
25//      string matcher
26
27enum string_matcher_type {
28    SM_INVALID = -1,
29    SM_ANY     = 0,             // matches any string
30    SM_WILDCARDED,              // match with wildcards (GBS_string_matches)
31    SM_REGEXPR,                 // match using regexpr
32};
33
34struct GBS_string_matcher {
35    string_matcher_type  type;
36    GB_CASE              case_flag;
37    char                *wildexpr;
38    GBS_regex           *regexpr;
39};
40
41GBS_string_matcher *GBS_compile_matcher(const char *search_expr, GB_CASE case_flag) {
42    /* returns a valid string matcher (to be used with GBS_string_matches_regexp)
43     * or NULp (in which case an error was exported)
44     */
45
46    GBS_string_matcher *matcher = ARB_alloc<GBS_string_matcher>(1);
47    GB_ERROR            error   = NULp;
48
49    matcher->type      = SM_INVALID;
50    matcher->case_flag = case_flag;
51    matcher->wildexpr  = NULp;
52    matcher->regexpr   = NULp;
53
54    if (search_expr[0] == '/') {
55        const char *end = strchr(search_expr, 0)-1;
56        if (end>search_expr && end[0] == '/') {
57            GB_CASE     expr_attached_case;
58            const char *unwrapped_expr = GBS_unwrap_regexpr(search_expr, &expr_attached_case, &error);
59
60            if (unwrapped_expr) {
61                if (expr_attached_case != GB_MIND_CASE) error = "format '/../i' not allowed here";
62                else {
63                    matcher->regexpr = GBS_compile_regexpr(unwrapped_expr, case_flag, &error);
64                    if (matcher->regexpr) {
65                        matcher->type = SM_REGEXPR;
66                    }
67                }
68            }
69        }
70    }
71
72    if (!matcher->regexpr && !error) {
73        if (strcmp(search_expr, "*") == 0) {
74            matcher->type = SM_ANY;
75        }
76        else {
77            matcher->type     = SM_WILDCARDED;
78            matcher->wildexpr = ARB_strdup(search_expr);
79        }
80    }
81
82    if (matcher->type == SM_INVALID) {
83        error = GBS_global_string("Failed to create GBS_string_matcher from '%s'", search_expr);
84    }
85
86    if (error) {
87        GBS_free_matcher(matcher);
88        matcher = NULp;
89        GB_export_error(error);
90    }
91    return matcher;
92}
93
94void GBS_free_matcher(GBS_string_matcher *matcher) {
95    free(matcher->wildexpr);
96    if (matcher->regexpr) GBS_free_regexpr(matcher->regexpr);
97    free(matcher);
98}
99
100// -------------------------
101//      wildcard search
102
103GB_CSTR GBS_find_string(GB_CSTR cont, GB_CSTR substr, int match_mode) {
104    /* search a substring in another string
105     * match_mode == 0     -> exact match
106     * match_mode == 1     -> a==A
107     * match_mode == 2     -> a==a && a==?
108     * match_mode == else  -> a==A && a==?
109     */
110    const char *p1, *p2;
111    char        b;
112
113    switch (match_mode) {
114
115        case 0: // exact match
116            for (p1 = cont, p2 = substr; *p1;) {
117                if (!(b = *p2)) {
118                    return (char *)cont;
119                }
120                else {
121                    if (b == *p1) {
122                        p1++;
123                        p2++;
124                    }
125                    else {
126                        p2 = substr;
127                        p1 = (++cont);
128                    }
129                }
130            }
131            if (!*p2)   return (char *)cont;
132            break;
133
134        case 1: // a==A
135            for (p1 = cont, p2 = substr; *p1;) {
136                if (!(b = *p2)) {
137                    return (char *)cont;
138                }
139                else {
140                    if (toupper(*p1) == toupper(b)) {
141                        p1++;
142                        p2++;
143                    }
144                    else {
145                        p2 = substr;
146                        p1 = (++cont);
147                    }
148                }
149            }
150            if (!*p2) return (char *)cont;
151            break;
152        case 2: // a==a && a==?
153            for (p1 = cont, p2 = substr; *p1;) {
154                if (!(b = *p2)) {
155                    return (char *)cont;
156                }
157                else {
158                    if (b == *p1 || (b=='?')) {
159                        p1++;
160                        p2++;
161                    }
162                    else {
163                        p2 = substr;
164                        p1 = (++cont);
165                    }
166                }
167            }
168            if (!*p2) return (char *)cont;
169            break;
170
171        default: // a==A && a==?
172            for (p1 = cont, p2 = substr; *p1;) {
173                if (!(b = *p2)) {
174                    return (char *)cont;
175                }
176                else {
177                    if (toupper(*p1) == toupper(b) || (b=='?')) {
178                        p1++;
179                        p2++;
180                    }
181                    else {
182                        p2 = substr;
183                        p1 = (++cont);
184                    }
185                }
186            }
187            if (!*p2) return (char *)cont;
188            break;
189    }
190    return NULp;
191}
192
193bool GBS_string_matches(const char *str, const char *search, GB_CASE case_sens) {
194    /* Wildcards in 'search' string:
195     *      ?   one character
196     *      *   several characters
197     *
198     * if 'case_sens' == GB_IGNORE_CASE -> change all letters to uppercase
199     *
200     * returns true if strings are equal, false otherwise
201     */
202
203    const char *p1, *p2;
204    char        a, b, *d;
205    long        i;
206    char        fsbuf[256];
207
208    p1 = str;
209    p2 = search;
210    while (1) {
211        a = *p1;
212        b = *p2;
213        if (b == '*') {
214            if (!p2[1]) break; // '*' also matches nothing
215            i = 0;
216            d = fsbuf;
217            for (p2++; (b=*p2)&&(b!='*');) {
218                *(d++) = b;
219                p2++;
220                i++;
221                if (i > 250) break;
222            }
223            if (*p2 != '*') {
224                p1 += strlen(p1)-i;     // check the end of the string
225                if (p1 < str) return false;
226                p2 -= i;
227            }
228            else {
229                *= 0;
230                p1  = GBS_find_string(p1, fsbuf, 2+(case_sens == GB_IGNORE_CASE)); // match with '?' wildcard
231                if (!p1) return false;
232                p1 += i;
233            }
234            continue;
235        }
236
237        if (!a) return !b;
238        if (a != b) {
239            if (b != '?') {
240                if (!b) return !a;
241                if (case_sens == GB_IGNORE_CASE) {
242                    a = toupper(a);
243                    b = toupper(b);
244                    if (a != b) return false;
245                }
246                else {
247                    return false;
248                }
249            }
250        }
251        p1++;
252        p2++;
253    }
254    return true;
255}
256
257bool GBS_string_matches_regexp(const char *str, const GBS_string_matcher *expr) {
258    /* Wildcard or regular expression match
259     * Returns true if match
260     *
261     * Use GBS_compile_matcher() and GBS_free_matcher() to maintain 'expr'
262     */
263    bool matches = false;
264
265    switch (expr->type) {
266        case SM_ANY: {
267            matches = true;
268            break;
269        }
270        case SM_WILDCARDED: {
271            matches = GBS_string_matches(str, expr->wildexpr, expr->case_flag);
272            break;
273        }
274        case SM_REGEXPR: {
275            matches = GBS_regmatch_compiled(str, expr->regexpr, NULp);
276            break;
277        }
278        case SM_INVALID: {
279            gb_assert(0);
280            break;
281        }
282    }
283
284    return matches;
285}
286
287// -----------------------------------
288//      Search replace tool (SRT)
289
290#define GBS_SET   ((char)1)
291#define GBS_SEP   ((char)2)
292#define GBS_MWILD ((char)3)
293#define GBS_WILD  ((char)4)
294
295__ATTR__USERESULT static GB_ERROR gbs_build_replace_string(GBS_strstruct& out,
296                                                           char *replaceBy, // will be modified!
297                                                           const char       *sWildcards, long sWildMax,
298                                                           const char*const *mWildcards, long mWildMax,
299                                                           const GBL_call_env& callEnv)
300{
301    int sWildAuto = 0; // count plain occurrences of '?' in replace string (ie. w/o number behind)
302    int mWildAuto = 0; // same for '*'
303
304    GBDATA *gb_container = callEnv.get_ref();
305
306    char *p = replaceBy;
307    char  c;
308    while ((c=*(p++))) {
309        switch (c) {
310            case GBS_MWILD:
311            case GBS_WILD: {
312                char d = *(p++);
313                if (d=='(') { // "*(..)" expressions
314                    char *closingParen = search_matching_parenthesis(p);
315
316                    if (!closingParen) {
317                        return GBS_global_string("Unbalanced parenthesis in '%s'", p-1);
318                    }
319
320                    // found reference: "*(gbd)"
321                    int separator = 0;
322                    *closingParen = 0;
323                    char *psym = strpbrk(p, "#|:");
324                    if (psym) {
325                        separator = *psym;
326                        *psym = 0;
327                    }
328
329                    GBDATA *gb_entry = NULp;
330                    if (*p) { // key was specified
331                        if (!gb_container) {
332                            return GBS_global_string("can't read key '%s' (called w/o database item)", p);
333                        }
334                        if (!GB_is_container(gb_container)) {
335                            if (ARB_strBeginsWith(p, "../")) { // redirect search via parent
336                                p += 3;
337                                gb_container = GB_get_father(gb_container);
338                            }
339                            else {
340                                return GBS_global_string("can't read key '%s' (DB item is no container)", p);
341                            }
342                        }
343                        gb_entry = GB_search(gb_container, p, GB_FIND);
344                        if (!gb_entry && GB_have_error()) {
345                            return GB_await_error();
346                        }
347                    }
348                    else {
349                        gb_entry = gb_container;
350                    }
351
352                    if (psym) *psym = separator;
353
354                    char *entry = (gb_entry && gb_entry != gb_container)
355                        ? GB_read_as_string(gb_entry)
356                        : ARB_strdup("");
357
358                    if (entry) {
359                        char *h;
360                        switch (separator) {
361                            case ':':
362                                h = GBS_string_eval_in_env(entry, psym+1, callEnv);
363                                if (!h) {
364                                    free(entry);
365                                    return GB_await_error();
366                                }
367
368                                out.cat(h);
369                                free(h);
370                                break;
371
372                            case '|':
373                                h = GB_command_interpreter_in_env(entry, psym+1, callEnv);
374                                if (!h) {
375                                    free(entry);
376                                    return GB_await_error();
377                                }
378
379                                out.cat(h);
380                                free(h);
381                                break;
382
383                            case '#':
384                                if (!entry[0]) { // missing field or empty content
385                                    out.cat(psym+1);
386                                    break;
387                                }
388                                // fall-through
389                            default:
390                                out.cat(entry);
391                                break;
392                        }
393                        free(entry);
394                    }
395                    *closingParen = ')';
396                    p = closingParen+1;
397                }
398                else {
399                    int  wildcard_num       = d - '1';
400                    bool followed_by_number = wildcard_num>=0 && wildcard_num<=9; // @@@ in fact this will also accept ':'
401
402                    if (c == GBS_WILD) {
403                        if (!followed_by_number) { // char behind wildcard is not in [1-9]
404                            --p; // "put back" that character
405                            wildcard_num = sWildAuto++;
406                        }
407                        if (wildcard_num>=sWildMax) {
408                            out.put('?');
409                        }
410                        else {
411                            out.put(sWildcards[wildcard_num]);
412                        }
413                    }
414                    else {
415                        if (!followed_by_number) { // char behind wildcard is not in [1-9]
416                            --p; // "put back" that character
417                            wildcard_num = mWildAuto++;
418                        }
419                        if (wildcard_num>=mWildMax) {
420                            out.put('*');
421                        }
422                        else {
423                            out.cat(mWildcards[wildcard_num]);
424                        }
425                    }
426                }
427                break;
428            }
429            default:
430                out.put(c);
431                break;
432        }
433    }
434    return NULp;
435}
436
437static char *gbs_compress_command(const char *com) {
438    /* Prepare SRT.
439     *
440     * Replaces all
441     *   '=' by GBS_SET
442     *   ':' by GBS_SEP
443     *   '?' by GBS_WILD if followed by a number or '?'
444     *   '*' by GBS_MWILD  or '('
445     * \ is the escape character
446     */
447
448    char       *result = ARB_strdup(com);
449    char       *d      = result;
450    const char *s      = result;
451    char ch;
452
453    while ((ch = *(s++))) {
454        switch (ch) {
455            case '=':   *(d++) = GBS_SET; break;
456            case ':':   *(d++) = GBS_SEP; break;
457            case '?':   *(d++) = GBS_WILD; break;
458            case '*':   *(d++) = GBS_MWILD; break;
459            case '\\':
460                ch = *(s++); if (!ch) { s--; break; };
461                switch (ch) {
462                    case 'n':   *(d++) = '\n'; break;
463                    case 't':   *(d++) = '\t'; break;
464                    case '0':   *(d++) = '\0'; break;
465                    default:    *(d++) = ch; break;
466                }
467                break;
468
469            default: *(d++) = ch; break;
470        }
471    }
472    *d = 0;
473    return result;
474}
475
476// AISC_MKPT_PROMOTE: class GBL_call_env;
477
478char *GBS_string_eval_in_env(const char *insource, const char *icommand, const GBL_call_env& callEnv) {
479     /* GBS_string_eval_in_env replaces substrings in source (implements SRT)
480      * Syntax: command = "oliver=olli:peter=peti"
481      *
482      * Returns a heapcopy of result of replacement.
483      *
484      *         * is a wildcard for any number of characters
485      *         ? is a wildcard for exactly one character
486      *
487      * To reference the parts matched by wildcards on the left side of the '=' use '?' and '*',
488      * to reference in a particular order use
489      *         *1 to reference to the first occurrence of *
490      *         *2 ----------"-------- second ------"-------
491      *         ...
492      *         *9 ----------"-------- ninth -------"-------
493      *
494      * If the first and last characters of the search string are no '*' wildcards,
495      * then the replace is repeated as many times as possible.
496      *
497      * '\' is the escape character: e.g. \n is newline; '\\' is '\'; '\=' is '='; ....
498      *
499      * If the passed GBL_call_env refers to a database entry (which has to be of type GB_DB, i.e. has to be a container),
500      * fields of that container may be inserted using
501      *
502      *         *(arb_field)          is the value of the containers child entry 'arb_field'
503      *         *(arb_field#string)   value of the child entry 'arb_field' or 'string' (if that entry does not exist)
504      *         *(arb_field\:SRT)     runs SRT recursively on the value of the child entry 'arb_field'
505      *         *([arb_field]|ACI)    runs the ACI command interpreter on the value of the child entry 'arb_field' (or on an empty string)
506      *
507      * If an error occurs it returns NULp - in this case the error-message gets exported!
508      *
509      * Notes:
510      * - global interpreter (SRT+ACI+REG) is provided by GB_command_interpreter_in_env()
511      * - REG is provided by GBS_regreplace(), GBS_regmatch() and GBS_regmatch_compiled()
512      * - ACI is only provided via GB_command_interpreter_in_env()
513      */
514    if (!icommand || !icommand[0]) {
515        return ARB_strdup(insource);
516    }
517
518    if (traceACI) {
519        print_trace(GBS_global_string("SR: in='%s' cmd='%s':\n", insource, icommand));
520    }
521    LocallyModify<int> inc(traceIndent, traceIndent+1);
522
523    char *command = gbs_compress_command(icommand);
524
525    // copy insource (to allow to modify it)
526    size_t        inlen = strlen(insource);
527    GBS_strstruct in(inlen+1);
528    in.ncat(insource, inlen);
529
530    GBS_strstruct out(inlen+500);
531
532    GB_ERROR  error = NULp;
533    char     *next_subcmd;
534    for (char *subcmd = command; subcmd; subcmd = next_subcmd) { // loop over sub-commands
535        // search next subcommand (=pos behind next colon):
536        next_subcmd = strchr(subcmd, GBS_SEP);
537        if (next_subcmd) *(next_subcmd++) = 0;
538
539        if (!subcmd[0]) continue; // empty subcommand -> do nothing
540
541        // search for replace string:
542        char *replaceBy = strchr(subcmd+1, GBS_SET);
543        if (!replaceBy) {
544            error = GBS_global_string("SRT ERROR: no '=' found in command '%s' (position > %zi)", icommand, subcmd-command+1);
545            break;
546        }
547        *(replaceBy++) = 0;
548
549        GB_CSTR not_yet_copied = in.get_data(); // point into 'in' string (to not-yet-copied part)
550        out.erase();
551
552        if (in.empty() && subcmd[0] == GBS_MWILD && subcmd[1] == 0) {
553            // plain '*' shall also match an empty input string -> handle manually here
554            const char *empty = "";
555            error             = gbs_build_replace_string(out, replaceBy, NULp, 0, &empty, 1, callEnv);
556        }
557        else {
558            char  sWildcard[40];  // character  which matched vs one '?'
559            char *mWildcard[10];  // substrings which matched vs one '*'
560            long  sWildSeen = 0;  // number of '?' seen (on left side on subcommand)
561            long  mWildSeen = 0;  // number of '*' seen (on left side on subcommand)
562
563            bool match_failed = false;
564            for (GB_CSTR source = not_yet_copied; *source; ) { // loop over string
565                gb_assert(!match_failed);
566
567                char    *search      = subcmd;
568                GB_CSTR  start_match = NULp; // start of string that matches a wildcard (none yet)
569
570                char     c;
571                while (!match_failed && (c = *(search++))) { // match expression vs. string
572                    switch (c) {
573                        case GBS_MWILD: {
574                            if (!start_match) start_match = source;
575
576                            char *start_of_wildcard = search;
577                            if (!(c = *(search++))) {       // last character is a '*' wildcard -> expression matched
578                                mWildcard[mWildSeen++] = ARB_strdup(source);
579                                source                     = strchr(source, 0); // jump to EOS
580                                --search;
581                                break; // (effectively does exit while-loop)
582                            }
583                            // @@@ 'c' read in above if-condition is ignored if non-zero (got tests)
584
585                            while ((c=*(search++)) && c!=GBS_MWILD && c!=GBS_WILD) ; // search the next wildcardstring
586
587                            search--; // back one character
588                            *search = 0;
589
590                            char    what_wild_card = c;
591                            GB_CSTR p              = GBS_find_string(source, start_of_wildcard, 0);
592
593                            if (!p) match_failed = true; // string behind wildcard does not appear in input -> no match
594                            else {
595                                mWildcard[mWildSeen++] = ARB_strpartdup(source, p-1);
596                                source = p + strlen(start_of_wildcard);
597                                *search = what_wild_card;
598                            }
599                            break;
600                        }
601                        case GBS_WILD:
602                            if (!source[0]) match_failed = true; // '?' does not match "nothing" -> no match
603                            else {
604                                if (!start_match) start_match = source;
605                                sWildcard[sWildSeen++]        = *(source++);
606                            }
607                            break;
608
609                        default:
610                            if (start_match) {
611                                if (c != *(source++)) match_failed = true; // mismatch after '?' or after last '*'
612                            }
613                            else {
614                                char *buf1 = search-1;
615
616                                while ((c=*(search++)) && c != GBS_MWILD && c!=GBS_WILD) ;   // search the next wildcardstring
617
618                                search--;                                                    // back one character
619                                *search = 0;
620
621                                char    what_wild_card = c;
622                                GB_CSTR p              = GBS_find_string(source, buf1, 0);
623                                if (!p) {
624                                    // string infrontof wildcard (or EOS) not found -> no match
625                                    match_failed = true;
626                                }
627                                else  {
628                                    start_match = p;
629                                    source      = p + strlen(buf1);
630                                    *search     = what_wild_card;
631                                }
632
633                            }
634                            break;
635                    }
636                }
637
638                if (!match_failed) {
639                    /* now we got
640                     *
641                     * in:                 GBS_strstruct containing entire input string
642                     * source:             pointer to end of match   (inside 'in')
643                     * start_match:        pointer to start of match (inside 'in')
644                     * not_yet_copied:     pointer to the not-copied part of the input string
645                     * replaceBy:          the replace string
646                     */
647
648                    // now look for the replace string
649                    out.ncat(not_yet_copied, start_match-not_yet_copied);                                                                // concat part before the match
650                    error          = gbs_build_replace_string(out, replaceBy, sWildcard, sWildSeen, mWildcard, mWildSeen, callEnv);      // execute SRT command
651                    not_yet_copied = source;
652                }
653
654                for (long i = 0; i < mWildSeen; i++) {
655                    freenull(mWildcard[i]);
656                }
657                sWildSeen = 0;
658                mWildSeen = 0;
659
660                if (error || match_failed) break;
661            }
662        }
663
664        // Note: reached when left side expression didn't match input string
665        // (also reached when done with current sub-expression)
666        if (error) break;
667
668        out.cat(not_yet_copied); // cat the rest of the input
669
670        if (traceACI) {
671            print_trace(GBS_global_string("'%s' -> '%s'\n", in.get_data(), out.get_data()));
672        }
673
674        in.swap_content(out);
675    }
676    free(command);
677    if (error) {
678        GB_export_error(error);
679        return NULp;
680    }
681    return in.release();
682}
683
684char *GBS_string_eval(const char *insource, const char *icommand) {
685    GBL_env      env(NULp, NULp);
686    GBL_call_env callEnv(NULp, env);
687
688    return GBS_string_eval_in_env(insource, icommand, callEnv);
689}
Note: See TracBrowser for help on using the repository browser.