source: tags/arb-6.0.4/CORE/arb_match.cxx

Last change on this file was 10704, checked in by westram, 11 years ago
  • reintegrated branch 'helptest'
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 9.2 KB
Line 
1// ================================================================= //
2//                                                                   //
3//   File      : arb_match.cxx                                       //
4//   Purpose   : POSIX ERE                                           //
5//                                                                   //
6//   Coded by Ralf Westram (coder@reallysoft.de) in September 2013   //
7//   Institute of Microbiology (Technical University Munich)         //
8//   http://www.arb-home.de/                                         //
9//                                                                   //
10// ================================================================= //
11
12// AISC_MKPT_PROMOTE:#ifndef ARB_CORE_H
13// AISC_MKPT_PROMOTE:#include "arb_core.h"
14// AISC_MKPT_PROMOTE:#endif
15
16#include "arb_match.h"
17#include "arb_assert.h"
18#include "arb_msg.h"
19#include "arb_string.h"
20#include "arb_strbuf.h"
21
22#include <regex.h>
23
24// ---------------------------------------------
25//      Regular Expressions search/replace
26
27struct GBS_regex { regex_t compiled; }; // definition exists twice (see ../SL/REGEXPR/RegExpr.cxx)
28
29inline char *give_buffer(size_t size) {
30    static char   *buf     = NULL;
31    static size_t  bufsize = 0;
32
33    if (size<1) size = 1;
34    if (bufsize<size) {
35        bufsize = size;
36        freeset(buf, (char*)malloc(bufsize));
37    }
38    return buf;
39}
40
41GBS_regex *GBS_compile_regexpr(const char *regexpr, GB_CASE case_flag, GB_ERROR *error) {
42    GBS_regex *comreg  = (GBS_regex*)malloc(sizeof(*comreg));
43    int        cflags  = REG_EXTENDED|(case_flag == GB_IGNORE_CASE ? REG_ICASE : 0)|REG_NEWLINE;
44    int        errcode = regcomp(&comreg->compiled, regexpr, cflags);
45
46    if (errcode != 0) {          // error compiling regexpr
47        size_t  size = regerror(errcode, &comreg->compiled, NULL, 0);
48        char   *buf  = give_buffer(size);
49
50        regerror(errcode, &comreg->compiled, buf, size);
51        *error = buf;
52
53        free(comreg);
54        comreg = NULL;
55    }
56    else {
57        *error = NULL;
58    }
59
60    return comreg;
61}
62
63void GBS_free_regexpr(GBS_regex *toFree) {
64    if (toFree) {
65        regfree(&toFree->compiled);
66        free(toFree);
67    }
68}
69
70const char *GBS_unwrap_regexpr(const char *regexpr_in_slashes, GB_CASE *case_flag, GB_ERROR *error) {
71    /* unwraps 'expr' from '/expr/[i]'
72     * if slashes are not present, 'error' is set
73     * 'case_flag' is set to GB_MIND_CASE   if format is '/expr/' or
74     *                    to GB_IGNORE_CASE if format is '/expr/i'
75     *
76     * returns a pointer to a static buffer (containing the unwrapped expression)
77     * (Note: The content is invalidated by the next call to GBS_unwrap_regexpr)
78     */
79
80    const char *result = NULL;
81    const char *end    = strchr(regexpr_in_slashes, 0);
82
83    if (end >= (regexpr_in_slashes+3)) {
84        *case_flag = GB_MIND_CASE;
85        if (end[-1] == 'i') {
86            *case_flag = GB_IGNORE_CASE;
87            end--;
88        }
89        if (regexpr_in_slashes[0] == '/' && end[-1] == '/') {
90            arb_assert(*error == NULL);
91
92            static char   *result_buffer = 0;
93            static size_t  max_len    = 0;
94
95            size_t len = end-regexpr_in_slashes-2;
96            arb_assert(len>0); // don't accept empty expression
97
98            if (len>max_len) {
99                max_len = len*3/2;
100                freeset(result_buffer, (char*)malloc(max_len+1));
101            }
102
103            memcpy(result_buffer, regexpr_in_slashes+1, len);
104            result_buffer[len] = 0;
105
106            result = result_buffer;
107        }
108    }
109
110    if (!result) {
111        *error = GBS_global_string("Regular expression format is '/expr/' or '/expr/i', not '%s'",
112                                   regexpr_in_slashes);
113    }
114    return result;
115}
116
117const char *GBS_regmatch_compiled(const char *str, GBS_regex *comreg, size_t *matchlen) {
118    /* like GBS_regmatch,
119     * - but uses a precompiled regular expression
120     * - no errors can occur here (beside out of memory, which is not handled)
121     */
122
123    regmatch_t  match;
124    int         res      = regexec(&comreg->compiled, str, 1, &match, 0);
125    const char *matchpos = NULL;
126
127    if (res == 0) { // matched
128        matchpos = str+match.rm_so;
129        if (matchlen) *matchlen = match.rm_eo-match.rm_so;
130    }
131
132    return matchpos;
133}
134
135const char *GBS_regmatch(const char *str, const char *regExpr, size_t *matchlen, GB_ERROR *error) {
136    /* searches 'str' for first occurrence of 'regExpr'
137     * 'regExpr' has to be in format "/expr/[i]", where 'expr' is a POSIX extended regular expression
138     *
139     * for regexpression format see http://dev.mikro.biologie.tu-muenchen.de/help_nightly/reg.html#Syntax_of_POSIX_extended_regular_expressions_as_used_in_ARB
140     *
141     * returns
142     * - pointer to start of first match in 'str' and
143     *   length of match in 'matchlen' ('matchlen' may be NULL, then no len is reported)
144     *                                            or
145     * - NULL if nothing matched (in this case 'matchlen' is undefined)
146     *
147     * 'error' will be set if sth is wrong
148     *
149     * Note: Only use this function if you do exactly ONE match.
150     *       Use GBS_regmatch_compiled if you use the regexpr twice or more!
151     */
152    const char *firstMatch     = NULL;
153    GB_CASE     case_flag;
154    const char *unwrapped_expr = GBS_unwrap_regexpr(regExpr, &case_flag, error);
155
156    if (unwrapped_expr) {
157        GBS_regex *comreg = GBS_compile_regexpr(unwrapped_expr, case_flag, error);
158        if (comreg) {
159            firstMatch = GBS_regmatch_compiled(str, comreg, matchlen);
160            GBS_free_regexpr(comreg);
161        }
162    }
163
164    return firstMatch;
165}
166
167char *GBS_regreplace(const char *str, const char *regReplExpr, GB_ERROR *error) {
168    /* search and replace all matches in 'str' using POSIX extended regular expression
169     * 'regReplExpr' has to be in format '/regexpr/replace/[i]'
170     *
171     * returns
172     * - a heap copy of the modified string or
173     * - NULL if something went wrong (in this case 'error' contains the reason)
174     *
175     * 'replace' may contain several special substrings:
176     *
177     *     "\n" gets replaced by '\n'
178     *     "\t" -------''------- '\t'
179     *     "\\" -------''------- '\\'
180     *     "\0" -------''------- the complete match to regexpr
181     *     "\1" -------''------- the match to the first subexpression
182     *     "\2" -------''------- the match to the second subexpression
183     *     ...
184     *     "\9" -------''------- the match to the ninth subexpression
185     */
186
187    GB_CASE     case_flag;
188    const char *unwrapped_expr = GBS_unwrap_regexpr(regReplExpr, &case_flag, error);
189    char       *result         = NULL;
190
191    if (unwrapped_expr) {
192        const char *sep = unwrapped_expr;
193        while (sep) {
194            sep = strchr(sep, '/');
195            if (!sep) break;
196            if (sep>unwrapped_expr && sep[-1] != '\\') break;
197        }
198
199        if (!sep) {
200            // Warning: GB_command_interpreter() tests for this error message - don't change
201            *error = "Missing '/' between search and replace string";
202        }
203        else {
204            char      *regexpr  = GB_strpartdup(unwrapped_expr, sep-1);
205            char      *replexpr = GB_strpartdup(sep+1, NULL);
206            GBS_regex *comreg   = GBS_compile_regexpr(regexpr, case_flag, error);
207
208            if (comreg) {
209                GBS_strstruct *out    = GBS_stropen(1000);
210                int            eflags = 0;
211
212                while (str) {
213                    regmatch_t match[10];
214                    int        res = regexec(&comreg->compiled, str, 10, match, eflags);
215
216                    if (res == REG_NOMATCH) {
217                        GBS_strcat(out, str);
218                        str = 0;
219                    }
220                    else {      // found match
221                        size_t p;
222                        char   c;
223
224                        GBS_strncat(out, str, match[0].rm_so);
225
226                        for (p = 0; (c = replexpr[p]); ++p) {
227                            if (c == '\\') {
228                                c = replexpr[++p];
229                                if (!c) break;
230                                if (c >= '0' && c <= '9') {
231                                    regoff_t start = match[c-'0'].rm_so;
232                                    GBS_strncat(out, str+start, match[c-'0'].rm_eo-start);
233                                }
234                                else {
235                                    switch (c) {
236                                        case 'n': c = '\n'; break;
237                                        case 't': c = '\t'; break;
238                                        default: break;
239                                    }
240                                    GBS_chrcat(out, c);
241                                }
242                            }
243                            else {
244                                GBS_chrcat(out, c);
245                            }
246                        }
247
248                        str    = str+match[0].rm_eo; // continue behind match
249                        eflags = REG_NOTBOL; // for futher matches, do not regard 'str' as "beginning of line"
250                    }
251                }
252
253                GBS_free_regexpr(comreg);
254                result = GBS_strclose(out);
255            }
256            free(replexpr);
257            free(regexpr);
258        }
259    }
260
261    return result;
262}
263
Note: See TracBrowser for help on using the repository browser.