source: tags/ms_r17q1/CORE/arb_match.cxx

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