Add wxNumberFormatter class helping to deal with thousands separators.

wxNumberFormatter formats and parses numbers with thousands separators.

Add the class itself as well as documentation and the unit test for it.

See #12166.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@66710 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2011-01-19 10:47:40 +00:00
parent 6e91eb1f76
commit 6686fbad16
33 changed files with 822 additions and 4 deletions

190
src/common/numformatter.cpp Normal file
View File

@@ -0,0 +1,190 @@
/////////////////////////////////////////////////////////////////////////////
// Name: numformatter.cpp
// Purpose: wxNumberFormatter
// Author: Fulvio Senore, Vadim Zeitlin
// Created: 2010-11-06
// Copyright: (c) 2010 wxWidgets team
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#include "wx/numformatter.h"
#include "wx/intl.h"
// ============================================================================
// wxNumberFormatter implementation
// ============================================================================
// ----------------------------------------------------------------------------
// Locale information accessors
// ----------------------------------------------------------------------------
wxChar wxNumberFormatter::GetDecimalSeparator()
{
// Notice that while using static variable here is not MT-safe, the worst
// that can happen is that we redo the initialization if we're called
// concurrently from more than one thread so it's not a real problem.
static wxChar s_decimalSeparator = 0;
if ( !s_decimalSeparator )
{
const wxString
s = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER);
if ( s.empty() )
{
// We really must have something for decimal separator, so fall
// back to the C locale default.
s_decimalSeparator = '.';
}
else
{
// To the best of my knowledge there are no locales like this.
wxASSERT_MSG( s.length() == 1,
"Multi-character decimal separator?" );
s_decimalSeparator = s[0];
}
}
return s_decimalSeparator;
}
bool wxNumberFormatter::GetThousandsSeparatorIfUsed(wxChar *sep)
{
static wxChar s_thousandsSeparator = 0;
static bool s_initialized = false;
if ( !s_initialized )
{
const wxString
s = wxLocale::GetInfo(wxLOCALE_THOUSANDS_SEP, wxLOCALE_CAT_NUMBER);
if ( !s.empty() )
{
wxASSERT_MSG( s.length() == 1,
"Multi-character thousands separator?" );
s_thousandsSeparator = s[0];
}
//else: Unlike above it's perfectly fine for the thousands separator to
// be empty if grouping is not used, so just leave it as 0.
s_initialized = true;
}
if ( !s_thousandsSeparator )
return false;
if ( sep )
*sep = s_thousandsSeparator;
return true;
}
// ----------------------------------------------------------------------------
// Conversion to string and helpers
// ----------------------------------------------------------------------------
wxString wxNumberFormatter::ToString(long val, int style)
{
wxString s = wxString::Format("%ld", val);
if ( style & Style_WithThousandsSep )
AddThousandsSeparators(s);
wxASSERT_MSG( !(style & Style_NoTrailingZeroes),
"Style_NoTrailingZeroes can't be used with integer values" );
return s;
}
wxString wxNumberFormatter::ToString(double val, int precision, int style)
{
const wxString fmt = wxString::Format("%%.%df", precision);
wxString s = wxString::Format(fmt, val);
if ( style & Style_WithThousandsSep )
AddThousandsSeparators(s);
if ( style & Style_NoTrailingZeroes )
RemoveTrailingZeroes(s);
return s;
}
void wxNumberFormatter::AddThousandsSeparators(wxString& s)
{
wxChar thousandsSep;
if ( !GetThousandsSeparatorIfUsed(&thousandsSep) )
return;
size_t pos = s.find(GetDecimalSeparator());
if ( pos == wxString::npos )
{
// Start grouping at the end of an integer number.
pos = s.length();
}
// We currently group digits by 3 independently of the locale. This is not
// the right thing to do and we should use lconv::grouping (under POSIX)
// and GetLocaleInfo(LOCALE_SGROUPING) (under MSW) to get information about
// the correct grouping to use. This is something that needs to be done at
// wxLocale level first and then used here in the future (TODO).
const size_t GROUP_LEN = 3;
while ( pos > GROUP_LEN )
{
pos -= GROUP_LEN;
s.insert(pos, thousandsSep);
}
}
void wxNumberFormatter::RemoveTrailingZeroes(wxString& s)
{
const size_t posDecSep = s.find(GetDecimalSeparator());
wxCHECK_RET( posDecSep != wxString::npos, "No decimal separator" );
wxCHECK_RET( posDecSep, "Can't start with decimal separator" );
// Find the last character to keep.
size_t posLastNonZero = s.find_last_not_of("0");
// If it's the decimal separator itself, don't keep it neither.
if ( posLastNonZero == posDecSep )
posLastNonZero--;
s.erase(posLastNonZero + 1);
}
// ----------------------------------------------------------------------------
// Conversion from strings
// ----------------------------------------------------------------------------
void wxNumberFormatter::RemoveThousandsSeparators(wxString& s)
{
wxChar thousandsSep;
if ( !GetThousandsSeparatorIfUsed(&thousandsSep) )
return;
s.Replace(wxString(thousandsSep), wxString());
}
bool wxNumberFormatter::FromString(wxString s, long *val)
{
RemoveThousandsSeparators(s);
return s.ToLong(val);
}
bool wxNumberFormatter::FromString(wxString s, double *val)
{
RemoveThousandsSeparators(s);
return s.ToDouble(val);
}