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