Cleaned up regex.cpp
[wxWidgets.git] / src / common / regex.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/regex.cpp
3 // Purpose: regular expression matching
4 // Author: Karsten Ballüder and Vadim Zeitlin
5 // Modified by:
6 // Created: 13.07.01
7 // RCS-ID: $Id$
8 // Copyright: (c) 2000 Karsten Ballüder <ballueder@gmx.net>
9 // 2001 Vadim Zeitlin <vadim@wxwindows.org>
10 // Licence: wxWindows licence
11 ///////////////////////////////////////////////////////////////////////////////
12
13 // ============================================================================
14 // declarations
15 // ============================================================================
16
17 // ----------------------------------------------------------------------------
18 // headers
19 // ----------------------------------------------------------------------------
20
21 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
22 #pragma implementation "regex.h"
23 #endif
24
25 // For compilers that support precompilation, includes "wx.h".
26 #include "wx/wxprec.h"
27
28 #ifdef __BORLANDC__
29 #pragma hdrstop
30 #endif
31
32 #if wxUSE_REGEX
33
34 #ifndef WX_PRECOMP
35 #include "wx/object.h"
36 #include "wx/string.h"
37 #include "wx/log.h"
38 #include "wx/intl.h"
39 #endif //WX_PRECOMP
40
41 // FreeBSD, Watcom and DMars require this, CW doesn't have nor need it.
42 // Others also don't seem to need it. If you have an error related to
43 // (not) including <sys/types.h> please report details to
44 // wx-dev@lists.wxwindows.org
45 #if defined(__UNIX__) || defined(__WATCOMC__) || defined(__DIGITALMARS__)
46 # include <sys/types.h>
47 #endif
48
49 #ifndef __WXWINCE__
50 #include <regex.h>
51 #endif
52
53 #include "wx/regex.h"
54
55 #if wxUSE_UNICODE
56 # if !defined(wxUSE_BUILTIN_REGEX)
57 # error "Unicode not supported with system regex, please reconfigure with --with-regex=builtin"
58 # endif
59 #endif
60
61 // ----------------------------------------------------------------------------
62 // private classes
63 // ----------------------------------------------------------------------------
64
65 // the real implementation of wxRegEx
66 class wxRegExImpl
67 {
68 public:
69 // ctor and dtor
70 wxRegExImpl();
71 ~wxRegExImpl();
72
73 // return TRUE if Compile() had been called successfully
74 bool IsValid() const { return m_isCompiled; }
75
76 // RE operations
77 bool Compile(const wxString& expr, int flags = 0);
78 bool Matches(const wxChar *str, int flags = 0) const;
79 bool GetMatch(size_t *start, size_t *len, size_t index = 0) const;
80 int Replace(wxString *pattern, const wxString& replacement,
81 size_t maxMatches = 0) const;
82
83 private:
84 // return the string containing the error message for the given err code
85 wxString GetErrorMsg(int errorcode) const;
86
87 // init the members
88 void Init()
89 {
90 m_isCompiled = FALSE;
91 m_Matches = NULL;
92 m_nMatches = 0;
93 }
94
95 // free the RE if compiled
96 void Free()
97 {
98 if ( IsValid() )
99 {
100 regfree(&m_RegEx);
101 }
102
103 delete [] m_Matches;
104 }
105
106 // free the RE if any and reinit the members
107 void Reinit()
108 {
109 Free();
110 Init();
111 }
112
113
114 // compiled RE
115 regex_t m_RegEx;
116
117 // the subexpressions data
118 regmatch_t *m_Matches;
119 size_t m_nMatches;
120
121 // TRUE if m_RegEx is valid
122 bool m_isCompiled;
123 };
124
125 // ============================================================================
126 // implementation
127 // ============================================================================
128
129 // ----------------------------------------------------------------------------
130 // wxRegExImpl
131 // ----------------------------------------------------------------------------
132
133 wxRegExImpl::wxRegExImpl()
134 {
135 Init();
136 }
137
138 wxRegExImpl::~wxRegExImpl()
139 {
140 Free();
141 }
142
143 wxString wxRegExImpl::GetErrorMsg(int errorcode) const
144 {
145 wxString szError;
146
147 // first get the string length needed
148 int len = regerror(errorcode, &m_RegEx, NULL, 0);
149 if ( len > 0 )
150 {
151 char* szcmbError = new char[++len];
152
153 (void)regerror(errorcode, &m_RegEx, szcmbError, len);
154
155 szError = wxConvertMB2WX(szcmbError);
156 delete [] szcmbError;
157 }
158 else // regerror() returned 0
159 {
160 szError = _("unknown error");
161 }
162
163 return szError;
164 }
165
166 bool wxRegExImpl::Compile(const wxString& expr, int flags)
167 {
168 Reinit();
169
170 // translate our flags to regcomp() ones
171 wxASSERT_MSG( !(flags &
172 ~(wxRE_BASIC | wxRE_ICASE | wxRE_NOSUB | wxRE_NEWLINE)),
173 _T("unrecognized flags in wxRegEx::Compile") );
174
175 int flagsRE = 0;
176 if ( !(flags & wxRE_BASIC) )
177 flagsRE |= REG_EXTENDED;
178 if ( flags & wxRE_ICASE )
179 flagsRE |= REG_ICASE;
180 if ( flags & wxRE_NOSUB )
181 flagsRE |= REG_NOSUB;
182 if ( flags & wxRE_NEWLINE )
183 flagsRE |= REG_NEWLINE;
184
185
186
187 int errorcode = regcomp(&m_RegEx, expr, flagsRE);
188
189 if ( errorcode )
190 {
191 wxLogError(_("Invalid regular expression '%s': %s"),
192 expr.c_str(), GetErrorMsg(errorcode).c_str());
193
194 m_isCompiled = FALSE;
195 }
196 else // ok
197 {
198 // don't allocate the matches array now, but do it later if necessary
199 if ( flags & wxRE_NOSUB )
200 {
201 // we don't need it at all
202 m_nMatches = 0;
203 }
204 else
205 {
206 // we will alloc the array later (only if really needed) but count
207 // the number of sub-expressions in the regex right now
208
209 // there is always one for the whole expression
210 m_nMatches = 1;
211
212 // and some more for bracketed subexperessions
213 for ( const wxChar *cptr = expr.c_str(); *cptr; cptr++ )
214 {
215 if ( *cptr == _T('\\') )
216 {
217 // in basic RE syntax groups are inside \(...\)
218 if ( *++cptr == _T('(') && (flags & wxRE_BASIC) )
219 {
220 m_nMatches++;
221 }
222 }
223 else if ( *cptr == _T('(') && !(flags & wxRE_BASIC) )
224 {
225 // we know that the previous character is not an unquoted
226 // backslash because it would have been eaten above, so we
227 // have a bar '(' and this indicates a group start for the
228 // extended syntax
229 m_nMatches++;
230 }
231 }
232 }
233
234 m_isCompiled = TRUE;
235 }
236
237 return IsValid();
238 }
239
240 bool wxRegExImpl::Matches(const wxChar *str, int flags) const
241 {
242 wxCHECK_MSG( IsValid(), FALSE, _T("must successfully Compile() first") );
243
244 // translate our flags to regexec() ones
245 wxASSERT_MSG( !(flags & ~(wxRE_NOTBOL | wxRE_NOTEOL)),
246 _T("unrecognized flags in wxRegEx::Matches") );
247
248 int flagsRE = 0;
249 if ( flags & wxRE_NOTBOL )
250 flagsRE |= REG_NOTBOL;
251 if ( flags & wxRE_NOTEOL )
252 flagsRE |= REG_NOTEOL;
253
254 // allocate matches array if needed
255 wxRegExImpl *self = wxConstCast(this, wxRegExImpl);
256 if ( !m_Matches && m_nMatches )
257 {
258 self->m_Matches = new regmatch_t[m_nMatches];
259 }
260
261 // do match it
262 int rc = regexec(&self->m_RegEx, str, m_nMatches, m_Matches, flagsRE);
263
264 switch ( rc )
265 {
266 case 0:
267 // matched successfully
268 return TRUE;
269
270 default:
271 // an error occured
272 wxLogError(_("Failed to match '%s' in regular expression: %s"),
273 str, GetErrorMsg(rc).c_str());
274 // fall through
275
276 case REG_NOMATCH:
277 // no match
278 return FALSE;
279 }
280 }
281
282 bool wxRegExImpl::GetMatch(size_t *start, size_t *len, size_t index) const
283 {
284 wxCHECK_MSG( IsValid(), FALSE, _T("must successfully Compile() first") );
285 wxCHECK_MSG( m_Matches, FALSE, _T("can't use with wxRE_NOSUB") );
286 wxCHECK_MSG( index < m_nMatches, FALSE, _T("invalid match index") );
287
288 const regmatch_t& match = m_Matches[index];
289
290 if ( start )
291 *start = match.rm_so;
292 if ( len )
293 *len = match.rm_eo - match.rm_so;
294
295 return TRUE;
296 }
297
298 int wxRegExImpl::Replace(wxString *text,
299 const wxString& replacement,
300 size_t maxMatches) const
301 {
302 wxCHECK_MSG( text, -1, _T("NULL text in wxRegEx::Replace") );
303 wxCHECK_MSG( IsValid(), -1, _T("must successfully Compile() first") );
304
305 // the replacement text
306 wxString textNew;
307
308 // attempt at optimization: don't iterate over the string if it doesn't
309 // contain back references at all
310 bool mayHaveBackrefs =
311 replacement.find_first_of(_T("\\&")) != wxString::npos;
312
313 if ( !mayHaveBackrefs )
314 {
315 textNew = replacement;
316 }
317
318 // the position where we start looking for the match
319 //
320 // NB: initial version had a nasty bug because it used a wxChar* instead of
321 // an index but the problem is that replace() in the loop invalidates
322 // all pointers into the string so we have to use indices instead
323 size_t matchStart = 0;
324
325 // number of replacement made: we won't make more than maxMatches of them
326 // (unless maxMatches is 0 which doesn't limit the number of replacements)
327 size_t countRepl = 0;
328
329 // note that "^" shouldn't match after the first call to Matches() so we
330 // use wxRE_NOTBOL to prevent it from happening
331 while ( (!maxMatches || countRepl < maxMatches) &&
332 Matches(text->c_str() + matchStart, countRepl ? wxRE_NOTBOL : 0) )
333 {
334 // the string possibly contains back references: we need to calculate
335 // the replacement text anew after each match
336 if ( mayHaveBackrefs )
337 {
338 mayHaveBackrefs = FALSE;
339 textNew.clear();
340 textNew.reserve(replacement.length());
341
342 for ( const wxChar *p = replacement.c_str(); *p; p++ )
343 {
344 size_t index = (size_t)-1;
345
346 if ( *p == _T('\\') )
347 {
348 if ( wxIsdigit(*++p) )
349 {
350 // back reference
351 wxChar *end;
352 index = (size_t)wxStrtoul(p, &end, 10);
353 p = end - 1; // -1 to compensate for p++ in the loop
354 }
355 //else: backslash used as escape character
356 }
357 else if ( *p == _T('&') )
358 {
359 // treat this as "\0" for compatbility with ed and such
360 index = 0;
361 }
362
363 // do we have a back reference?
364 if ( index != (size_t)-1 )
365 {
366 // yes, get its text
367 size_t start, len;
368 if ( !GetMatch(&start, &len, index) )
369 {
370 wxFAIL_MSG( _T("invalid back reference") );
371
372 // just eat it...
373 }
374 else
375 {
376 textNew += wxString(text->c_str() + matchStart + start,
377 len);
378
379 mayHaveBackrefs = TRUE;
380 }
381 }
382 else // ordinary character
383 {
384 textNew += *p;
385 }
386 }
387 }
388
389 size_t start, len;
390 if ( !GetMatch(&start, &len) )
391 {
392 // we did have match as Matches() returned true above!
393 wxFAIL_MSG( _T("internal logic error in wxRegEx::Replace") );
394
395 return -1;
396 }
397
398 matchStart += start;
399 text->replace(matchStart, len, textNew);
400
401 countRepl++;
402
403 matchStart += textNew.length();
404 }
405
406 return countRepl;
407 }
408
409 // ----------------------------------------------------------------------------
410 // wxRegEx: all methods are mostly forwarded to wxRegExImpl
411 // ----------------------------------------------------------------------------
412
413 void wxRegEx::Init()
414 {
415 m_impl = NULL;
416 }
417
418
419 wxRegEx::~wxRegEx()
420 {
421 delete m_impl;
422 }
423
424 bool wxRegEx::Compile(const wxString& expr, int flags)
425 {
426 if ( !m_impl )
427 {
428 m_impl = new wxRegExImpl;
429 }
430
431 if ( !m_impl->Compile(expr, flags) )
432 {
433 // error message already given in wxRegExImpl::Compile
434 delete m_impl;
435 m_impl = NULL;
436
437 return FALSE;
438 }
439
440 return TRUE;
441 }
442
443 bool wxRegEx::Matches(const wxChar *str, int flags) const
444 {
445 wxCHECK_MSG( IsValid(), FALSE, _T("must successfully Compile() first") );
446
447 return m_impl->Matches(str, flags);
448 }
449
450 bool wxRegEx::GetMatch(size_t *start, size_t *len, size_t index) const
451 {
452 wxCHECK_MSG( IsValid(), FALSE, _T("must successfully Compile() first") );
453
454 return m_impl->GetMatch(start, len, index);
455 }
456
457 wxString wxRegEx::GetMatch(const wxString& text, size_t index) const
458 {
459 size_t start, len;
460 if ( !GetMatch(&start, &len, index) )
461 return wxEmptyString;
462
463 return text.Mid(start, len);
464 }
465
466 int wxRegEx::Replace(wxString *pattern,
467 const wxString& replacement,
468 size_t maxMatches) const
469 {
470 wxCHECK_MSG( IsValid(), -1, _T("must successfully Compile() first") );
471
472 return m_impl->Replace(pattern, replacement, maxMatches);
473 }
474
475 #endif // wxUSE_REGEX