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