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