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