From 1a2d75fbe6c7fe6e617f03802491e0e8b9749211 Mon Sep 17 00:00:00 2001 From: Shyotl Date: Sun, 15 May 2011 22:50:56 -0500 Subject: [PATCH] LLStringOps, LLStringUtilBase, and LLDate updated to V2. If dates go wonky this is where it's from --- indra/llcommon/lldate.cpp | 151 +++-- indra/llcommon/lldate.h | 5 +- indra/llcommon/llfasttimer.h | 3 + indra/llcommon/llstring.cpp | 587 +++++++++++++++++- indra/llcommon/llstring.h | 191 ++++-- indra/newview/llfasttimerview.cpp | 1 + indra/newview/llwindebug.cpp | 6 +- .../win_crash_logger/llcrashloggerwindows.cpp | 6 +- 8 files changed, 833 insertions(+), 117 deletions(-) diff --git a/indra/llcommon/lldate.cpp b/indra/llcommon/lldate.cpp index be294480e..e949cbbb4 100644 --- a/indra/llcommon/lldate.cpp +++ b/indra/llcommon/lldate.cpp @@ -38,10 +38,13 @@ #include "apr_time.h" #include +#include +#include #include #include #include "lltimer.h" +#include "llstring.h" static const F64 DATE_EPOCH = 0.0; @@ -88,47 +91,40 @@ std::string LLDate::asString() const // is one of the standards used and the prefered format std::string LLDate::asRFC1123() const { - std::ostringstream stream; - toHTTPDateStream(stream); - return stream.str(); + return toHTTPDateString (std::string ("%A, %d %b %Y %H:%M:%S GMT")); } -void LLDate::toHTTPDateStream(std::ostream& s) const + +std::string LLDate::toHTTPDateString (std::string fmt) const { - // http://apr.apache.org/docs/apr/0.9/group__apr__time.html - apr_time_t time = (apr_time_t)(mSecondsSinceEpoch * LL_APR_USEC_PER_SEC); + + time_t locSeconds = (time_t) mSecondsSinceEpoch; + struct tm * gmt = gmtime (&locSeconds); + return toHTTPDateString(gmt, fmt); +} - apr_time_exp_t exp_time ; //Apache time module - - if (apr_time_exp_gmt(&exp_time, time) != APR_SUCCESS) - { +std::string LLDate::toHTTPDateString (tm * gmt, std::string fmt) +{ // Return Epoch UTC date - s << "Thursday, 01 Jan 1970 00:00:00 GMT" ; - return; - } - s << std::dec << std::setfill('0'); -#if( LL_WINDOWS || __GNUC__ > 2) - s << std::right ; -#else - s.setf(ios::right); -#endif - static char const* const weekdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; - static char const* const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; - std::string day = weekdays[exp_time.tm_wday]; - std::string month = months[exp_time.tm_mon]; + // avoid calling setlocale() unnecessarily - it's expensive. + static std::string prev_locale = ""; + std::string this_locale = LLStringUtil::getLocale(); + if (this_locale != prev_locale) + { + setlocale(LC_TIME, this_locale.c_str()); + prev_locale = this_locale; + } - s << std::setw(day.length()) << (day) - << ", " << std::setw(2) << (exp_time.tm_mday) - << ' ' << std::setw(month.length()) << (month) - << ' ' << std::setw(4) << (exp_time.tm_year + 1900) - << ' ' << std::setw(2) << (exp_time.tm_hour) - << ':' << std::setw(2) << (exp_time.tm_min) - << ':' << std::setw(2) << (exp_time.tm_sec) - << " GMT"; - - // RFC 1123 date does not use microseconds - //llinfos << "Date in RFC 1123 format is " << s << llendl; + // use strftime() as it appears to be faster than std::time_put + char buffer[128]; + strftime(buffer, 128, fmt.c_str(), gmt); + std::string res(buffer); +#if LL_WINDOWS + // Convert from locale-dependant charset to UTF-8 (EXT-8524). + res = ll_convert_string_to_utf8_string(res); +#endif + return res; } void LLDate::toStream(std::ostream& s) const @@ -159,7 +155,39 @@ void LLDate::toStream(std::ostream& s) const s << '.' << std::setw(2) << (int)(exp_time.tm_usec / (LL_APR_USEC_PER_SEC / 100)); } - s << 'Z'; + s << 'Z' + << std::setfill(' '); +} + +bool LLDate::split(S32 *year, S32 *month, S32 *day, S32 *hour, S32 *min, S32 *sec) const +{ + apr_time_t time = (apr_time_t)(mSecondsSinceEpoch * LL_APR_USEC_PER_SEC); + + apr_time_exp_t exp_time; + if (apr_time_exp_gmt(&exp_time, time) != APR_SUCCESS) + { + return false; + } + + if (year) + *year = exp_time.tm_year + 1900; + + if (month) + *month = exp_time.tm_mon + 1; + + if (day) + *day = exp_time.tm_mday; + + if (hour) + *hour = exp_time.tm_hour; + + if (min) + *min = exp_time.tm_min; + + if (sec) + *sec = exp_time.tm_sec; + + return true; } bool LLDate::fromString(const std::string& iso8601_date) @@ -223,13 +251,62 @@ bool LLDate::fromStream(std::istream& s) s >> fractional; seconds_since_epoch += fractional; } - c = s.get(); // skip the Z - if (c != 'Z') { return false; } + + c = s.peek(); // check for offset + if (c == '+' || c == '-') + { + S32 offset_sign = (c == '+') ? 1 : -1; + S32 offset_hours = 0; + S32 offset_minutes = 0; + S32 offset_in_seconds = 0; + + s >> offset_hours; + + c = s.get(); // skip the colon a get the minutes if there are any + if (c == ':') + { + s >> offset_minutes; + } + + offset_in_seconds = (offset_hours * 60 + offset_sign * offset_minutes) * 60; + seconds_since_epoch -= offset_in_seconds; + } + else if (c != 'Z') { return false; } // skip the Z mSecondsSinceEpoch = seconds_since_epoch; return true; } +bool LLDate::fromYMDHMS(S32 year, S32 month, S32 day, S32 hour, S32 min, S32 sec) +{ + struct apr_time_exp_t exp_time; + + exp_time.tm_year = year - 1900; + exp_time.tm_mon = month - 1; + exp_time.tm_mday = day; + exp_time.tm_hour = hour; + exp_time.tm_min = min; + exp_time.tm_sec = sec; + + // zero out the unused fields + exp_time.tm_usec = 0; + exp_time.tm_wday = 0; + exp_time.tm_yday = 0; + exp_time.tm_isdst = 0; + exp_time.tm_gmtoff = 0; + + // generate a time_t from that + apr_time_t time; + if (apr_time_exp_gmt_get(&time, &exp_time) != APR_SUCCESS) + { + return false; + } + + mSecondsSinceEpoch = time / LL_APR_USEC_PER_SEC; + + return true; +} + F64 LLDate::secondsSinceEpoch() const { return mSecondsSinceEpoch; diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h index d27da79ad..e116e9cdf 100644 --- a/indra/llcommon/lldate.h +++ b/indra/llcommon/lldate.h @@ -84,7 +84,9 @@ public: std::string asString() const; std::string asRFC1123() const; void toStream(std::ostream&) const; - void toHTTPDateStream(std::ostream&) const; + bool split(S32 *year, S32 *month = NULL, S32 *day = NULL, S32 *hour = NULL, S32 *min = NULL, S32 *sec = NULL) const; + std::string toHTTPDateString (std::string fmt) const; + static std::string toHTTPDateString (tm * gmt, std::string fmt); /** * @brief Set the date from an ISO-8601 string. * @@ -99,6 +101,7 @@ public: */ bool fromString(const std::string& iso8601_date); bool fromStream(std::istream&); + bool fromYMDHMS(S32 year, S32 month = 1, S32 day = 0, S32 hour = 0, S32 min = 0, S32 sec = 0); /** * @brief Return the date in seconds since epoch. diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index 898ed496b..11c4cbdf8 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -51,6 +51,9 @@ public: FTM_IDLE, FTM_SLEEP, + // general timers + FT_STRING_FORMAT, + // common messaging components FTM_PUMP, FTM_CURL, diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index f08b4c042..7423726cb 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -34,6 +34,7 @@ #include "llstring.h" #include "llerror.h" +#include "llfasttimer.h" #if LL_WINDOWS #define WIN32_LEAN_AND_MEAN @@ -71,6 +72,24 @@ U8 hex_as_nybble(char hex) return 0; // uh - oh, not hex any more... } +bool iswindividual(llwchar elem) +{ + U32 cur_char = (U32)elem; + bool result = false; + if (0x2E80<= cur_char && cur_char <= 0x9FFF) + { + result = true; + } + else if (0xAC00<= cur_char && cur_char <= 0xD7A0 ) + { + result = true; + } + else if (0xF900<= cur_char && cur_char <= 0xFA60 ) + { + result = true; + } + return result; +} bool _read_file_into_string(std::string& str, const std::string& filename) { @@ -630,14 +649,14 @@ namespace snprintf_hack } } -std::string ll_convert_wide_to_string(const wchar_t* in) +std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page) { std::string out; if(in) { int len_in = wcslen(in); int len_out = WideCharToMultiByte( - CP_ACP, + code_page, 0, in, len_in, @@ -652,7 +671,7 @@ std::string ll_convert_wide_to_string(const wchar_t* in) if(pout) { WideCharToMultiByte( - CP_ACP, + code_page, 0, in, len_in, @@ -666,8 +685,55 @@ std::string ll_convert_wide_to_string(const wchar_t* in) } return out; } + +wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page) +{ + // From review: + // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, + // plus one for a null terminator, and be guaranteed to not overflow. + + // Normally, I'd call that sort of thing premature optimization, + // but we *are* seeing string operations taking a bunch of time, especially when constructing widgets. +// int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0); + + // reserve place to NULL terminator + int output_str_len = in.length(); + wchar_t* w_out = new wchar_t[output_str_len + 1]; + + memset(w_out, 0, output_str_len + 1); + int real_output_str_len = MultiByteToWideChar (code_page, 0, in.c_str(), in.length(), w_out, output_str_len); + + //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. + w_out[real_output_str_len] = 0; + + return w_out; +} + +std::string ll_convert_string_to_utf8_string(const std::string& in) +{ + wchar_t* w_mesg = ll_convert_string_to_wide(in, CP_ACP); + std::string out_utf8(ll_convert_wide_to_string(w_mesg, CP_UTF8)); + delete[] w_mesg; + + return out_utf8; +} #endif // LL_WINDOWS +long LLStringOps::sPacificTimeOffset = 0; +long LLStringOps::sLocalTimeOffset = 0; +bool LLStringOps::sPacificDaylightTime = 0; +std::map LLStringOps::datetimeToCodes; + +std::vector LLStringOps::sWeekDayList; +std::vector LLStringOps::sWeekDayShortList; +std::vector LLStringOps::sMonthList; +std::vector LLStringOps::sMonthShortList; + + +std::string LLStringOps::sDayFormat; +std::string LLStringOps::sAM; +std::string LLStringOps::sPM; + S32 LLStringOps::collate(const llwchar* a, const llwchar* b) { #if LL_WINDOWS @@ -679,6 +745,107 @@ S32 LLStringOps::collate(const llwchar* a, const llwchar* b) #endif } +void LLStringOps::setupDatetimeInfo (bool daylight) +{ + time_t nowT, localT, gmtT; + struct tm * tmpT; + + nowT = time (NULL); + + tmpT = gmtime (&nowT); + gmtT = mktime (tmpT); + + tmpT = localtime (&nowT); + localT = mktime (tmpT); + + sLocalTimeOffset = (long) (gmtT - localT); + if (tmpT->tm_isdst) + { + sLocalTimeOffset -= 60 * 60; // 1 hour + } + + sPacificDaylightTime = daylight; + sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60; + + datetimeToCodes["wkday"] = "%a"; // Thu + datetimeToCodes["weekday"] = "%A"; // Thursday + datetimeToCodes["year4"] = "%Y"; // 2009 + datetimeToCodes["year"] = "%Y"; // 2009 + datetimeToCodes["year2"] = "%y"; // 09 + datetimeToCodes["mth"] = "%b"; // Aug + datetimeToCodes["month"] = "%B"; // August + datetimeToCodes["mthnum"] = "%m"; // 08 + datetimeToCodes["day"] = "%d"; // 31 + datetimeToCodes["sday"] = "%-d"; // 9 + datetimeToCodes["hour24"] = "%H"; // 14 + datetimeToCodes["hour"] = "%H"; // 14 + datetimeToCodes["hour12"] = "%I"; // 02 + datetimeToCodes["min"] = "%M"; // 59 + datetimeToCodes["ampm"] = "%p"; // AM + datetimeToCodes["second"] = "%S"; // 59 + datetimeToCodes["timezone"] = "%Z"; // PST +} + +void tokenizeStringToArray(const std::string& data, std::vector& output) +{ + output.clear(); + size_t length = data.size(); + + // tokenize it and put it in the array + std::string cur_word; + for(size_t i = 0; i < length; ++i) + { + if(data[i] == ':') + { + output.push_back(cur_word); + cur_word.clear(); + } + else + { + cur_word.append(1, data[i]); + } + } + output.push_back(cur_word); +} + +void LLStringOps::setupWeekDaysNames(const std::string& data) +{ + tokenizeStringToArray(data,sWeekDayList); +} +void LLStringOps::setupWeekDaysShortNames(const std::string& data) +{ + tokenizeStringToArray(data,sWeekDayShortList); +} +void LLStringOps::setupMonthNames(const std::string& data) +{ + tokenizeStringToArray(data,sMonthList); +} +void LLStringOps::setupMonthShortNames(const std::string& data) +{ + tokenizeStringToArray(data,sMonthShortList); +} +void LLStringOps::setupDayFormat(const std::string& data) +{ + sDayFormat = data; +} + + +std::string LLStringOps::getDatetimeCode (std::string key) +{ + std::map::iterator iter; + + iter = datetimeToCodes.find (key); + if (iter != datetimeToCodes.end()) + { + return iter->second; + } + else + { + return std::string(""); + } +} + + namespace LLStringFn { // NOTE - this restricts output to ascii @@ -715,12 +882,12 @@ namespace LLStringFn // https://wiki.lindenlab.com/wiki/Unicode_Guidelines has details on // allowable code points for XML. Specifically, they are: // 0x09, 0x0a, 0x0d, and 0x20 on up. JC - std::string strip_invalid_xml(const std::string& input) + std::string strip_invalid_xml(const std::string& instr) { std::string output; - output.reserve( input.size() ); - std::string::const_iterator it = input.begin(); - while (it != input.end()) + output.reserve( instr.size() ); + std::string::const_iterator it = instr.begin(); + while (it != instr.end()) { // Must compare as unsigned for >= // Test most likely match first @@ -756,6 +923,412 @@ namespace LLStringFn } } +//////////////////////////////////////////////////////////// + +// Forward specialization of LLStringUtil::format before use in LLStringUtil::formatDatetime. +template<> +S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions); + +//static +template<> +void LLStringUtil::getTokens(const std::string& instr, std::vector& tokens, const std::string& delims) +{ + std::string currToken; + std::string::size_type begIdx, endIdx; + + begIdx = instr.find_first_not_of (delims); + while (begIdx != std::string::npos) + { + endIdx = instr.find_first_of (delims, begIdx); + if (endIdx == std::string::npos) + { + endIdx = instr.length(); + } + + currToken = instr.substr(begIdx, endIdx - begIdx); + LLStringUtil::trim (currToken); + tokens.push_back(currToken); + begIdx = instr.find_first_not_of (delims, endIdx); + } +} + +template<> +LLStringUtil::size_type LLStringUtil::getSubstitution(const std::string& instr, size_type& start, std::vector& tokens) +{ + const std::string delims (","); + + // Find the first ] + size_type pos2 = instr.find(']', start); + if (pos2 == std::string::npos) + return std::string::npos; + + // Find the last [ before ] + size_type pos1 = instr.find_last_of('[', pos2-1); + if (pos1 == std::string::npos || pos1 < start) + return std::string::npos; + + getTokens(std::string(instr,pos1+1,pos2-pos1-1), tokens, delims); + start = pos2+1; + + return pos1; +} + +// static +template<> +bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const format_map_t& substitutions) +{ + // see if we have a replacement for the bracketed string (without the brackets) + // test first using has() because if we just look up with operator[] we get back an + // empty string even if the value is missing. We want to distinguish between + // missing replacements and deliberately empty replacement strings. + format_map_t::const_iterator iter = substitutions.find(token); + if (iter != substitutions.end()) + { + replacement = iter->second; + return true; + } + // if not, see if there's one WITH brackets + iter = substitutions.find(std::string("[" + token + "]")); + if (iter != substitutions.end()) + { + replacement = iter->second; + return true; + } + + return false; +} + +// static +template<> +bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const LLSD& substitutions) +{ + // see if we have a replacement for the bracketed string (without the brackets) + // test first using has() because if we just look up with operator[] we get back an + // empty string even if the value is missing. We want to distinguish between + // missing replacements and deliberately empty replacement strings. + if (substitutions.has(token)) + { + replacement = substitutions[token].asString(); + return true; + } + // if not, see if there's one WITH brackets + else if (substitutions.has(std::string("[" + token + "]"))) + { + replacement = substitutions[std::string("[" + token + "]")].asString(); + return true; + } + + return false; +} + +//static +template<> +void LLStringUtil::setLocale(std::string inLocale) +{ + sLocale = inLocale; +}; + +//static +template<> +std::string LLStringUtil::getLocale(void) +{ + return sLocale; +}; + +// static +template<> +void LLStringUtil::formatNumber(std::string& numStr, std::string decimals) +{ + std::stringstream strStream; + S32 intDecimals = 0; + + convertToS32 (decimals, intDecimals); + if (!sLocale.empty()) + { + // std::locale() throws if the locale is unknown! (EXT-7926) + try + { + strStream.imbue(std::locale(sLocale.c_str())); + } catch (const std::exception &) + { + LL_WARNS_ONCE("Locale") << "Cannot set locale to " << sLocale << LL_ENDL; + } + } + + if (!intDecimals) + { + S32 intStr; + + if (convertToS32(numStr, intStr)) + { + strStream << intStr; + numStr = strStream.str(); + } + } + else + { + F32 floatStr; + + if (convertToF32(numStr, floatStr)) + { + strStream << std::fixed << std::showpoint << std::setprecision(intDecimals) << floatStr; + numStr = strStream.str(); + } + } +} + +// static +template<> +bool LLStringUtil::formatDatetime(std::string& replacement, std::string token, + std::string param, S32 secFromEpoch) +{ + if (param == "local") // local + { + secFromEpoch -= LLStringOps::getLocalTimeOffset(); + } + else if (param != "utc") // slt + { + secFromEpoch -= LLStringOps::getPacificTimeOffset(); + } + + // if never fell into those two ifs above, param must be utc + if (secFromEpoch < 0) secFromEpoch = 0; + + LLDate datetime((F64)secFromEpoch); + std::string code = LLStringOps::getDatetimeCode (token); + + // special case to handle timezone + if (code == "%Z") { + if (param == "utc") + { + replacement = "GMT"; + } + else if (param == "local") + { + replacement = ""; // user knows their own timezone + } + else + { + // "slt" = Second Life Time, which is deprecated. + // If not utc or user local time, fallback to Pacific time + replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST"; + } + return true; + } + + //EXT-7013 + //few codes are not suppotred by strtime function (example - weekdays for Japanise) + //so use predefined ones + + //if sWeekDayList is not empty than current locale doesn't support + //weekday name. + time_t loc_seconds = (time_t) secFromEpoch; + if(LLStringOps::sWeekDayList.size() == 7 && code == "%A") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayList[gmt->tm_wday]; + } + else if(LLStringOps::sWeekDayShortList.size() == 7 && code == "%a") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayShortList[gmt->tm_wday]; + } + else if(LLStringOps::sMonthList.size() == 12 && code == "%B") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sMonthList[gmt->tm_mon]; + } + else if( !LLStringOps::sDayFormat.empty() && code == "%d" ) + { + struct tm * gmt = gmtime (&loc_seconds); + LLStringUtil::format_map_t args; + args["[MDAY]"] = llformat ("%d", gmt->tm_mday); + replacement = LLStringOps::sDayFormat; + LLStringUtil::format(replacement, args); + } + else if (code == "%-d") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = llformat ("%d", gmt->tm_mday); // day of the month without leading zero + } + else if( !LLStringOps::sAM.empty() && !LLStringOps::sPM.empty() && code == "%p" ) + { + struct tm * gmt = gmtime (&loc_seconds); + if(gmt->tm_hour<12) + { + replacement = LLStringOps::sAM; + } + else + { + replacement = LLStringOps::sPM; + } + } + else + { + replacement = datetime.toHTTPDateString(code); + } + + // *HACK: delete leading zero from hour string in case 'hour12' (code = %I) time format + // to show time without leading zero, e.g. 08:16 -> 8:16 (EXT-2738). + // We could have used '%l' format instead, but it's not supported by Windows. + if(code == "%I" && token == "hour12" && replacement.at(0) == '0') + { + replacement = replacement.at(1); + } + + return !code.empty(); +} + +// LLStringUtil::format recogizes the following patterns. +// All substitutions *must* be encased in []'s in the input string. +// The []'s are optional in the substitution map. +// [FOO_123] +// [FOO,number,precision] +// [FOO,datetime,format] + + +// static +template<> +S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) +{ + LLFastTimer ft(LLFastTimer::FT_STRING_FORMAT); + S32 res = 0; + + std::string output; + std::vector tokens; + + std::string::size_type start = 0; + std::string::size_type prev_start = 0; + std::string::size_type key_start = 0; + while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) + { + output += std::string(s, prev_start, key_start-prev_start); + prev_start = start; + + bool found_replacement = false; + std::string replacement; + + if (tokens.size() == 0) + { + found_replacement = false; + } + else if (tokens.size() == 1) + { + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + } + else if (tokens[1] == "number") + { + std::string param = "0"; + + if (tokens.size() > 2) param = tokens[2]; + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + if (found_replacement) formatNumber (replacement, param); + } + else if (tokens[1] == "datetime") + { + std::string param; + if (tokens.size() > 2) param = tokens[2]; + + format_map_t::const_iterator iter = substitutions.find("datetime"); + if (iter != substitutions.end()) + { + S32 secFromEpoch = 0; + BOOL r = LLStringUtil::convertToS32(iter->second, secFromEpoch); + if (r) + { + found_replacement = formatDatetime(replacement, tokens[0], param, secFromEpoch); + } + } + } + + if (found_replacement) + { + output += replacement; + res++; + } + else + { + // we had no replacement, use the string as is + // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" + output += std::string(s, key_start, start-key_start); + } + tokens.clear(); + } + // send the remainder of the string (with no further matches for bracketed names) + output += std::string(s, start); + s = output; + return res; +} + +//static +template<> +S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) +{ + LLFastTimer ft(LLFastTimer::FT_STRING_FORMAT); + S32 res = 0; + + if (!substitutions.isMap()) + { + return res; + } + + std::string output; + std::vector tokens; + + std::string::size_type start = 0; + std::string::size_type prev_start = 0; + std::string::size_type key_start = 0; + while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) + { + output += std::string(s, prev_start, key_start-prev_start); + prev_start = start; + + bool found_replacement = false; + std::string replacement; + + if (tokens.size() == 0) + { + found_replacement = false; + } + else if (tokens.size() == 1) + { + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + } + else if (tokens[1] == "number") + { + std::string param = "0"; + + if (tokens.size() > 2) param = tokens[2]; + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + if (found_replacement) formatNumber (replacement, param); + } + else if (tokens[1] == "datetime") + { + std::string param; + if (tokens.size() > 2) param = tokens[2]; + + S32 secFromEpoch = (S32) substitutions["datetime"].asInteger(); + found_replacement = formatDatetime (replacement, tokens[0], param, secFromEpoch); + } + + if (found_replacement) + { + output += replacement; + res++; + } + else + { + // we had no replacement, use the string as is + // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" + output += std::string(s, key_start, start-key_start); + } + tokens.clear(); + } + // send the remainder of the string (with no further matches for bracketed names) + output += std::string(s, start); + s = output; + return res; +} //////////////////////////////////////////////////////////// // Testing diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 171747921..bdbefe935 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -37,6 +37,9 @@ #include #include #include +#include +#include +#include "llsd.h" #if LL_LINUX || LL_SOLARIS #include @@ -148,7 +151,23 @@ struct char_traits class LL_COMMON_API LLStringOps { +private: + static long sPacificTimeOffset; + static long sLocalTimeOffset; + static bool sPacificDaylightTime; + + static std::map datetimeToCodes; + public: + static std::vector sWeekDayList; + static std::vector sWeekDayShortList; + static std::vector sMonthList; + static std::vector sMonthShortList; + static std::string sDayFormat; + + static std::string sAM; + static std::string sPM; + static char toUpper(char elem) { return toupper((unsigned char)elem); } static llwchar toUpper(llwchar elem) { return towupper(elem); } @@ -177,14 +196,31 @@ public: static S32 collate(const llwchar* a, const llwchar* b); static bool isHexString(const std::string& str); + + static void setupDatetimeInfo(bool pacific_daylight_time); + + static void setupWeekDaysNames(const std::string& data); + static void setupWeekDaysShortNames(const std::string& data); + static void setupMonthNames(const std::string& data); + static void setupMonthShortNames(const std::string& data); + static void setupDayFormat(const std::string& data); + + + static long getPacificTimeOffset(void) { return sPacificTimeOffset;} + static long getLocalTimeOffset(void) { return sLocalTimeOffset;} + // Is the Pacific time zone (aka server time zone) + // currently in daylight savings time? + static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} + + static std::string getDatetimeCode (std::string key); }; /** * @brief Return a string constructed from in without crashing if the * pointer is NULL. */ -std::string LL_COMMON_API ll_safe_string(const char* in); -std::string LL_COMMON_API ll_safe_string(const char* in, S32 maxlen); +LL_COMMON_API std::string ll_safe_string(const char* in); +LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen); // Allowing assignments from non-strings into format_map_t is apparently @@ -206,6 +242,9 @@ private: template class LLStringUtilBase { +private: + static std::string sLocale; + public: typedef typename std::basic_string::size_type size_type; @@ -213,10 +252,18 @@ public: ///////////////////////////////////////////////////////////////////////////////////////// // Static Utility functions that operate on std::strings - static std::basic_string const null; + static const std::basic_string null; typedef std::map format_map_t; - static S32 format(std::basic_string& s, const format_map_t& fmt_map); + LL_COMMON_API static void getTokens(const std::basic_string& instr, std::vector >& tokens, const std::basic_string& delims); + LL_COMMON_API static void formatNumber(std::basic_string& numStr, std::basic_string decimals); + LL_COMMON_API static bool formatDatetime(std::basic_string& replacement, std::basic_string token, std::basic_string param, S32 secFromEpoch); + LL_COMMON_API static S32 format(std::basic_string& s, const format_map_t& substitutions); + LL_COMMON_API static S32 format(std::basic_string& s, const LLSD& substitutions); + LL_COMMON_API static bool simpleReplacement(std::basic_string& replacement, std::basic_string token, const format_map_t& substitutions); + LL_COMMON_API static bool simpleReplacement(std::basic_string& replacement, std::basic_string token, const LLSD& substitutions); + LL_COMMON_API static void setLocale (std::string inLocale); + LL_COMMON_API static std::string getLocale (void); static bool isValidIndex(const std::basic_string& string, size_type i) { @@ -233,7 +280,25 @@ public: // True if this is the head of s. static BOOL isHead( const std::basic_string& string, const T* s ); - + + /** + * @brief Returns true if string starts with substr + * + * If etither string or substr are empty, this method returns false. + */ + static bool startsWith( + const std::basic_string& string, + const std::basic_string& substr); + + /** + * @brief Returns true if string ends in substr + * + * If etither string or substr are empty, this method returns false. + */ + static bool endsWith( + const std::basic_string& string, + const std::basic_string& substr); + static void addCRLF(std::basic_string& string); static void removeCRLF(std::basic_string& string); @@ -298,13 +363,19 @@ public: // Copies src into dst at a given offset. static void copyInto(std::basic_string& dst, const std::basic_string& src, size_type offset); + static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); } + + #ifdef _DEBUG - static void testHarness(); + LL_COMMON_API static void testHarness(); #endif +private: + LL_COMMON_API static size_type getSubstitution(const std::basic_string& instr, size_type& start, std::vector >& tokens); }; -template std::basic_string const LLStringUtilBase::null; +template const std::basic_string LLStringUtilBase::null; +template std::string LLStringUtilBase::sLocale; typedef LLStringUtilBase LLStringUtil; typedef LLStringUtilBase LLWStringUtil; @@ -366,6 +437,7 @@ LL_COMMON_API U8 hex_as_nybble(char hex); * @return Returns true on success. If false, str is unmodified. */ LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename); +LL_COMMON_API bool iswindividual(llwchar elem); /** * Unicode support @@ -495,7 +567,20 @@ using snprintf_hack::snprintf; * * This replaces the unsafe W2A macro from ATL. */ -LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in); +LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page); + +/** + * Converts a string to wide string. + * + * It will allocate memory for result string with "new []". Don't forget to release it with "delete []". + */ +LL_COMMON_API wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page); + +/** + * Converts incoming string into urf8 string + * + */ +LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in); //@} #endif // LL_WINDOWS @@ -558,63 +643,12 @@ namespace LLStringFn } //////////////////////////////////////////////////////////// +// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp. +// There is no LLWStringUtil::format implementation currently. +// Calling thse for anything other than LLStringUtil will produce link errors. -// LLStringBase::format() -// -// This function takes a string 's' and a map 'fmt_map' of strings-to-strings. -// All occurances of strings in 's' from the left-hand side of 'fmt_map' are -// then replaced with the corresponding right-hand side of 'fmt_map', non- -// recursively. The function returns the number of substitutions made. +//////////////////////////////////////////////////////////// -// static -template -S32 LLStringUtilBase::format(std::basic_string& s, const format_map_t& fmt_map) -{ - typedef typename std::basic_string::size_type string_size_type_t; - string_size_type_t scanstart = 0; - S32 res = 0; - - // Look for the first match of any keyword, replace that keyword, - // repeat from the end of the replacement string. This avoids - // accidentally performing substitution on a substituted string. - while (1) - { - string_size_type_t first_match_pos = scanstart; - string_size_type_t first_match_str_length = 0; - std::basic_string first_match_str_replacement; - - for (format_map_t::const_iterator iter = fmt_map.begin(); - iter != fmt_map.end(); - ++iter) - { - string_size_type_t n = s.find(iter->first, scanstart); - if (n != std::basic_string::npos && - (n < first_match_pos || - 0 == first_match_str_length)) - { - first_match_pos = n; - first_match_str_length = iter->first.length(); - first_match_str_replacement = iter->second; - } - } - - if (0 == first_match_str_length) - { - // no more keys found to substitute from this point - // in the string forward. - break; - } - else - { - s.erase(first_match_pos, first_match_str_length); - s.insert(first_match_pos, first_match_str_replacement); - scanstart = first_match_pos + - first_match_str_replacement.length(); - ++res; - } - } - return res; -} // static template @@ -1003,14 +1037,15 @@ void LLStringUtilBase::stripNonprintable(std::basic_string& string) { return; } - char* c_string = new char[string.size() + 1]; + size_t src_size = string.size(); + char* c_string = new char[src_size + 1]; if(c_string == NULL) { return; } - strcpy(c_string, string.c_str()); /*Flawfinder: ignore*/ + copy(c_string, string.c_str(), src_size+1); char* write_head = &c_string[0]; - for (size_type i = 0; i < string.size(); i++) + for (size_type i = 0; i < src_size; i++) { char* read_head = &string[i]; write_head = &c_string[j]; @@ -1090,6 +1125,30 @@ BOOL LLStringUtilBase::isHead( const std::basic_string& string, const T* s } } +// static +template +bool LLStringUtilBase::startsWith( + const std::basic_string& string, + const std::basic_string& substr) +{ + if(string.empty() || (substr.empty())) return false; + if(0 == string.find(substr)) return true; + return false; +} + +// static +template +bool LLStringUtilBase::endsWith( + const std::basic_string& string, + const std::basic_string& substr) +{ + if(string.empty() || (substr.empty())) return false; + std::string::size_type idx = string.rfind(substr); + if(std::string::npos == idx) return false; + return (idx == (string.size() - substr.size())); +} + + template BOOL LLStringUtilBase::convertToBOOL(const std::basic_string& string, BOOL& value) { diff --git a/indra/newview/llfasttimerview.cpp b/indra/newview/llfasttimerview.cpp index 27661567e..27f916327 100644 --- a/indra/newview/llfasttimerview.cpp +++ b/indra/newview/llfasttimerview.cpp @@ -81,6 +81,7 @@ static struct ft_display_info ft_display_table[] = { LLFastTimer::FTM_MESSAGES, " System Messages", &LLColor4::grey1, 1 }, { LLFastTimer::FTM_MOUSEHANDLER, " Mouse", &LLColor4::grey1, 0 }, { LLFastTimer::FTM_KEYHANDLER, " Keyboard", &LLColor4::grey1, 0 }, + { LLFastTimer::FT_STRING_FORMAT, " String Format", &LLColor4::grey3, 0 }, { LLFastTimer::FTM_SLEEP, " Sleep", &LLColor4::grey2, 0 }, { LLFastTimer::FTM_IDLE, " Idle", &blue0, 0 }, { LLFastTimer::FTM_PUMP, " Pump", &LLColor4::magenta2, 1 }, diff --git a/indra/newview/llwindebug.cpp b/indra/newview/llwindebug.cpp index bc8281480..4d175a071 100644 --- a/indra/newview/llwindebug.cpp +++ b/indra/newview/llwindebug.cpp @@ -429,7 +429,7 @@ void WINAPI Get_Call_Stack(const EXCEPTION_RECORD* exception_record, if (Get_Module_By_Ret_Addr(Ebp->Ret_Addr, Module_Name, Module_Addr)) { // Save module's address and full path. - tmp_info["CallStack"][Ret_Addr_I]["ModuleName"] = ll_convert_wide_to_string(Module_Name); + tmp_info["CallStack"][Ret_Addr_I]["ModuleName"] = ll_convert_wide_to_string(Module_Name,CP_ACP); tmp_info["CallStack"][Ret_Addr_I]["ModuleAddress"] = (int)Module_Addr; tmp_info["CallStack"][Ret_Addr_I]["CallOffset"] = (int)(Ebp->Ret_Addr - Module_Addr); @@ -548,7 +548,7 @@ LLSD WINAPI Get_Exception_Info(PEXCEPTION_POINTERS pException) Get_Version_Str(info); GetModuleFileName(NULL, Str, MAX_PATH); - info["Process"] = ll_convert_wide_to_string(Str); + info["Process"] = ll_convert_wide_to_string(Str,CP_ACP); info["ThreadID"] = (S32)GetCurrentThreadId(); // If exception occurred. @@ -560,7 +560,7 @@ LLSD WINAPI Get_Exception_Info(PEXCEPTION_POINTERS pException) // If module with E.ExceptionAddress found - save its path and date. if (Get_Module_By_Ret_Addr((PBYTE)E.ExceptionAddress, Module_Name, Module_Addr)) { - info["Module"] = ll_convert_wide_to_string(Module_Name); + info["Module"] = ll_convert_wide_to_string(Module_Name,CP_ACP); if ((hFile = CreateFile(Module_Name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE) diff --git a/indra/win_crash_logger/llcrashloggerwindows.cpp b/indra/win_crash_logger/llcrashloggerwindows.cpp index c9e01c841..1a233fe2f 100644 --- a/indra/win_crash_logger/llcrashloggerwindows.cpp +++ b/indra/win_crash_logger/llcrashloggerwindows.cpp @@ -145,7 +145,7 @@ void LLCrashLoggerWindows::ProcessCaption(HWND hWnd) TCHAR header[MAX_STRING]; std::string final; GetWindowText(hWnd, templateText, sizeof(templateText)); - final = llformat(ll_convert_wide_to_string(templateText).c_str(), gProductName.c_str()); + final = llformat(ll_convert_wide_to_string(templateText,CP_ACP).c_str(), gProductName.c_str()); ConvertLPCSTRToLPWSTR(final.c_str(), header); SetWindowText(hWnd, header); } @@ -158,7 +158,7 @@ void LLCrashLoggerWindows::ProcessDlgItemText(HWND hWnd, int nIDDlgItem) TCHAR header[MAX_STRING]; std::string final; GetDlgItemText(hWnd, nIDDlgItem, templateText, sizeof(templateText)); - final = llformat(ll_convert_wide_to_string(templateText).c_str(), gProductName.c_str()); + final = llformat(ll_convert_wide_to_string(templateText,CP_ACP).c_str(), gProductName.c_str()); ConvertLPCSTRToLPWSTR(final.c_str(), header); SetDlgItemText(hWnd, nIDDlgItem, header); } @@ -201,7 +201,7 @@ bool handle_button_click(WORD button_id) wbuffer, // pointer to buffer for text 20000 // maximum size of string ); - std::string user_text(ll_convert_wide_to_string(wbuffer)); + std::string user_text(ll_convert_wide_to_string(wbuffer,CP_ACP)); // Activate and show the window. ShowWindow(gHwndProgress, SW_SHOW); // Try doing this second to make the progress window go frontmost.