]> git.saurik.com Git - wxWidgets.git/blame - src/common/numformatter.cpp
fixing overrelease and out-of-bounds write, fixes #13725
[wxWidgets.git] / src / common / numformatter.cpp
CommitLineData
6686fbad 1/////////////////////////////////////////////////////////////////////////////
80fdcdb9 2// Name: src/common/numformatter.cpp
6686fbad
VZ
3// Purpose: wxNumberFormatter
4// Author: Fulvio Senore, Vadim Zeitlin
5// Created: 2010-11-06
6// Copyright: (c) 2010 wxWidgets team
7// Licence: wxWindows licence
8/////////////////////////////////////////////////////////////////////////////
9
10// ----------------------------------------------------------------------------
11// headers
12// ----------------------------------------------------------------------------
13
14// For compilers that support precompilation, includes "wx.h".
15#include "wx/wxprec.h"
16
17#ifdef __BORLANDC__
18 #pragma hdrstop
19#endif
20
21#include "wx/numformatter.h"
22#include "wx/intl.h"
23
7451fe7b 24#include <locale.h> // for setlocale and LC_ALL
fa5d9d20 25
d8d9844b
VZ
26// ----------------------------------------------------------------------------
27// local helpers
28// ----------------------------------------------------------------------------
29
30namespace
31{
32
33// Contains information about the locale which was used to initialize our
34// cached values of the decimal and thousands separators. Notice that it isn't
35// enough to store just wxLocale because the user code may call setlocale()
36// directly and storing just C locale string is not enough because we can use
37// the OS API directly instead of the CRT ones on some platforms. So just store
38// both.
39class LocaleId
40{
41public:
42 LocaleId()
43 {
88def163 44#if wxUSE_INTL
d8d9844b 45 m_wxloc = NULL;
88def163 46#endif // wxUSE_INTL
d8d9844b
VZ
47 m_cloc = NULL;
48 }
49
50 ~LocaleId()
51 {
52 Free();
53 }
54
88def163 55#if wxUSE_INTL
d8d9844b
VZ
56 // Return true if this is the first time this function is called for this
57 // object or if the program locale has changed since the last time it was
58 // called. Otherwise just return false indicating that updating locale-
59 // dependent information is not necessary.
60 bool NotInitializedOrHasChanged()
61 {
62 wxLocale * const wxloc = wxGetLocale();
63 const char * const cloc = setlocale(LC_ALL, NULL);
64 if ( m_wxloc || m_cloc )
65 {
66 if ( m_wxloc == wxloc && strcmp(m_cloc, cloc) == 0 )
67 return false;
68
69 Free();
70 }
71 //else: Not initialized yet.
72
73 m_wxloc = wxloc;
74 m_cloc = wxCRT_StrdupA(cloc);
75
76 return true;
77 }
88def163 78#endif // wxUSE_INTL
d8d9844b
VZ
79
80private:
81 void Free()
82 {
88def163 83#if wxUSE_INTL
d8d9844b 84 free(m_cloc);
88def163 85#endif // wxUSE_INTL
d8d9844b
VZ
86 }
87
88def163 88#if wxUSE_INTL
d8d9844b
VZ
89 // Non-owned pointer to wxLocale which was used.
90 wxLocale *m_wxloc;
88def163 91#endif // wxUSE_INTL
d8d9844b
VZ
92
93 // Owned pointer to the C locale string.
94 char *m_cloc;
95
96 wxDECLARE_NO_COPY_CLASS(LocaleId);
97};
98
99} // anonymous namespace
100
6686fbad
VZ
101// ============================================================================
102// wxNumberFormatter implementation
103// ============================================================================
104
105// ----------------------------------------------------------------------------
106// Locale information accessors
107// ----------------------------------------------------------------------------
108
109wxChar wxNumberFormatter::GetDecimalSeparator()
110{
88def163 111#if wxUSE_INTL
6686fbad
VZ
112 // Notice that while using static variable here is not MT-safe, the worst
113 // that can happen is that we redo the initialization if we're called
114 // concurrently from more than one thread so it's not a real problem.
115 static wxChar s_decimalSeparator = 0;
116
0d30c79b
VZ
117 // Remember the locale which was current when we initialized, we must redo
118 // the initialization if the locale changed.
d8d9844b 119 static LocaleId s_localeUsedForInit;
0d30c79b 120
d8d9844b 121 if ( s_localeUsedForInit.NotInitializedOrHasChanged() )
6686fbad
VZ
122 {
123 const wxString
124 s = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER);
125 if ( s.empty() )
126 {
127 // We really must have something for decimal separator, so fall
128 // back to the C locale default.
129 s_decimalSeparator = '.';
130 }
131 else
132 {
133 // To the best of my knowledge there are no locales like this.
134 wxASSERT_MSG( s.length() == 1,
135 "Multi-character decimal separator?" );
136
137 s_decimalSeparator = s[0];
138 }
139 }
140
141 return s_decimalSeparator;
88def163
DS
142#else // !wxUSE_INTL
143 return wxT('.');
144#endif // wxUSE_INTL/!wxUSE_INTL
6686fbad
VZ
145}
146
147bool wxNumberFormatter::GetThousandsSeparatorIfUsed(wxChar *sep)
148{
88def163 149#if wxUSE_INTL
6686fbad 150 static wxChar s_thousandsSeparator = 0;
d8d9844b 151 static LocaleId s_localeUsedForInit;
6686fbad 152
d8d9844b 153 if ( s_localeUsedForInit.NotInitializedOrHasChanged() )
6686fbad
VZ
154 {
155 const wxString
156 s = wxLocale::GetInfo(wxLOCALE_THOUSANDS_SEP, wxLOCALE_CAT_NUMBER);
157 if ( !s.empty() )
158 {
159 wxASSERT_MSG( s.length() == 1,
160 "Multi-character thousands separator?" );
161
162 s_thousandsSeparator = s[0];
163 }
164 //else: Unlike above it's perfectly fine for the thousands separator to
165 // be empty if grouping is not used, so just leave it as 0.
6686fbad
VZ
166 }
167
168 if ( !s_thousandsSeparator )
169 return false;
170
171 if ( sep )
172 *sep = s_thousandsSeparator;
173
174 return true;
88def163
DS
175#else // !wxUSE_INTL
176 wxUnusedVar(sep);
177 return false;
178#endif // wxUSE_INTL/!wxUSE_INTL
6686fbad
VZ
179}
180
181// ----------------------------------------------------------------------------
182// Conversion to string and helpers
183// ----------------------------------------------------------------------------
184
f2a5052b 185wxString wxNumberFormatter::PostProcessIntString(wxString s, int style)
6686fbad 186{
6686fbad
VZ
187 if ( style & Style_WithThousandsSep )
188 AddThousandsSeparators(s);
189
190 wxASSERT_MSG( !(style & Style_NoTrailingZeroes),
191 "Style_NoTrailingZeroes can't be used with integer values" );
192
193 return s;
194}
195
f2a5052b
VZ
196wxString wxNumberFormatter::ToString(long val, int style)
197{
198 return PostProcessIntString(wxString::Format("%ld", val), style);
199}
200
201#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG
202
203wxString wxNumberFormatter::ToString(wxLongLong_t val, int style)
204{
205 return PostProcessIntString(wxString::Format("%" wxLongLongFmtSpec "d", val),
206 style);
207}
208
209#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG
210
6686fbad
VZ
211wxString wxNumberFormatter::ToString(double val, int precision, int style)
212{
213 const wxString fmt = wxString::Format("%%.%df", precision);
214 wxString s = wxString::Format(fmt, val);
215
216 if ( style & Style_WithThousandsSep )
217 AddThousandsSeparators(s);
218
219 if ( style & Style_NoTrailingZeroes )
220 RemoveTrailingZeroes(s);
221
222 return s;
223}
224
225void wxNumberFormatter::AddThousandsSeparators(wxString& s)
226{
227 wxChar thousandsSep;
228 if ( !GetThousandsSeparatorIfUsed(&thousandsSep) )
229 return;
230
231 size_t pos = s.find(GetDecimalSeparator());
232 if ( pos == wxString::npos )
233 {
234 // Start grouping at the end of an integer number.
235 pos = s.length();
236 }
237
a05cbc20
VZ
238 // End grouping at the beginning of the digits -- there could be at a sign
239 // before their start.
240 const size_t start = s.find_first_of("0123456789");
241
6686fbad
VZ
242 // We currently group digits by 3 independently of the locale. This is not
243 // the right thing to do and we should use lconv::grouping (under POSIX)
244 // and GetLocaleInfo(LOCALE_SGROUPING) (under MSW) to get information about
245 // the correct grouping to use. This is something that needs to be done at
246 // wxLocale level first and then used here in the future (TODO).
247 const size_t GROUP_LEN = 3;
248
a05cbc20 249 while ( pos > start + GROUP_LEN )
6686fbad
VZ
250 {
251 pos -= GROUP_LEN;
252 s.insert(pos, thousandsSep);
253 }
254}
255
256void wxNumberFormatter::RemoveTrailingZeroes(wxString& s)
257{
258 const size_t posDecSep = s.find(GetDecimalSeparator());
a7d696f1
VZ
259 wxCHECK_RET( posDecSep != wxString::npos,
260 wxString::Format("No decimal separator in \"%s\"", s) );
6686fbad
VZ
261 wxCHECK_RET( posDecSep, "Can't start with decimal separator" );
262
263 // Find the last character to keep.
264 size_t posLastNonZero = s.find_last_not_of("0");
265
266 // If it's the decimal separator itself, don't keep it neither.
267 if ( posLastNonZero == posDecSep )
268 posLastNonZero--;
269
270 s.erase(posLastNonZero + 1);
271}
272
273// ----------------------------------------------------------------------------
274// Conversion from strings
275// ----------------------------------------------------------------------------
276
277void wxNumberFormatter::RemoveThousandsSeparators(wxString& s)
278{
279 wxChar thousandsSep;
280 if ( !GetThousandsSeparatorIfUsed(&thousandsSep) )
281 return;
282
283 s.Replace(wxString(thousandsSep), wxString());
284}
285
286bool wxNumberFormatter::FromString(wxString s, long *val)
287{
288 RemoveThousandsSeparators(s);
289 return s.ToLong(val);
290}
291
f2a5052b
VZ
292#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG
293
294bool wxNumberFormatter::FromString(wxString s, wxLongLong_t *val)
295{
296 RemoveThousandsSeparators(s);
297 return s.ToLongLong(val);
298}
299
300#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG
301
6686fbad
VZ
302bool wxNumberFormatter::FromString(wxString s, double *val)
303{
304 RemoveThousandsSeparators(s);
305 return s.ToDouble(val);
306}