X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/71b5725f30bd6006f77f56ffdeb697b72241888d..bf2c43c76e2819be443ab1d830ab68d9569d66b1:/src/common/regex.cpp diff --git a/src/common/regex.cpp b/src/common/regex.cpp index e96257c8b6..a93ca08bf5 100644 --- a/src/common/regex.cpp +++ b/src/common/regex.cpp @@ -1,11 +1,10 @@ /////////////////////////////////////////////////////////////////////////////// // Name: src/common/regex.cpp // Purpose: regular expression matching -// Author: Karsten Ballüder and Vadim Zeitlin +// Author: Karsten Ballueder and Vadim Zeitlin // Modified by: // Created: 13.07.01 -// RCS-ID: $Id$ -// Copyright: (c) 2000 Karsten Ballüder +// Copyright: (c) 2000 Karsten Ballueder // 2001 Vadim Zeitlin // Licence: wxWindows licence /////////////////////////////////////////////////////////////////////////////// @@ -18,10 +17,6 @@ // headers // ---------------------------------------------------------------------------- -#ifdef __GNUG__ - #pragma implementation "regex.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -31,26 +26,130 @@ #if wxUSE_REGEX +#include "wx/regex.h" + #ifndef WX_PRECOMP #include "wx/object.h" - #include "wx/string.h" #include "wx/log.h" #include "wx/intl.h" + #include "wx/crt.h" #endif //WX_PRECOMP -// FreeBSD & Watcom require this, it probably doesn't hurt for others +// FreeBSD, Watcom and DMars require this, CW doesn't have nor need it. +// Others also don't seem to need it. If you have an error related to +// (not) including please report details to +// wx-dev@lists.wxwindows.org #if defined(__UNIX__) || defined(__WATCOMC__) || defined(__DIGITALMARS__) - #include +# include #endif #include -#include "wx/regex.h" +// WXREGEX_USING_BUILTIN defined when using the built-in regex lib +// WXREGEX_USING_RE_SEARCH defined when using re_search in the GNU regex lib +// WXREGEX_IF_NEED_LEN() wrap the len parameter only used with the built-in +// or GNU regex +// WXREGEX_CONVERT_TO_MB defined when the regex lib is using chars and +// wxChar is wide, so conversion must be done +// WXREGEX_CHAR(x) Convert wxChar to wxRegChar +// +#ifdef __REG_NOFRONT +# define WXREGEX_USING_BUILTIN +# define WXREGEX_IF_NEED_LEN(x) ,x +# if wxUSE_UNICODE +# define WXREGEX_CHAR(x) (x).wc_str() +# else +# define WXREGEX_CHAR(x) (x).mb_str() +# endif +#else +# ifdef HAVE_RE_SEARCH +# define WXREGEX_IF_NEED_LEN(x) ,x +# define WXREGEX_USING_RE_SEARCH +# else +# define WXREGEX_IF_NEED_LEN(x) +# endif +# if wxUSE_UNICODE +# define WXREGEX_CONVERT_TO_MB +# endif +# define WXREGEX_CHAR(x) (x).mb_str() +# define wx_regfree regfree +# define wx_regerror regerror +#endif // ---------------------------------------------------------------------------- // private classes // ---------------------------------------------------------------------------- +#ifndef WXREGEX_USING_RE_SEARCH + +// the array of offsets for the matches, the usual POSIX regmatch_t array. +class wxRegExMatches +{ +public: + typedef regmatch_t *match_type; + + wxRegExMatches(size_t n) { m_matches = new regmatch_t[n]; } + ~wxRegExMatches() { delete [] m_matches; } + + // we just use casts here because the fields of regmatch_t struct may be 64 + // bit but we're limited to size_t in our public API and are not going to + // change it because operating on strings longer than 4GB using it is + // absolutely impractical anyhow + size_t Start(size_t n) const + { + return wx_truncate_cast(size_t, m_matches[n].rm_so); + } + + size_t End(size_t n) const + { + return wx_truncate_cast(size_t, m_matches[n].rm_eo); + } + + regmatch_t *get() const { return m_matches; } + +private: + regmatch_t *m_matches; +}; + +#else // WXREGEX_USING_RE_SEARCH + +// the array of offsets for the matches, the struct used by the GNU lib +class wxRegExMatches +{ +public: + typedef re_registers *match_type; + + wxRegExMatches(size_t n) + { + m_matches.num_regs = n; + m_matches.start = new regoff_t[n]; + m_matches.end = new regoff_t[n]; + } + + ~wxRegExMatches() + { + delete [] m_matches.start; + delete [] m_matches.end; + } + + size_t Start(size_t n) const { return m_matches.start[n]; } + size_t End(size_t n) const { return m_matches.end[n]; } + + re_registers *get() { return &m_matches; } + +private: + re_registers m_matches; +}; + +#endif // WXREGEX_USING_RE_SEARCH + +// the character type used by the regular expression engine +#ifndef WXREGEX_CONVERT_TO_MB +typedef wxChar wxRegChar; +#else +typedef char wxRegChar; +#endif + // the real implementation of wxRegEx class wxRegExImpl { @@ -59,24 +158,26 @@ public: wxRegExImpl(); ~wxRegExImpl(); - // return TRUE if Compile() had been called successfully + // return true if Compile() had been called successfully bool IsValid() const { return m_isCompiled; } // RE operations bool Compile(const wxString& expr, int flags = 0); - bool Matches(const wxChar *str, int flags = 0) const; + bool Matches(const wxRegChar *str, int flags + WXREGEX_IF_NEED_LEN(size_t len)) const; bool GetMatch(size_t *start, size_t *len, size_t index = 0) const; + size_t GetMatchCount() const; int Replace(wxString *pattern, const wxString& replacement, size_t maxMatches = 0) const; private: // return the string containing the error message for the given err code - wxString GetErrorMsg(int errorcode) const; + wxString GetErrorMsg(int errorcode, bool badconv) const; // init the members void Init() { - m_isCompiled = FALSE; + m_isCompiled = false; m_Matches = NULL; m_nMatches = 0; } @@ -86,10 +187,10 @@ private: { if ( IsValid() ) { - regfree(&m_RegEx); + wx_regfree(&m_RegEx); } - delete [] m_Matches; + delete m_Matches; } // free the RE if any and reinit the members @@ -99,18 +200,18 @@ private: Init(); } - // compiled RE - regex_t m_RegEx; + regex_t m_RegEx; // the subexpressions data - regmatch_t *m_Matches; - size_t m_nMatches; + wxRegExMatches *m_Matches; + size_t m_nMatches; - // TRUE if m_RegEx is valid - bool m_isCompiled; + // true if m_RegEx is valid + bool m_isCompiled; }; + // ============================================================================ // implementation // ============================================================================ @@ -129,48 +230,65 @@ wxRegExImpl::~wxRegExImpl() Free(); } -wxString wxRegExImpl::GetErrorMsg(int errorcode) const +wxString wxRegExImpl::GetErrorMsg(int errorcode, bool badconv) const { - wxString msg; +#ifdef WXREGEX_CONVERT_TO_MB + // currently only needed when using system library in Unicode mode + if ( badconv ) + { + return _("conversion to 8-bit encoding failed"); + } +#else + // 'use' badconv to avoid a compiler warning + (void)badconv; +#endif + + wxString szError; // first get the string length needed - int len = regerror(errorcode, &m_RegEx, NULL, 0); + int len = wx_regerror(errorcode, &m_RegEx, NULL, 0); if ( len > 0 ) { - len++; - -#if wxUSE_UNICODE - wxCharBuffer buf(len); + char* szcmbError = new char[++len]; - (void)regerror(errorcode, &m_RegEx, (char *)buf.data(), len); + (void)wx_regerror(errorcode, &m_RegEx, szcmbError, len); - msg = wxString(buf.data(), wxConvLibc); -#else // !Unicode - (void)regerror(errorcode, &m_RegEx, msg.GetWriteBuf(len), len); - - msg.UngetWriteBuf(); -#endif // Unicode/!Unicode + szError = wxConvLibc.cMB2WX(szcmbError); + delete [] szcmbError; } else // regerror() returned 0 { - msg = _("unknown error"); + szError = _("unknown error"); } - return msg; + return szError; } bool wxRegExImpl::Compile(const wxString& expr, int flags) { Reinit(); - // translate our flags to regcomp() ones - wxASSERT_MSG( !(flags & - ~(wxRE_BASIC | wxRE_ICASE | wxRE_NOSUB | wxRE_NEWLINE)), - _T("unrecognized flags in wxRegEx::Compile") ); +#ifdef WX_NO_REGEX_ADVANCED +# define FLAVORS wxRE_BASIC +#else +# define FLAVORS (wxRE_ADVANCED | wxRE_BASIC) + wxASSERT_MSG( (flags & FLAVORS) != FLAVORS, + wxT("incompatible flags in wxRegEx::Compile") ); +#endif + wxASSERT_MSG( !(flags & ~(FLAVORS | wxRE_ICASE | wxRE_NOSUB | wxRE_NEWLINE)), + wxT("unrecognized flags in wxRegEx::Compile") ); + // translate our flags to regcomp() ones int flagsRE = 0; if ( !(flags & wxRE_BASIC) ) - flagsRE |= REG_EXTENDED; + { +#ifndef WX_NO_REGEX_ADVANCED + if (flags & wxRE_ADVANCED) + flagsRE |= REG_ADVANCED; + else +#endif + flagsRE |= REG_EXTENDED; + } if ( flags & wxRE_ICASE ) flagsRE |= REG_ICASE; if ( flags & wxRE_NOSUB ) @@ -179,13 +297,23 @@ bool wxRegExImpl::Compile(const wxString& expr, int flags) flagsRE |= REG_NEWLINE; // compile it - int errorcode = regcomp(&m_RegEx, expr.mb_str(), flagsRE); +#ifdef WXREGEX_USING_BUILTIN + bool conv = true; + // FIXME-UTF8: use wc_str() after removing ANSI build + int errorcode = wx_re_comp(&m_RegEx, expr.c_str(), expr.length(), flagsRE); +#else + // FIXME-UTF8: this is potentially broken, we shouldn't even try it + // and should always use builtin regex library (or PCRE?) + const wxWX2MBbuf conv = expr.mbc_str(); + int errorcode = conv ? regcomp(&m_RegEx, conv, flagsRE) : REG_BADPAT; +#endif + if ( errorcode ) { wxLogError(_("Invalid regular expression '%s': %s"), - expr.c_str(), GetErrorMsg(errorcode).c_str()); + expr.c_str(), GetErrorMsg(errorcode, !conv).c_str()); - m_isCompiled = FALSE; + m_isCompiled = false; } else // ok { @@ -206,38 +334,70 @@ bool wxRegExImpl::Compile(const wxString& expr, int flags) // and some more for bracketed subexperessions for ( const wxChar *cptr = expr.c_str(); *cptr; cptr++ ) { - if ( *cptr == _T('\\') ) + if ( *cptr == wxT('\\') ) { // in basic RE syntax groups are inside \(...\) - if ( *++cptr == _T('(') && (flags & wxRE_BASIC) ) + if ( *++cptr == wxT('(') && (flags & wxRE_BASIC) ) { m_nMatches++; } } - else if ( *cptr == _T('(') && !(flags & wxRE_BASIC) ) + else if ( *cptr == wxT('(') && !(flags & wxRE_BASIC) ) { // we know that the previous character is not an unquoted // backslash because it would have been eaten above, so we - // have a bar '(' and this indicates a group start for the - // extended syntax - m_nMatches++; + // have a bare '(' and this indicates a group start for the + // extended syntax. '(?' is used for extensions by perl- + // like REs (e.g. advanced), and is not valid for POSIX + // extended, so ignore them always. + if ( cptr[1] != wxT('?') ) + m_nMatches++; } } } - m_isCompiled = TRUE; + m_isCompiled = true; } return IsValid(); } -bool wxRegExImpl::Matches(const wxChar *str, int flags) const +#ifdef WXREGEX_USING_RE_SEARCH + +// On GNU, regexec is implemented as a wrapper around re_search. re_search +// requires a length parameter which the POSIX regexec does not have, +// therefore regexec must do a strlen on the search text each time it is +// called. This can drastically affect performance when matching is done in +// a loop along a string, such as during a search and replace. Therefore if +// re_search is detected by configure, it is used directly. +// +static int ReSearch(const regex_t *preg, + const char *text, + size_t len, + re_registers *matches, + int eflags) +{ + regex_t *pattern = const_cast(preg); + + pattern->not_bol = (eflags & REG_NOTBOL) != 0; + pattern->not_eol = (eflags & REG_NOTEOL) != 0; + pattern->regs_allocated = REGS_FIXED; + + int ret = re_search(pattern, text, len, 0, len, matches); + return ret >= 0 ? 0 : REG_NOMATCH; +} + +#endif // WXREGEX_USING_RE_SEARCH + +bool wxRegExImpl::Matches(const wxRegChar *str, + int flags + WXREGEX_IF_NEED_LEN(size_t len)) const { - wxCHECK_MSG( IsValid(), FALSE, _T("must successfully Compile() first") ); + wxCHECK_MSG( IsValid(), false, wxT("must successfully Compile() first") ); // translate our flags to regexec() ones wxASSERT_MSG( !(flags & ~(wxRE_NOTBOL | wxRE_NOTEOL)), - _T("unrecognized flags in wxRegEx::Matches") ); + wxT("unrecognized flags in wxRegEx::Matches") ); int flagsRE = 0; if ( flags & wxRE_NOTBOL ) @@ -249,60 +409,95 @@ bool wxRegExImpl::Matches(const wxChar *str, int flags) const wxRegExImpl *self = wxConstCast(this, wxRegExImpl); if ( !m_Matches && m_nMatches ) { - self->m_Matches = new regmatch_t[m_nMatches]; + self->m_Matches = new wxRegExMatches(m_nMatches); } + wxRegExMatches::match_type matches = m_Matches ? m_Matches->get() : NULL; + // do match it - int rc = regexec(&self->m_RegEx, wxConvertWX2MB(str), m_nMatches, m_Matches, flagsRE); +#if defined WXREGEX_USING_BUILTIN + int rc = wx_re_exec(&self->m_RegEx, str, len, NULL, m_nMatches, matches, flagsRE); +#elif defined WXREGEX_USING_RE_SEARCH + int rc = str ? ReSearch(&self->m_RegEx, str, len, matches, flagsRE) : REG_BADPAT; +#else + int rc = str ? regexec(&self->m_RegEx, str, m_nMatches, matches, flagsRE) : REG_BADPAT; +#endif switch ( rc ) { case 0: // matched successfully - return TRUE; + return true; default: - // an error occured - wxLogError(_("Failed to match '%s' in regular expression: %s"), - str, GetErrorMsg(rc).c_str()); + // an error occurred + wxLogError(_("Failed to find match for regular expression: %s"), + GetErrorMsg(rc, !str).c_str()); // fall through case REG_NOMATCH: // no match - return FALSE; + return false; } } bool wxRegExImpl::GetMatch(size_t *start, size_t *len, size_t index) const { - wxCHECK_MSG( IsValid(), FALSE, _T("must successfully Compile() first") ); - wxCHECK_MSG( m_Matches, FALSE, _T("can't use with wxRE_NOSUB") ); - wxCHECK_MSG( index < m_nMatches, FALSE, _T("invalid match index") ); - - const regmatch_t& match = m_Matches[index]; + wxCHECK_MSG( IsValid(), false, wxT("must successfully Compile() first") ); + wxCHECK_MSG( m_nMatches, false, wxT("can't use with wxRE_NOSUB") ); + wxCHECK_MSG( m_Matches, false, wxT("must call Matches() first") ); + wxCHECK_MSG( index < m_nMatches, false, wxT("invalid match index") ); if ( start ) - *start = match.rm_so; + *start = m_Matches->Start(index); if ( len ) - *len = match.rm_eo - match.rm_so; + *len = m_Matches->End(index) - m_Matches->Start(index); + + return true; +} + +size_t wxRegExImpl::GetMatchCount() const +{ + wxCHECK_MSG( IsValid(), 0, wxT("must successfully Compile() first") ); + wxCHECK_MSG( m_nMatches, 0, wxT("can't use with wxRE_NOSUB") ); - return TRUE; + return m_nMatches; } int wxRegExImpl::Replace(wxString *text, const wxString& replacement, size_t maxMatches) const { - wxCHECK_MSG( text, -1, _T("NULL text in wxRegEx::Replace") ); - wxCHECK_MSG( IsValid(), -1, _T("must successfully Compile() first") ); + wxCHECK_MSG( text, wxNOT_FOUND, wxT("NULL text in wxRegEx::Replace") ); + wxCHECK_MSG( IsValid(), wxNOT_FOUND, wxT("must successfully Compile() first") ); + + // the input string +#ifndef WXREGEX_CONVERT_TO_MB + const wxChar *textstr = text->c_str(); + size_t textlen = text->length(); +#else + const wxWX2MBbuf textstr = WXREGEX_CHAR(*text); + if (!textstr) + { + wxLogError(_("Failed to find match for regular expression: %s"), + GetErrorMsg(0, true).c_str()); + return 0; + } + size_t textlen = strlen(textstr); + text->clear(); +#endif // the replacement text wxString textNew; + // the result, allow 25% extra + wxString result; + result.reserve(5 * textlen / 4); + // attempt at optimization: don't iterate over the string if it doesn't // contain back references at all bool mayHaveBackrefs = - replacement.find_first_of(_T("\\&")) != wxString::npos; + replacement.find_first_of(wxT("\\&")) != wxString::npos; if ( !mayHaveBackrefs ) { @@ -310,10 +505,6 @@ int wxRegExImpl::Replace(wxString *text, } // the position where we start looking for the match - // - // NB: initial version had a nasty bug because it used a wxChar* instead of - // an index but the problem is that replace() in the loop invalidates - // all pointers into the string so we have to use indices instead size_t matchStart = 0; // number of replacement made: we won't make more than maxMatches of them @@ -323,13 +514,20 @@ int wxRegExImpl::Replace(wxString *text, // note that "^" shouldn't match after the first call to Matches() so we // use wxRE_NOTBOL to prevent it from happening while ( (!maxMatches || countRepl < maxMatches) && - Matches(text->c_str() + matchStart, countRepl ? wxRE_NOTBOL : 0) ) + Matches( +#ifndef WXREGEX_CONVERT_TO_MB + textstr + matchStart, +#else + textstr.data() + matchStart, +#endif + countRepl ? wxRE_NOTBOL : 0 + WXREGEX_IF_NEED_LEN(textlen - matchStart)) ) { // the string possibly contains back references: we need to calculate // the replacement text anew after each match if ( mayHaveBackrefs ) { - mayHaveBackrefs = FALSE; + mayHaveBackrefs = false; textNew.clear(); textNew.reserve(replacement.length()); @@ -337,7 +535,7 @@ int wxRegExImpl::Replace(wxString *text, { size_t index = (size_t)-1; - if ( *p == _T('\\') ) + if ( *p == wxT('\\') ) { if ( wxIsdigit(*++p) ) { @@ -348,7 +546,7 @@ int wxRegExImpl::Replace(wxString *text, } //else: backslash used as escape character } - else if ( *p == _T('&') ) + else if ( *p == wxT('&') ) { // treat this as "\0" for compatbility with ed and such index = 0; @@ -361,16 +559,22 @@ int wxRegExImpl::Replace(wxString *text, size_t start, len; if ( !GetMatch(&start, &len, index) ) { - wxFAIL_MSG( _T("invalid back reference") ); + wxFAIL_MSG( wxT("invalid back reference") ); // just eat it... } else { - textNew += wxString(text->c_str() + matchStart + start, - len); + textNew += wxString( +#ifndef WXREGEX_CONVERT_TO_MB + textstr +#else + textstr.data() +#endif + + matchStart + start, + *wxConvCurrent, len); - mayHaveBackrefs = TRUE; + mayHaveBackrefs = true; } } else // ordinary character @@ -384,19 +588,36 @@ int wxRegExImpl::Replace(wxString *text, if ( !GetMatch(&start, &len) ) { // we did have match as Matches() returned true above! - wxFAIL_MSG( _T("internal logic error in wxRegEx::Replace") ); + wxFAIL_MSG( wxT("internal logic error in wxRegEx::Replace") ); - return -1; + return wxNOT_FOUND; } + // an insurance against implementations that don't grow exponentially + // to ensure building the result takes linear time + if (result.capacity() < result.length() + start + textNew.length()) + result.reserve(2 * result.length()); + +#ifndef WXREGEX_CONVERT_TO_MB + result.append(*text, matchStart, start); +#else + result.append(wxString(textstr.data() + matchStart, *wxConvCurrent, start)); +#endif matchStart += start; - text->replace(matchStart, len, textNew); + result.append(textNew); countRepl++; - matchStart += textNew.length(); + matchStart += len; } +#ifndef WXREGEX_CONVERT_TO_MB + result.append(*text, matchStart, wxString::npos); +#else + result.append(wxString(textstr.data() + matchStart, *wxConvCurrent)); +#endif + *text = result; + return countRepl; } @@ -409,7 +630,6 @@ void wxRegEx::Init() m_impl = NULL; } - wxRegEx::~wxRegEx() { delete m_impl; @@ -425,25 +645,25 @@ bool wxRegEx::Compile(const wxString& expr, int flags) if ( !m_impl->Compile(expr, flags) ) { // error message already given in wxRegExImpl::Compile - delete m_impl; - m_impl = NULL; + wxDELETE(m_impl); - return FALSE; + return false; } - return TRUE; + return true; } -bool wxRegEx::Matches(const wxChar *str, int flags) const +bool wxRegEx::Matches(const wxString& str, int flags) const { - wxCHECK_MSG( IsValid(), FALSE, _T("must successfully Compile() first") ); + wxCHECK_MSG( IsValid(), false, wxT("must successfully Compile() first") ); - return m_impl->Matches(str, flags); + return m_impl->Matches(WXREGEX_CHAR(str), flags + WXREGEX_IF_NEED_LEN(str.length())); } bool wxRegEx::GetMatch(size_t *start, size_t *len, size_t index) const { - wxCHECK_MSG( IsValid(), FALSE, _T("must successfully Compile() first") ); + wxCHECK_MSG( IsValid(), false, wxT("must successfully Compile() first") ); return m_impl->GetMatch(start, len, index); } @@ -457,11 +677,18 @@ wxString wxRegEx::GetMatch(const wxString& text, size_t index) const return text.Mid(start, len); } +size_t wxRegEx::GetMatchCount() const +{ + wxCHECK_MSG( IsValid(), 0, wxT("must successfully Compile() first") ); + + return m_impl->GetMatchCount(); +} + int wxRegEx::Replace(wxString *pattern, const wxString& replacement, size_t maxMatches) const { - wxCHECK_MSG( IsValid(), -1, _T("must successfully Compile() first") ); + wxCHECK_MSG( IsValid(), wxNOT_FOUND, wxT("must successfully Compile() first") ); return m_impl->Replace(pattern, replacement, maxMatches); }