summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2006-05-17 15:51:16 (GMT)
committerGeorg Brandl <georg@python.org>2006-05-17 15:51:16 (GMT)
commitb89316fdbf5d5d4d33ab780c5ce2ab006ebb6ea7 (patch)
tree109cd781e14cff9877c4d41a174dcb2a559350ab /Lib
parent9d6da3e2f29344598178243bb519bc68ad8045b4 (diff)
downloadcpython-b89316fdbf5d5d4d33ab780c5ce2ab006ebb6ea7.zip
cpython-b89316fdbf5d5d4d33ab780c5ce2ab006ebb6ea7.tar.gz
cpython-b89316fdbf5d5d4d33ab780c5ce2ab006ebb6ea7.tar.bz2
Patch #1180296: improve locale string formatting functions
Diffstat (limited to 'Lib')
-rw-r--r--Lib/locale.py170
-rw-r--r--Lib/test/test_locale.py25
2 files changed, 149 insertions, 46 deletions
diff --git a/Lib/locale.py b/Lib/locale.py
index cfc69b1..b60c0a4 100644
--- a/Lib/locale.py
+++ b/Lib/locale.py
@@ -88,13 +88,16 @@ except ImportError:
### Number formatting APIs
# Author: Martin von Loewis
+# improved by Georg Brandl
#perform the grouping from right to left
-def _group(s):
- conv=localeconv()
- grouping=conv['grouping']
- if not grouping:return (s, 0)
- result=""
+def _group(s, monetary=False):
+ conv = localeconv()
+ thousands_sep = conv[monetary and 'mon_thousands_sep' or 'thousands_sep']
+ grouping = conv[monetary and 'mon_grouping' or 'grouping']
+ if not grouping:
+ return (s, 0)
+ result = ""
seps = 0
spaces = ""
if s[-1] == ' ':
@@ -103,63 +106,142 @@ def _group(s):
s = s[:sp]
while s and grouping:
# if grouping is -1, we are done
- if grouping[0]==CHAR_MAX:
+ if grouping[0] == CHAR_MAX:
break
# 0: re-use last group ad infinitum
- elif grouping[0]!=0:
+ elif grouping[0] != 0:
#process last group
- group=grouping[0]
- grouping=grouping[1:]
+ group = grouping[0]
+ grouping = grouping[1:]
if result:
- result=s[-group:]+conv['thousands_sep']+result
+ result = s[-group:] + thousands_sep + result
seps += 1
else:
- result=s[-group:]
- s=s[:-group]
+ result = s[-group:]
+ s = s[:-group]
if s and s[-1] not in "0123456789":
# the leading string is only spaces and signs
- return s+result+spaces,seps
+ return s + result + spaces, seps
if not result:
- return s+spaces,seps
+ return s + spaces, seps
if s:
- result=s+conv['thousands_sep']+result
+ result = s + thousands_sep + result
seps += 1
- return result+spaces,seps
-
-def format(f,val,grouping=0):
- """Formats a value in the same way that the % formatting would use,
+ return result + spaces, seps
+
+def format(percent, value, grouping=False, monetary=False, *additional):
+ """Returns the locale-aware substitution of a %? specifier
+ (percent).
+
+ additional is for format strings which contain one or more
+ '*' modifiers."""
+ # this is only for one-percent-specifier strings and this should be checked
+ if percent[0] != '%':
+ raise ValueError("format() must be given exactly one %char "
+ "format specifier")
+ if additional:
+ formatted = percent % ((value,) + additional)
+ else:
+ formatted = percent % value
+ # floats and decimal ints need special action!
+ if percent[-1] in 'eEfFgG':
+ seps = 0
+ parts = formatted.split('.')
+ if grouping:
+ parts[0], seps = _group(parts[0], monetary=monetary)
+ decimal_point = localeconv()[monetary and 'mon_decimal_point'
+ or 'decimal_point']
+ formatted = decimal_point.join(parts)
+ while seps:
+ sp = formatted.find(' ')
+ if sp == -1: break
+ formatted = formatted[:sp] + formatted[sp+1:]
+ seps -= 1
+ elif percent[-1] in 'diu':
+ if grouping:
+ formatted = _group(formatted, monetary=monetary)[0]
+ return formatted
+
+import re, operator
+_percent_re = re.compile(r'%(?:\((?P<key>.*?)\))?'
+ r'(?P<modifiers>[-#0-9 +*.hlL]*?)[eEfFgGdiouxXcrs%]')
+
+def format_string(f, val, grouping=False):
+ """Formats a string in the same way that the % formatting would use,
but takes the current locale into account.
Grouping is applied if the third parameter is true."""
- result = f % val
- fields = result.split(".")
- seps = 0
- if grouping:
- fields[0],seps=_group(fields[0])
- if len(fields)==2:
- result = fields[0]+localeconv()['decimal_point']+fields[1]
- elif len(fields)==1:
- result = fields[0]
+ percents = list(_percent_re.finditer(f))
+ new_f = _percent_re.sub('%s', f)
+
+ if isinstance(val, tuple):
+ new_val = list(val)
+ i = 0
+ for perc in percents:
+ starcount = perc.group('modifiers').count('*')
+ new_val[i] = format(perc.group(), new_val[i], grouping, False, *new_val[i+1:i+1+starcount])
+ del new_val[i+1:i+1+starcount]
+ i += (1 + starcount)
+ val = tuple(new_val)
+ elif operator.isMappingType(val):
+ for perc in percents:
+ key = perc.group("key")
+ val[key] = format(perc.group(), val[key], grouping)
else:
- raise Error, "Too many decimal points in result string"
+ # val is a single value
+ val = format(percents[0].group(), val, grouping)
+
+ return new_f % val
+
+def currency(val, symbol=True, grouping=False, international=False):
+ """Formats val according to the currency settings
+ in the current locale."""
+ conv = localeconv()
- while seps:
- # If the number was formatted for a specific width, then it
- # might have been filled with spaces to the left or right. If
- # so, kill as much spaces as there where separators.
- # Leading zeroes as fillers are not yet dealt with, as it is
- # not clear how they should interact with grouping.
- sp = result.find(" ")
- if sp==-1:break
- result = result[:sp]+result[sp+1:]
- seps -= 1
+ # check for illegal values
+ digits = conv[international and 'int_frac_digits' or 'frac_digits']
+ if digits == 127:
+ raise ValueError("Currency formatting is not possible using "
+ "the 'C' locale.")
+
+ s = format('%%.%if' % digits, abs(val), grouping, monetary=True)
+ # '<' and '>' are markers if the sign must be inserted between symbol and value
+ s = '<' + s + '>'
+
+ if symbol:
+ smb = conv[international and 'int_curr_symbol' or 'currency_symbol']
+ precedes = conv[val<0 and 'n_cs_precedes' or 'p_cs_precedes']
+ separated = conv[val<0 and 'n_sep_by_space' or 'p_sep_by_space']
+
+ if precedes:
+ s = smb + (separated and ' ' or '') + s
+ else:
+ s = s + (separated and ' ' or '') + smb
+
+ sign_pos = conv[val<0 and 'n_sign_posn' or 'p_sign_posn']
+ sign = conv[val<0 and 'negative_sign' or 'positive_sign']
+
+ if sign_pos == 0:
+ s = '(' + s + ')'
+ elif sign_pos == 1:
+ s = sign + s
+ elif sign_pos == 2:
+ s = s + sign
+ elif sign_pos == 3:
+ s = s.replace('<', sign)
+ elif sign_pos == 4:
+ s = s.replace('>', sign)
+ else:
+ # the default if nothing specified;
+ # this should be the most fitting sign position
+ s = sign + s
- return result
+ return s.replace('<', '').replace('>', '')
def str(val):
"""Convert float to integer, taking the locale into account."""
- return format("%.12g",val)
+ return format("%.12g", val)
-def atof(string,func=float):
+def atof(string, func=float):
"Parses a string as a float according to the locale settings."
#First, get rid of the grouping
ts = localeconv()['thousands_sep']
@@ -179,10 +261,10 @@ def atoi(str):
def _test():
setlocale(LC_ALL, "")
#do grouping
- s1=format("%d", 123456789,1)
+ s1 = format("%d", 123456789,1)
print s1, "is", atoi(s1)
#standard formatting
- s1=str(3.14)
+ s1 = str(3.14)
print s1, "is", atof(s1)
### Locale name aliasing engine
diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py
index 1523e77..ec5a533 100644
--- a/Lib/test/test_locale.py
+++ b/Lib/test/test_locale.py
@@ -20,14 +20,14 @@ for tloc in tlocs:
else:
raise ImportError, "test locale not supported (tried %s)"%(', '.join(tlocs))
-def testformat(formatstr, value, grouping = 0, output=None):
+def testformat(formatstr, value, grouping = 0, output=None, func=locale.format):
if verbose:
if output:
print "%s %% %s =? %s ..." %\
(repr(formatstr), repr(value), repr(output)),
else:
print "%s %% %s works? ..." % (repr(formatstr), repr(value)),
- result = locale.format(formatstr, value, grouping = grouping)
+ result = func(formatstr, value, grouping = grouping)
if output and result != output:
if verbose:
print 'no'
@@ -49,6 +49,27 @@ try:
testformat("%-10.f", 4200, grouping=1, output='4%s200 ' % sep)
# Invoke getpreferredencoding to make sure it does not cause exceptions,
locale.getpreferredencoding()
+
+ # === Test format() with more complex formatting strings
+ # test if grouping is independent from other characters in formatting string
+ testformat("One million is %i", 1000000, grouping=1, output='One million is 1,000,000',
+ func=locale.format_string)
+ testformat("One million is %i", 1000000, grouping=1, output='One million is 1,000,000',
+ func=locale.format_string)
+ # test dots in formatting string
+ testformat(".%f.", 1000.0, output='.1000.000000.', func=locale.format_string)
+ # test floats
+ testformat("--> %10.2f", 1000.0, grouping=1, output='--> 1,000.00',
+ func=locale.format_string)
+ # test asterisk formats
+ testformat("%10.*f", (2, 1000.0), grouping=0, output=' 1000.00',
+ func=locale.format_string)
+ testformat("%*.*f", (10, 2, 1000.0), grouping=1, output=' 1,000.00',
+ func=locale.format_string)
+ # test more-in-one
+ testformat("int %i float %.2f str %s", (1000, 1000.0, 'str'), grouping=1,
+ output='int 1,000 float 1,000.00 str str', func=locale.format_string)
+
finally:
locale.setlocale(locale.LC_NUMERIC, oldlocale)