#include <config.h>
#include <string>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <regex>
#ifdef WIN32
#define NOMINMAX
#include <windows.h>
#undef NOMINMAX
#else
#include <unistd.h>
#endif
#include <xercesc/util/TransService.hpp>
#include <xercesc/util/TranscodingException.hpp>
#include <utils/common/UtilExceptions.h>
#include <utils/common/ToString.h>
#include <utils/common/StringTokenizer.h>
#include "StringUtils.h"
#define KM_PER_MILE 1.609344
std::string StringUtils::emptyString;
XERCES_CPP_NAMESPACE::XMLLCPTranscoder* StringUtils::myLCPTranscoder = nullptr;
std::string
StringUtils::prune(const std::string& str) {
const std::string::size_type endpos = str.find_last_not_of(" \t\n\r");
if (std::string::npos != endpos) {
const int startpos = (int)str.find_first_not_of(" \t\n\r");
return str.substr(startpos, endpos - startpos + 1);
}
return "";
}
std::string
StringUtils::pruneZeros(const std::string& str, int max) {
const std::string::size_type endpos = str.find_last_not_of("0");
if (endpos != std::string::npos && str.back() == '0') {
std::string res = str.substr(0, MAX2((int)str.size() - max, (int)endpos + 1));
return res;
}
return str;
}
std::string
StringUtils::to_lower_case(const std::string& str) {
std::string s = str;
std::transform(s.begin(), s.end(), s.begin(), [](char c) {
return (char)::tolower(c);
});
return s;
}
std::string
StringUtils::latin1_to_utf8(std::string str) {
std::string result;
for (const auto& c : str) {
const unsigned char uc = (unsigned char)c;
if (uc < 128) {
result += uc;
} else {
result += (char)(0xc2 + (uc > 0xbf));
result += (char)((uc & 0x3f) + 0x80);
}
}
return result;
}
std::string
StringUtils::convertUmlaute(std::string str) {
str = replace(str, "\xE4", "ae");
str = replace(str, "\xC4", "Ae");
str = replace(str, "\xF6", "oe");
str = replace(str, "\xD6", "Oe");
str = replace(str, "\xFC", "ue");
str = replace(str, "\xDC", "Ue");
str = replace(str, "\xDF", "ss");
str = replace(str, "\xC9", "E");
str = replace(str, "\xE9", "e");
str = replace(str, "\xC8", "E");
str = replace(str, "\xE8", "e");
return str;
}
std::string
StringUtils::replace(std::string str, const std::string& what, const std::string& by) {
std::string::size_type idx = str.find(what);
const int what_len = (int)what.length();
if (what_len > 0) {
const int by_len = (int)by.length();
while (idx != std::string::npos) {
str = str.replace(idx, what_len, by);
idx = str.find(what, idx + by_len);
}
}
return str;
}
std::string
StringUtils::substituteEnvironment(const std::string& str, const std::chrono::time_point<std::chrono::system_clock>* const timeRef) {
std::string s = str;
if (timeRef != nullptr) {
const std::string::size_type localTimeIndex = str.find("${LOCALTIME}");
const std::string::size_type utcIndex = str.find("${UTC}");
const bool isUTC = utcIndex != std::string::npos;
if (localTimeIndex != std::string::npos || isUTC) {
const time_t rawtime = std::chrono::system_clock::to_time_t(*timeRef);
char buffer [80];
struct tm* timeinfo = isUTC ? gmtime(&rawtime) : localtime(&rawtime);
strftime(buffer, 80, "%Y-%m-%d-%H-%M-%S.", timeinfo);
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(*timeRef);
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(*timeRef - seconds);
const std::string micro = buffer + toString(microseconds.count());
if (isUTC) {
s.replace(utcIndex, 6, micro);
} else {
s.replace(localTimeIndex, 12, micro);
}
}
}
const std::string::size_type pidIndex = str.find("${PID}");
if (pidIndex != std::string::npos) {
#ifdef WIN32
s.replace(pidIndex, 6, toString(::GetCurrentProcessId()));
#else
s.replace(pidIndex, 6, toString(::getpid()));
#endif
}
if (std::getenv("SUMO_LOGO") == nullptr) {
s = replace(s, "${SUMO_LOGO}", "${SUMO_HOME}/data/logo/sumo-128x138.png");
}
const std::string::size_type tildeIndex = str.find("~");
if (tildeIndex == 0) {
s.replace(0, 1, "${HOME}");
}
s = replace(s, ",~", ",${HOME}");
#ifdef WIN32
if (std::getenv("HOME") == nullptr) {
s = replace(s, "${HOME}", "${USERPROFILE}");
}
#endif
std::regex envVarExpr(R"(\$\{(.+?)\})");
std::smatch match;
std::string strIter = s;
while (std::regex_search(strIter, match, envVarExpr)) {
std::string varName = match[1];
std::string varValue;
if (std::getenv(varName.c_str()) != nullptr) {
varValue = std::getenv(varName.c_str());
}
s = std::regex_replace(s, std::regex("\\$\\{" + varName + "\\}"), varValue);
strIter = match.suffix();
}
return s;
}
std::string
StringUtils::isoTimeString(const std::chrono::time_point<std::chrono::system_clock>* const timeRef) {
const std::chrono::system_clock::time_point now = timeRef == nullptr ? std::chrono::system_clock::now() : *timeRef;
const auto now_seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
const std::time_t now_c = std::chrono::system_clock::to_time_t(now);
const auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - now_seconds).count();
std::tm local_tm = *std::localtime(&now_c);
std::time_t utc_time = std::time(nullptr);
std::tm utc_tm = *std::gmtime(&utc_time);
const double offset = std::difftime(std::mktime(&local_tm), std::mktime(&utc_tm)) / 3600.0;
const int hours_offset = static_cast<int>(offset);
const int minutes_offset = static_cast<int>((offset - hours_offset) * 60);
std::ostringstream oss;
oss << std::put_time(&local_tm, "%Y-%m-%dT%H:%M:%S") << "."
<< std::setw(6) << std::setfill('0') << std::abs(microseconds)
<< (hours_offset >= 0 ? "+" : "-")
<< std::setw(2) << std::setfill('0') << std::abs(hours_offset) << ":"
<< std::setw(2) << std::setfill('0') << std::abs(minutes_offset);
return oss.str();
}
bool
StringUtils::startsWith(const std::string& str, const std::string prefix) {
return str.compare(0, prefix.length(), prefix) == 0;
}
bool
StringUtils::endsWith(const std::string& str, const std::string suffix) {
if (str.length() >= suffix.length()) {
return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
} else {
return false;
}
}
std::string
StringUtils::padFront(const std::string& str, int length, char padding) {
return std::string(MAX2(0, length - (int)str.size()), padding) + str;
}
std::string
StringUtils::escapeXML(const std::string& orig, const bool maskDoubleHyphen) {
std::string result = replace(orig, "&", "&");
result = replace(result, ">", ">");
result = replace(result, "<", "<");
result = replace(result, "\"", """);
if (maskDoubleHyphen) {
result = replace(result, "--", "--");
}
for (char invalid = '\1'; invalid < ' '; invalid++) {
result = replace(result, std::string(1, invalid).c_str(), "");
}
return replace(result, "'", "'");
}
std::string
StringUtils::escapeShell(const std::string& orig) {
std::string result = replace(orig, "\"", "\\\"");
return result;
}
std::string
StringUtils::urlEncode(const std::string& toEncode, const std::string encodeWhich) {
std::ostringstream out;
for (int i = 0; i < (int)toEncode.length(); ++i) {
const char t = toEncode.at(i);
if ((encodeWhich != "" && encodeWhich.find(t) == std::string::npos) ||
(encodeWhich == "" &&
((t >= 45 && t <= 57) ||
(t >= 65 && t <= 90) ||
t == 95 ||
(t >= 97 && t <= 122) ||
t == 126))
) {
out << toEncode.at(i);
} else {
out << charToHex(toEncode.at(i));
}
}
return out.str();
}
std::string
StringUtils::urlDecode(const std::string& toDecode) {
std::ostringstream out;
for (int i = 0; i < (int)toDecode.length(); ++i) {
if (toDecode.at(i) == '%') {
std::string str(toDecode.substr(i + 1, 2));
out << hexToChar(str);
i += 2;
} else {
out << toDecode.at(i);
}
}
return out.str();
}
std::string
StringUtils::charToHex(unsigned char c) {
short i = c;
std::stringstream s;
s << "%" << std::setw(2) << std::setfill('0') << std::hex << i;
return s.str();
}
unsigned char
StringUtils::hexToChar(const std::string& str) {
short c = 0;
if (!str.empty()) {
std::istringstream in(str);
in >> std::hex >> c;
if (in.fail()) {
throw NumberFormatException(str + " could not be interpreted as hex");
}
}
return static_cast<unsigned char>(c);
}
int
StringUtils::toInt(const std::string& sData) {
long long int result = toLong(sData);
if (result > std::numeric_limits<int>::max() || result < std::numeric_limits<int>::min()) {
throw NumberFormatException(toString(result) + " int overflow");
}
return (int)result;
}
bool
StringUtils::isInt(const std::string& sData) {
if (isLong(sData)) {
const long long int result = toLong(sData);
return ((result <= std::numeric_limits<int>::max()) && (result >= std::numeric_limits<int>::min()));
}
return false;
}
int
StringUtils::toIntSecure(const std::string& sData, int def) {
if (sData.length() == 0) {
return def;
}
return toInt(sData);
}
long long int
StringUtils::toLong(const std::string& sData) {
const char* const data = sData.c_str();
if (data == 0 || data[0] == 0) {
throw EmptyData();
}
char* end;
errno = 0;
#ifdef _MSC_VER
long long int ret = _strtoi64(data, &end, 10);
#else
long long int ret = strtoll(data, &end, 10);
#endif
if (errno == ERANGE) {
errno = 0;
throw NumberFormatException("(long long integer range) " + sData);
}
if ((int)(end - data) != (int)strlen(data)) {
throw NumberFormatException("(long long integer format) " + sData);
}
return ret;
}
bool
StringUtils::isLong(const std::string& sData) {
const char* const data = sData.c_str();
if (data == 0 || data[0] == 0) {
return false;
}
char* end;
errno = 0;
#ifdef _MSC_VER
_strtoi64(data, &end, 10);
#else
strtoll(data, &end, 10);
#endif
if (errno == ERANGE) {
return false;
}
if ((int)(end - data) != (int)strlen(data)) {
return false;
}
return true;
}
int
StringUtils::hexToInt(const std::string& sData) {
if (sData.length() == 0) {
throw EmptyData();
}
size_t idx = 0;
int result;
try {
if (sData[0] == '#') {
result = std::stoi(sData.substr(1), &idx, 16);
idx++;
} else {
result = std::stoi(sData, &idx, 16);
}
} catch (...) {
throw NumberFormatException("(hex integer format) " + sData);
}
if (idx != sData.length()) {
throw NumberFormatException("(hex integer format) " + sData);
}
return result;
}
bool
StringUtils::isHex(std::string sData) {
if (sData.length() == 0) {
return false;
}
if (sData[0] == '#') {
sData = sData.substr(1);
}
const char* sDataPtr = sData.c_str();
char* returnPtr;
errno = 0;
strtol(sDataPtr, &returnPtr, 16);
if (errno == ERANGE) {
return false;
}
if (sDataPtr == returnPtr) {
return false;
}
if (static_cast<size_t>(returnPtr - sDataPtr) != sData.size()) {
return false;
}
return true;
}
double
StringUtils::toDouble(const std::string& sData) {
if (sData.size() == 0) {
throw EmptyData();
}
try {
size_t idx = 0;
const double result = std::stod(sData, &idx);
if (idx != sData.size()) {
throw NumberFormatException("(double format) " + sData);
} else {
return result;
}
} catch (...) {
throw NumberFormatException("(double) " + sData);
}
}
bool
StringUtils::isDouble(const std::string& sData) {
if (sData.size() == 0) {
return false;
}
const char* sDataPtr = sData.c_str();
char* returnPtr;
errno = 0;
strtod(sDataPtr, &returnPtr);
if (errno == ERANGE) {
return false;
}
if (sDataPtr == returnPtr) {
return false;
}
if (static_cast<size_t>(returnPtr - sDataPtr) != sData.size()) {
return false;
}
return true;
}
double
StringUtils::toDoubleSecure(const std::string& sData, const double def) {
if (sData.length() == 0) {
return def;
}
return toDouble(sData);
}
bool
StringUtils::toBool(const std::string& sData) {
if (sData.length() == 0) {
throw EmptyData();
}
const std::string s = to_lower_case(sData);
if (s == "1" || s == "yes" || s == "true" || s == "on" || s == "x" || s == "t") {
return true;
}
if (s == "0" || s == "no" || s == "false" || s == "off" || s == "-" || s == "f") {
return false;
}
throw BoolFormatException(s);
}
bool
StringUtils::isBool(const std::string& sData) {
if (sData.length() == 0) {
return false;
}
const std::string s = to_lower_case(sData);
if (s == "1" || s == "yes" || s == "true" || s == "on" || s == "x" || s == "t") {
return true;
}
if (s == "0" || s == "no" || s == "false" || s == "off" || s == "-" || s == "f") {
return true;
}
return false;
}
MMVersion
StringUtils::toVersion(const std::string& sData) {
std::vector<std::string> parts = StringTokenizer(sData, ".").getVector();
return MMVersion(toInt(parts.front()), toDouble(parts.back()));
}
double
StringUtils::parseDist(const std::string& sData) {
if (sData.size() == 0) {
throw EmptyData();
}
try {
size_t idx = 0;
const double result = std::stod(sData, &idx);
if (idx != sData.size()) {
const std::string unit = prune(sData.substr(idx));
if (unit == "m" || unit == "metre" || unit == "meter" || unit == "metres" || unit == "meters") {
return result;
}
if (unit == "km" || unit == "kilometre" || unit == "kilometer" || unit == "kilometres" || unit == "kilometers") {
return result * 1000.;
}
if (unit == "mi" || unit == "mile" || unit == "miles") {
return result * 1000. * KM_PER_MILE;
}
if (unit == "nmi") {
return result * 1852.;
}
if (unit == "ft" || unit == "foot" || unit == "feet") {
return result * 12. * 0.0254;
}
if (unit == "\"" || unit == "in" || unit == "inch" || unit == "inches") {
return result * 0.0254;
}
if (unit[0] == '\'') {
double inches = 12 * result;
if (unit.length() > 1) {
inches += std::stod(unit.substr(1), &idx);
if (unit.substr(idx) == "\"") {
return inches * 0.0254;
}
}
}
throw NumberFormatException("(distance format) " + sData);
} else {
return result;
}
} catch (...) {
throw NumberFormatException("(double) " + sData);
}
}
double
StringUtils::parseSpeed(const std::string& sData, const bool defaultKmph) {
if (sData.size() == 0) {
throw EmptyData();
}
try {
size_t idx = 0;
const double result = std::stod(sData, &idx);
if (idx != sData.size()) {
const std::string unit = prune(sData.substr(idx));
if (unit == "km/h" || unit == "kph" || unit == "kmh" || unit == "kmph") {
return result / 3.6;
}
if (unit == "m/s") {
return result;
}
if (unit == "mph") {
return result * KM_PER_MILE / 3.6;
}
if (unit == "knots") {
return result * 1.852 / 3.6;
}
throw NumberFormatException("(speed format) " + sData);
} else {
return defaultKmph ? result / 3.6 : result;
}
} catch (...) {
throw NumberFormatException("(double) " + sData);
}
}
std::string
StringUtils::transcode(const XMLCh* const data, int length) {
if (data == 0) {
throw EmptyData();
}
if (length == 0) {
return "";
}
#if _XERCES_VERSION < 30100
char* t = XERCES_CPP_NAMESPACE::XMLString::transcode(data);
std::string result(t);
XERCES_CPP_NAMESPACE::XMLString::release(&t);
return result;
#else
try {
XERCES_CPP_NAMESPACE::TranscodeToStr utf8(data, "UTF-8");
return reinterpret_cast<const char*>(utf8.str());
} catch (XERCES_CPP_NAMESPACE::TranscodingException&) {
return "?";
}
#endif
}
std::string
StringUtils::transcodeFromLocal(const std::string& localString) {
#if _XERCES_VERSION > 30100
try {
if (myLCPTranscoder == nullptr) {
myLCPTranscoder = XERCES_CPP_NAMESPACE::XMLPlatformUtils::fgTransService->makeNewLCPTranscoder(XERCES_CPP_NAMESPACE::XMLPlatformUtils::fgMemoryManager);
}
if (myLCPTranscoder != nullptr) {
return transcode(myLCPTranscoder->transcode(localString.c_str()));
}
} catch (XERCES_CPP_NAMESPACE::TranscodingException&) {}
#endif
return localString;
}
std::string
StringUtils::transcodeToLocal(const std::string& utf8String) {
#if _XERCES_VERSION > 30100
try {
if (myLCPTranscoder == nullptr) {
myLCPTranscoder = XERCES_CPP_NAMESPACE::XMLPlatformUtils::fgTransService->makeNewLCPTranscoder(XERCES_CPP_NAMESPACE::XMLPlatformUtils::fgMemoryManager);
}
if (myLCPTranscoder != nullptr) {
XERCES_CPP_NAMESPACE::TranscodeFromStr utf8(reinterpret_cast<const XMLByte*>(utf8String.c_str()), utf8String.size(), "UTF-8");
return myLCPTranscoder->transcode(utf8.str());
}
} catch (XERCES_CPP_NAMESPACE::TranscodingException&) {}
#endif
return utf8String;
}
std::string
StringUtils::trim_left(const std::string s, const std::string& t) {
std::string result = s;
result.erase(0, s.find_first_not_of(t));
return result;
}
std::string
StringUtils::trim_right(const std::string s, const std::string& t) {
std::string result = s;
result.erase(s.find_last_not_of(t) + 1);
return result;
}
std::string
StringUtils::trim(const std::string s, const std::string& t) {
return trim_right(trim_left(s, t), t);
}
std::string
StringUtils::wrapText(const std::string s, int width) {
std::vector<std::string> parts = StringTokenizer(s).getVector();
std::string result;
std::string line;
bool firstLine = true;
bool firstWord = true;
for (std::string p : parts) {
if ((int)(line.size() + p.size()) < width || firstWord) {
if (firstWord) {
firstWord = false;
} else {
line += " ";
}
line = line + p;
} else {
if (firstLine) {
firstLine = false;
} else {
result += "\n";
}
result = result + line;
line.clear();
firstWord = true;
}
}
if (line.size() > 0) {
if (firstLine) {
firstLine = false;
} else {
result += "\n";
}
result = result + line;
}
return result;
}
void
StringUtils::resetTranscoder() {
myLCPTranscoder = nullptr;
}