ultimatepp/uppsrc/Core/Format.cpp
Mirek Fidler 34ff691308 sizeof(wchar) is changed to 4 (32 bits) to support non BMP unicode characters
This might bring some incompatibilities in the code that expects wchar to be 16 bit, which
  escpecially involves dealing with Win32 (and to lesser extend MacOS) APIs, so if your application
  is doing that, please check all instances of WCHAR (UniChar on MacOS) or even wchar
  especially type casts.

  To support host APIs, char16 is introduced (but there is no 16-bit String varian).

  Use ToSystemCharsetW, FromSystemCharsetW to convert texts to Win32 API.

- Support of drawing non-BMP characters in GUI
- Vastly improved character font replacement code (when drawing characters missing with requested font, replacement font is used)
- Last instances of Win32 ANSI calls (those ending with A) are removed
- UTF handling routines are refactored and their's naming is unified
- RTF is now being able to handle non-BMP characters (RTF is used as clipboard format for RichText)

Other minor changes:

- fixed TryRealloc issue
- improved MemoryCheck
- Removed MemoryAlloc48/MemoryFree48
- In theide Background parsing should less often cause delays in the main thread
2021-12-02 12:03:19 +01:00

1150 lines
24 KiB
C++

#include "Core.h"
//#BLITZ_APPROVE
namespace Upp {
// Old format ---------------------------
String VFormat(const char *fmt, va_list ptr) {
int limit = 2 * (int)strlen(fmt) + 1024;
if(limit < 1500) {
char buffer[1500];
vsprintf(buffer, fmt, ptr);
va_end(ptr);
int len = (int)strlen(buffer);
ASSERT(len <= limit);
return String(buffer, len);
}
else {
Buffer<char> buffer(limit);
vsprintf(buffer, fmt, ptr);
va_end(ptr);
int len = (int)strlen(buffer);
ASSERT(len <= limit);
return String(buffer, len);
}
}
// Formatting routines ---------------------------
// utoa32, utoa64 inspired by
// https://github.com/miloyip/itoa-benchmark/blob/940542a7770155ee3e9f2777ebc178dc899b43e0/src/branchlut.cpp
// by Milo Yip
const char s100[] =
"00010203040506070809"
"10111213141516171819"
"20212223242526272829"
"30313233343536373839"
"40414243444546474849"
"50515253545556575859"
"60616263646566676869"
"70717273747576777879"
"80818283848586878889"
"90919293949596979899"
;
namespace utoa_private {
force_inline
void Do2(char *t, dword d) {
memcpy(t, s100 + 2 * d, 2);
};
force_inline
void Do4(char *t, dword value) {
Do2(t, value / 100);
Do2(t + 2, value % 100);
}
force_inline
void Do8(char *t, dword value) {
Do4(t, value / 10000);
Do4(t + 4, value % 10000);
}
};
int utoa32(dword value, char *buffer)
{
using namespace utoa_private;
if (value < 10000) {
if(value < 100) {
if(value < 10) {
*buffer = char(value + '0');
return 1;
}
Do2(buffer, value % 100);
return 2;
}
if(value < 1000) {
*buffer = char(value / 100 + '0');
Do2(buffer + 1, value % 100);
return 3;
}
Do4(buffer, value);
return 4;
}
else if (value < 100000000) {
if(value < 10000000) {
if(value < 100000) {
*buffer = char(value / 10000 + '0');
Do4(buffer + 1, value % 10000);
return 5;
}
if(value < 1000000) {
Do2(buffer, value / 10000);
Do4(buffer + 2, value % 10000);
return 6;
}
*buffer = char(value / 1000000 + '0');
Do2(buffer + 1, value / 10000 % 100);
Do4(buffer + 3, value % 10000);
return 7;
}
Do8(buffer, value);
return 8;
}
else {
dword a = value / 100000000; // 2 digits
value %= 100000000;
if(a < 10) {
*buffer = char(a + '0');
Do8(buffer + 1, value);
return 9;
}
Do2(buffer, a);
Do8(buffer + 2, value);
return 10;
}
}
int utoa64(uint64 value, char *buffer)
{
using namespace utoa_private;
if(value <= 0xffffffff)
return utoa32((dword)value, buffer);
if(value < (uint64)1000000000 * 100000000) {
int q = utoa32(dword(value / 100000000), buffer);
Do8(buffer + q, value % 100000000);
return q + 8;
}
int q = utoa32(dword(value / ((uint64)100000000 * 100000000)), buffer);
Do8(buffer + q, value / 100000000 % 100000000);
Do8(buffer + 8 + q, value % 100000000);
return q + 16;
}
String FormatUInt64(uint64 w)
{
if(w < 100000000000000)
return String::Make(14, [&](char *s) { return utoa64(w, s); });
else
return String::Make(20, [&](char *s) { return utoa64(w, s); });
}
String FormatInt64(int64 i)
{
if(IsNull(i))
return String();
if(i < 0) {
i = -i;
if(i < 10000000000000)
return String::Make(14, [&](char *s) {
*s++ = '-';
return utoa64(i, s) + 1;
});
return String::Make(20, [&](char *s) {
*s++ = '-';
return utoa64(i, s) + 1;
});
}
if(i < 100000000000000)
return String::Make(14, [&](char *s) { return utoa64(i, s); });
return String::Make(20, [&](char *s) { return utoa64(i, s); });
}
String FormatIntBase(int i, int base, int width, char lpad, int sign, bool upper)
{
enum { BUFFER = sizeof(int) * 8 + 1 };
if(base < 2 || base > 36)
return "<invalid base>";
char buffer[BUFFER];
char *const e = buffer + (int)BUFFER;
char *p = e;
const char *itoc = upper ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" : "0123456789abcdefghijklmnopqrstuvwxyz";
if(sign < 0 || !IsNull(i))
{
unsigned x = i;
if(sign >= 0 && i < 0)
x = -i;
do
*--p = itoc[x % base];
while(x /= base);
}
bool minus = (sign >= 0 && i < 0 && !IsNull(i));
bool do_sign = (sign > 0 || sign >= 0 && minus);
if(do_sign && lpad != '0')
*--p = (minus ? '-' : '+');
if(width > e - p)
{
char *b = e - min<int>(width, BUFFER);
while(p > b)
*--p = lpad;
}
if(do_sign && lpad == '0')
*--p = (minus ? '-' : '+');
int dwd = (int)(e - p);
int pad = (width = max(width, dwd)) - dwd;
StringBuffer out(width);
char *o = out;
if(dwd < width)
memset(o, lpad, pad);
memcpy8(o + pad, p, dwd);
return String(out);
}
String FormatIntDec(int i, int width, char lpad, bool always_sign)
{
return FormatIntBase(i, 10, width, lpad, always_sign ? 1 : 0);
}
String FormatIntHex(int i, int width, char lpad)
{
return FormatIntBase(i, 16, width, lpad, -1);
}
String FormatIntHexUpper(int i, int width, char lpad)
{
return FormatIntBase(i, 16, width, lpad, -1, true);
}
String FormatIntOct(int i, int width, char lpad)
{
return FormatIntBase(i, 8, width, lpad, -1);
}
String FormatIntAlpha(int i, bool upper)
{
if(IsNull(i) || i == 0)
return Null;
String out;
if(i < 0)
{
out << '-';
i = -i;
}
char temp[10], *p = temp + 10;
const char *itoc = upper ? "ZABCDEFGHIJKLMNOPQRSTUVWXYZ" : "zabcdefghijklmnopqrstuvwxyz";
do
*--p = itoc[i-- % 26];
while(i /= 26);
out.Cat(p, temp + 10);
return out;
}
String FormatIntRoman(int i, bool upper)
{
if(IsNull(i) || i == 0)
return Null;
String out;
if(i < 0)
{
out << '-';
i = -i;
}
int m = i / 1000;
if(m)
{
out.Cat('M', m);
i -= 1000 * m;
}
char shift = upper ? 0 : 'a' - 'A';
static const int value[] = { 1000, 500, 100, 50, 10, 5, 1 };
static const char letter[] = { 'M', 'D', 'C', 'L', 'X', 'V', 'I' };
for(int n = 0; i && n < __countof(value); n++)
{
int v = value[n];
while(i >= v)
{
out << (char)(letter[n] + shift);
i -= v;
}
if(n < __countof(value) - 1)
for(int j = n + (value[n + 1] * 2 >= v ? 2 : 1); j < __countof(value); j++)
if(i >= v - value[j])
{ // subtraction scheme
out << (char)(letter[j] + shift) << (char)(letter[n] + shift);
i -= v - value[j];
break;
}
}
return out;
}
String Format64Hex(uint64 a)
{
char b[50];
char *p = b + 50;
do {
*--p = "0123456789abcdef"[a & 15];
a >>= 4;
}
while(a);
return String(p, b + 50);
}
String FormatBool(bool a) { return a ? "true" : "false"; }
String FormatPtr(const void *p) { return "0x" + FormatHex(p); }
String FormatDate(Date date, const char *format, int language)
{
if(IsNull(date))
return Null;
if(!format || !*format)
return Format(date);
return FormatTime(ToTime(date), format, language);
}
String FormatTime(Time t, const char *s, int language)
{
if(IsNull(t))
return Null;
String result;
if(!s || !*s)
return Format(t);
while(*s) {
int q = 0;
if(*s == 'M') {
while(*s == 'M') { s++; q++; }
if(q == 1)
result.Cat(Format("%d", t.month));
else
result.Cat(Format("%02d", t.month));
}
else
if(*s == 'D') {
while(*s == 'D') { s++; q++; }
if(q == 1)
result.Cat(Format("%d", t.day));
else
result.Cat(Format("%02d", t.day));
}
else
if(*s == 'Y') {
while(*s == 'Y') { s++; q++; }
if(q == 1)
result.Cat(Format("%d", t.year % 100));
else
if(q == 2)
result.Cat(Format("%02d", t.year % 100));
else
result.Cat(Format("%d", t.year));
}
else
if(*s == 'h') {
while(*s == 'h') { s++; q++; }
if(q == 1)
result.Cat(Format("%d", t.hour));
else
result.Cat(Format("%02d", t.hour));
}
else
if(*s == 'H') {
while(*s == 'H') { s++; q++; }
int h = ((t.hour + 11) % 12 + 1);
if(q == 1)
result.Cat(Format("%d", h));
else
result.Cat(Format("%02d", h));
}
else
if(*s == '<') {
s++;
while(*s && *s != '/') {
if(t.hour <= 12)
result.Cat(*s);
s++;
}
if(!*s) break;
s++;
while(*s && *s != '>') {
if(t.hour > 12)
result.Cat(*s);
s++;
}
if(!*s) break;
s++;
}
else
if(*s == 'm') {
while(*s == 'm') { s++; q++; }
if(q == 1)
result.Cat(Format("%d", t.minute));
else
result.Cat(Format("%02d", t.minute));
}
else
if(*s == 's') {
while(*s == 's') { s++; q++; }
if(q == 1)
result.Cat(Format("%d", t.second));
else
result.Cat(Format("%02d", t.second));
}
else
if(*s == '`' && s[1]) {
s++;
result.Cat(*s++);
}
else
result.Cat(*s++);
}
return result;
}
// New format ----------------------------
void TrimChar(String& s, int n)
{
if(GetDefaultCharset() == CHARSET_UTF8) {
WString h(s);
h.Trim(n);
s = h.ToString();
}
else
s.Trim(n);
}
struct FormId : Moveable<FormId> {
FormId(String id, int type) : id(id), type(type) {}
String id;
int type;
};
hash_t GetHashValue(const FormId& fid)
{
return CombineHash(fid.type, GetHashValue(fid.id));
}
bool operator==(const FormId& a, const FormId& b)
{
return a.type == b.type && a.id == b.id;
}
VectorMap<FormId, Formatter>& formatmap()
{
return Single< VectorMap<FormId, Formatter> > ();
}
void RegisterFormatter(int type, const char *id, Formatter f)
{
AssertST();
INTERLOCKED {
FormId fid(id, type);
formatmap().FindAdd(fid, f);
formatmap().Find(fid);
}
}
void RegisterValueFormatter(const char *id, Formatter f)
{
RegisterFormatter(VALUE_V, id, f);
}
void RegisterNullFormatter(const char *id, Formatter f)
{
RegisterFormatter(VOID_V, id, f);
RegisterFormatter(ERROR_V, id, f);
}
void RegisterNumberFormatter(const char *id, Formatter f)
{
RegisterFormatter(DOUBLE_V, id, f);
RegisterFormatter(INT64_V, id, f);
RegisterFormatter(INT_V, id, f);
RegisterFormatter(BOOL_V, id, f);
RegisterNullFormatter(id, f);
}
void RegisterStringFormatter(const char *id, Formatter f)
{
RegisterFormatter(WSTRING_V, id, f);
RegisterFormatter(STRING_V, id, f);
RegisterNullFormatter(id, f);
}
void RegisterDateTimeFormatter(const char *id, Formatter f)
{
RegisterFormatter(TIME_V, id, f);
RegisterFormatter(DATE_V, id, f);
RegisterNullFormatter(id, f);
}
String IntFormatter(const Formatting& f)
{
if(f.format.GetCount() == 0 && f.id[0] == 'd' && f.id[1] == 0)
return AsString((int)f.arg);
StringBuffer q;
q.SetLength(1000);
q.SetLength(sprintf(q, '%' + f.format + f.id, (int)f.arg));
return String(q);
}
String Int64Formatter(const Formatting& f)
{
StringBuffer q;
q.SetLength(1000);
q.SetLength(sprintf(q, '%' + f.format + f.id, (int64)f.arg));
return String(q);
}
String IntLowerAlphaFormatter(const Formatting& f)
{
return FormatIntAlpha(f.arg, false);
}
String IntUpperAlphaFormatter(const Formatting& f)
{
return FormatIntAlpha(f.arg, true);
}
String IntLowerRomanFormatter(const Formatting& f)
{
return FormatIntRoman(f.arg, false);
}
String IntUpperRomanFormatter(const Formatting& f)
{
return FormatIntRoman(f.arg, true);
}
String DoubleFormatter(const Formatting& f)
{
const char *s = f.format;
bool fillz = false;
bool wd = true;
bool left = false;
int width = 0;
int precision = 6;
int flags = FD_SIGN_EXP|FD_SPECIAL|FD_MINUS0;
const char *id = f.id;
if(*id++ == 'M')
flags |= FD_CAP_E;
bool lng = false;
if(*id == 'l') {
lng = true;
id++;
}
if(*id == 'E') flags |= FD_EXP|FD_CAP_E;
if(*id == 'e') flags |= FD_EXP;
if(*id == 'f') flags |= FD_FIX|FD_ZEROS;
while(*s) {
if(IsDigit(*s)) {
if(wd && *s == '0')
fillz = true;
dword n;
bool overflow = false;
s = ScanUint<char, byte, dword, 10>(n, s, overflow);
if(overflow || !s || n > (wd ? 1000u : 100u))
return Null;
(wd ? width : precision) = n;
}
else
switch(*s++) {
case '-': left = true; break;
case '+': flags |= FD_SIGN; break;
case ' ': flags |= FD_SIGN_SPACE; break;
case ',': flags |= FD_COMMA; wd = false; break;
case '.': flags &= ~FD_COMMA; wd = false; break;
case '!': flags |= FD_ZEROS; break;
case '?': flags &= ~FD_SPECIAL; break;
case '_': flags &= ~FD_MINUS0; break;
case '^': flags &= ~FD_SIGN_EXP; break;
case '&': flags |= FD_MINIMAL_EXP; break;
case '#': flags |= FD_ZEROS|FD_POINT; break;
}
}
String r = FormatDouble(f.arg, precision, flags);
if(lng) {
int q = r.Find('.');
if(q >= 0)
r = r.Mid(0, q) + GetLanguageInfo(f.language).decimal_point + r.Mid(q + 1);
}
if(width > r.GetCount()) {
if(fillz && !left && !IsNull(f.arg))
return IsDigit(*r) ? String('0', width - r.GetCount()) + r
: r.Mid(0, 1) + String('0', width - r.GetCount()) + r.Mid(1);
return left ? r + String(' ', width - r.GetCount()) : String(' ', width - r.GetCount()) + r;
}
return r;
}
String RealFormatter(const Formatting& f)
{
if(IsNull(f.arg))
return Null;
double value = f.arg;
const char *s = f.format;
int digits = 6;
const char *id = f.id;
bool fn = *id++ != 'v';
int flags = 0;
if(*s == '+') {
flags |= FD_SIGN;
s++;
}
if(IsDigit(*s) || *s == '-' && IsDigit(s[1])) {
digits = (int)strtol(s, NULL, 10);
while(IsDigit(*++s))
;
}
if(*s == '@') { s++; flags |= FD_NOTHSEPS; }
if(*s == ',') { s++; flags |= FD_COMMA; }
if(*s == '!') { s++; flags |= FD_ZEROS; }
if(*s == '^') {
if(*++s == '+') {
flags |= FD_SIGN_EXP;
s++;
}
while(IsDigit(*++s))
;
}
bool lng = false;
if(*id == 'l') {
lng = true;
id++;
}
if(*id == 'e') flags |= FD_EXP;
else if(*id == 'f') flags |= FD_FIX;
if(fn && value >= 1e-15 && value <= 1e15)
flags |= FD_FIX;
if(lng)
return GetLanguageInfo(f.language).FormatDouble(value, digits, flags, 0);
else
return FormatDouble(value, digits, flags);
}
String StringFormatter(const Formatting& f)
{
const String& s = f.arg;
if(f.format.GetCount() == 0 && f.id[0] == 's' && f.id[1] == 0)
return s;
int len = s.GetCharCount();
int width = len;
int precision = len;
bool lpad = false;
CParser p(f.format);
if(p.Char('-'))
lpad = true;
if(p.IsNumber())
width = p.ReadInt();
if(p.Char('.') && p.IsNumber())
precision = p.ReadInt();
// if(precision > len)
// return WString(~s, precision).ToString();
String q = s;
if(precision < len) {
TrimChar(q, precision);
len = precision;
}
String r;
if(lpad)
r.Cat(q);
if(width > len)
r.Cat(' ', width - len);
if(!lpad)
r.Cat(q);
return r;
}
static inline
void sFixPoint(char *s) // We do not want locale to affect decimal point, convert ',' to '.'
{
while(*s) {
if(*s == ',')
*s = '.';
s++;
}
}
String FloatFormatter(const Formatting& f)
{
double d = f.arg;
String fmt = '%' + f.format + f.id;
char h[256];
#ifdef COMPILER_MSC
int n = _snprintf(h, 256, fmt, d);
if(n < 0)
#else
int n = snprintf(h, 255, fmt, d);
if(n >= 254)
#endif
{
#ifdef COMPILER_MSC
n = _scprintf(fmt, d);
#endif
Buffer<char> ah(n + 1);
sprintf(ah, fmt, d);
sFixPoint(ah);
return String(ah, n);
}
if(n < 0)
return String();
sFixPoint(h);
return String(h, n);
}
String DateFormatter(const Formatting& f)
{
return GetLanguageInfo(f.language).FormatDate(f.arg);
}
String TimeFormatter(const Formatting& f)
{
return GetLanguageInfo(f.language).FormatTime(f.arg);
}
String SwitchFormatter(const Formatting& f)
{
const char *s = f.format;
int i = f.arg;
int o = i;
for(;;) {
int n = 0;
while(IsDigit(*s))
n = 10 * n + *s++ - '0';
if(!*s) return Null;
if(*s == '%') {
o = i % max(n, 1);
s++;
}
else
if(*s == ',' || *s == ':') {
if(o == n) {
while(*s && *s != ':')
s++;
if(!*s) return Null;
++s;
const char *b = s;
while(*s && *s != ';')
s++;
return String(b, s);
}
if(*s == ':')
while(*s && *s != ';')
s++;
if(!*s) return Null;
s++;
}
else
return s;
}
return String();
}
String StdFormatFormatter(const Formatting& f)
{
String out = StdFormat(f.arg);
if(!IsNull(out))
return out;
return f.format;
}
String MonthFormatter(const Formatting& f)
{
return MonthName((int)f.arg - 1, f.language);
}
String MONTHFormatter(const Formatting& f)
{
return ToUpper(MonthFormatter(f), GetLNGCharset(f.language));
}
String monthFormatter(const Formatting& f)
{
return ToLower(MonthFormatter(f), GetLNGCharset(f.language));
}
String MonFormatter(const Formatting& f)
{
return MonName((int)f.arg - 1, f.language);
}
String MONFormatter(const Formatting& f)
{
return ToUpper(MonFormatter(f), GetLNGCharset(f.language));
}
String monFormatter(const Formatting& f)
{
return ToLower(MonFormatter(f), GetLNGCharset(f.language));
}
String DayFormatter(const Formatting& f)
{
return DayName((int)f.arg, f.language);
}
String DAYFormatter(const Formatting& f)
{
return ToUpper(DayFormatter(f), GetLNGCharset(f.language));
}
String dayFormatter(const Formatting& f)
{
return ToLower(DayFormatter(f), GetLNGCharset(f.language));
}
String DyFormatter(const Formatting& f)
{
return DyName((int)f.arg, f.language);
}
String DYFormatter(const Formatting& f)
{
return ToUpper(DyFormatter(f), GetLNGCharset(f.language));
}
String dyFormatter(const Formatting& f)
{
return ToLower(DyFormatter(f), GetLNGCharset(f.language));
}
String twFormatter(const Formatting& f)
{
int q = f.arg;
return Sprintf(*f.format == '0' ? "%02d" : "%d", q ? q % 12 : 12);
}
String NumberFormatter(const Formatting& f)
{
int q = f.arg;
return AsString(q);
}
void IntDoubleRegister(int type)
{
RegisterFormatter(type, "", &NumberFormatter);
RegisterFormatter(type, "c", &IntFormatter);
RegisterFormatter(type, "d", &IntFormatter);
RegisterFormatter(type, "i", &IntFormatter);
RegisterFormatter(type, "o", &IntFormatter);
RegisterFormatter(type, "x", &IntFormatter);
RegisterFormatter(type, "X", &IntFormatter);
RegisterFormatter(type, "ld", &IntFormatter);
RegisterFormatter(type, "li", &IntFormatter);
RegisterFormatter(type, "lo", &IntFormatter);
RegisterFormatter(type, "lx", &IntFormatter);
RegisterFormatter(type, "lX", &IntFormatter);
RegisterFormatter(type, "lld", &Int64Formatter);
RegisterFormatter(type, "lli", &Int64Formatter);
RegisterFormatter(type, "llo", &Int64Formatter);
RegisterFormatter(type, "llx", &Int64Formatter);
RegisterFormatter(type, "llX", &Int64Formatter);
RegisterFormatter(type, "e", &FloatFormatter);
RegisterFormatter(type, "E", &FloatFormatter);
RegisterFormatter(type, "f", &FloatFormatter);
RegisterFormatter(type, "g", &FloatFormatter);
RegisterFormatter(type, "G", &FloatFormatter);
RegisterFormatter(type, "s", &SwitchFormatter);
RegisterFormatter(type, "month", &monthFormatter);
RegisterFormatter(type, "Month", &MonthFormatter);
RegisterFormatter(type, "MONTH", &MONTHFormatter);
RegisterFormatter(type, "mon", &monFormatter);
RegisterFormatter(type, "Mon", &MonFormatter);
RegisterFormatter(type, "MON", &MONFormatter);
RegisterFormatter(type, "Day", &DayFormatter);
RegisterFormatter(type, "DAY", &DAYFormatter);
RegisterFormatter(type, "day", &dayFormatter);
RegisterFormatter(type, "Dy", &DyFormatter);
RegisterFormatter(type, "DY", &DYFormatter);
RegisterFormatter(type, "dy", &dyFormatter);
RegisterFormatter(type, "tw", &twFormatter);
}
static void sRegisterFormatters()
{
ONCELOCK {
IntDoubleRegister(BOOL_V);
IntDoubleRegister(INT_V);
IntDoubleRegister(INT64_V);
IntDoubleRegister(DOUBLE_V);
RegisterStringFormatter("s", &StringFormatter);
RegisterNullFormatter("", &DateFormatter);
RegisterFormatter(DATE_V, "", &DateFormatter);
RegisterFormatter(TIME_V, "", &TimeFormatter);
RegisterNumberFormatter("n", &RealFormatter);
RegisterNumberFormatter("ne", &RealFormatter);
RegisterNumberFormatter("nf", &RealFormatter);
RegisterNumberFormatter("nl", &RealFormatter);
RegisterNumberFormatter("nle", &RealFormatter);
RegisterNumberFormatter("nlf", &RealFormatter);
RegisterNumberFormatter("v", &RealFormatter);
RegisterNumberFormatter("ve", &RealFormatter);
RegisterNumberFormatter("vf", &RealFormatter);
RegisterNumberFormatter("vl", &RealFormatter);
RegisterNumberFormatter("vle", &RealFormatter);
RegisterNumberFormatter("vlf", &RealFormatter);
// real number formats (n = fixed decimals, v = valid decimals)
// ne, ve - force exponential notation; nf, vf - force fixed notation; nl, vl - language-based formatting
// Options: [+][[-]<digits>][!][^[+]<expdig>]
// + .. always prepend sign
// [-]<digits> .. number of decimals to print (negative = left of decimal point, default = 6)
// ! .. keep insignificant zeros
// ^ .. exponent options:
// + .. always prepend sign to exponent
// <expdig> exponent padding width
RegisterNumberFormatter("m", &DoubleFormatter);
RegisterNumberFormatter("me", &DoubleFormatter);
RegisterNumberFormatter("mf", &DoubleFormatter);
RegisterNumberFormatter("ml", &DoubleFormatter);
RegisterNumberFormatter("mle", &DoubleFormatter);
RegisterNumberFormatter("mlf", &DoubleFormatter);
RegisterNumberFormatter("M", &DoubleFormatter);
RegisterNumberFormatter("mE", &DoubleFormatter);
RegisterNumberFormatter("Ml", &DoubleFormatter);
RegisterNumberFormatter("mlE", &DoubleFormatter);
RegisterNumberFormatter("a", &IntLowerAlphaFormatter);
RegisterNumberFormatter("A", &IntUpperAlphaFormatter);
RegisterNumberFormatter("r", &IntLowerRomanFormatter);
RegisterNumberFormatter("R", &IntUpperRomanFormatter);
RegisterValueFormatter("vt", &StdFormatFormatter);
RegisterValueFormatter("", &StdFormatFormatter);
}
}
INITBLOCK {
sRegisterFormatters();
}
String Format(int language, const char *s, const Vector<Value>& v)
{
sRegisterFormatters();
Formatting f;
f.language = language;
String result;
int pos = 0;
const char *b;
for(;;) {
int n = 0;
b = s;
for(;;) {
while(*s && *s != '%')
++s;
result.Cat(b, (int)(s - b));
if(*s == '\0')
return result;
++s;
if(*s == '%') {
result.Cat('%');
++s;
}
else
break;
b = s;
}
f.format.Clear();
f.id.Clear();
b = s;
int pad = -1;
int padn = 0;
String nvl_value = String::GetVoid();
for(;;) {
if(*s == '$') {
pos = n - 1;
b = ++s;
n = 0;
}
else
if(*s == ':') {
pos = n - 1;
b = ++s;
n = 0;
}
else
if(*s == '*') {
f.format.Cat(b, (int)(s - b));
b = ++s;
int i = v[pos++];
if(*s == ':' || *s == '$' || *s == '<' || *s == '>' || *s == '=')
n = i;
else
f.format.Cat(FormatInt(i));
}
else
if(*s == '<') {
padn = n;
pad = ALIGN_LEFT;
b = ++s;
n = 0;
}
else
if(*s == '>') {
padn = n;
pad = ALIGN_RIGHT;
b = ++s;
n = 0;
}
else
if(*s == '=') {
padn = n;
pad = ALIGN_CENTER;
b = ++s;
n = 0;
}
else
if(*s == '[') {
f.format.Cat(b, (int)(s - b));
s++;
b = s;
while(*s && *s != ']')
s++;
f.format.Cat(b, (int)(s - b));
if(*s) s++;
b = s;
if(!IsAlpha(*s) && *s != '~') break;
}
else if(*s == '~') {
nvl_value = f.format;
f.format = Null;
b = ++s;
}
else
if(!*s || *s == '`' || IsAlpha(*s))
break;
else {
if(!*s)
return result + "<unexpected end>";
if(IsDigit(*s))
n = 10 * n + *s - '0';
else
n = 0;
s++;
}
}
f.format.Cat(b, (int)(s - b));
b = s;
while(IsAlpha(*s))
s++;
f.id = String(b, s);
if(pos < 0 || pos >= v.GetCount())
{
result << "<invalid pos=" << pos << ">";
if(*s == '`')
s++;
continue;
}
f.arg = v[pos++];
String r;
if(!nvl_value.IsVoid() && IsNull(f.arg))
r = nvl_value;
else
{
Formatter ft = NULL;
#ifdef _DEBUG
int fi = formatmap().Find(FormId(f.id, f.arg.GetType()));
if(fi < 0) fi = formatmap().Find(FormId(f.id, VALUE_V));
if(fi >= 0)
ft = formatmap()[fi];
#else
for(;;) {
int fi = formatmap().Find(FormId(f.id, f.arg.GetType()));
if(fi < 0) fi = formatmap().Find(FormId(f.id, VALUE_V));
if(fi >= 0)
{
ft = formatmap()[fi];
break;
}
if(f.id.GetLength() == 0) break;
f.id.Trim(f.id.GetLength() - 1);
s--;
}
#endif
if(ft)
r = (*ft)(f);
else
r << "<N/A '" << f.id << "' for type " << (int)f.arg.GetType() << ">";
}
int len;
if(padn < 0 || padn > 1000)
r << "<invalid padding>";
else
switch(pad) {
case ALIGN_LEFT:
len = r.GetCharCount();
if(len < padn)
r.Cat(' ', padn - len);
else
TrimChar(r, padn);
break;
case ALIGN_RIGHT:
len = r.GetCharCount();
if(len < padn) {
String fill(' ', padn - len);
r = fill + r;
}
else
TrimChar(r, padn);
break;
case ALIGN_CENTER:
len = r.GetCharCount();
if(len < padn) {
int ll = (padn - len) / 2;
r = String(' ', ll) + r;
r.Cat(' ', padn - len - ll);
}
else
TrimChar(r, padn);
break;
}
result.Cat(r);
if(*s == '`')
s++;
}
}
String Format(const char *s, const Vector<Value>& v) { return Format(GetCurrentLanguage(), s, v); }
String Sprintf(const char *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
return VFormat(fmt, argptr);
}
String DeFormat(const char *text)
{
StringBuffer x;
while(*text) {
if(*text == '%')
x.Cat('%');
x.Cat(*text++);
}
return String(x);
}
}