If the last species name in a composition string contained a colon, it would not be parsed correctly.
322 lines
9.1 KiB
C++
322 lines
9.1 KiB
C++
/**
|
|
* @file stringUtils.cpp
|
|
* Contains definitions for string manipulation functions
|
|
* within Cantera.
|
|
*/
|
|
|
|
// This file is part of Cantera. See License.txt in the top-level directory or
|
|
// at http://www.cantera.org/license.txt for license and copyright information.
|
|
|
|
//@{
|
|
#include "cantera/base/ct_defs.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#define SNPRINTF _snprintf
|
|
#else
|
|
#define SNPRINTF snprintf
|
|
#endif
|
|
//@}
|
|
|
|
#include "cantera/base/stringUtils.h"
|
|
#include "cantera/base/ctexceptions.h"
|
|
#include "cantera/base/ctml.h"
|
|
#include "cantera/base/utilities.h"
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <sstream>
|
|
#include <cstdio>
|
|
|
|
namespace ba = boost::algorithm;
|
|
|
|
namespace Cantera
|
|
{
|
|
|
|
std::string fp2str(const double x, const std::string& fmt)
|
|
{
|
|
warn_deprecated("fp2str", "Unused. To be removed after Cantera 2.3. "
|
|
"Use fmt::format instead.");
|
|
char buf[64];
|
|
int n = SNPRINTF(buf, 63, fmt.c_str(), x);
|
|
if (n > 0) {
|
|
buf[63] = '\0';
|
|
return std::string(buf);
|
|
}
|
|
return std::string(" ");
|
|
}
|
|
|
|
std::string int2str(const int n, const std::string& fmt)
|
|
{
|
|
warn_deprecated("int2str", "Unused. To be removed after Cantera 2.3. "
|
|
"Use fmt::format instead.");
|
|
char buf[30];
|
|
int m = SNPRINTF(buf, 30, fmt.c_str(), n);
|
|
if (m > 0) {
|
|
buf[29] = '\0';
|
|
return std::string(buf);
|
|
}
|
|
return std::string(" ");
|
|
}
|
|
|
|
std::string int2str(const size_t n)
|
|
{
|
|
warn_deprecated("int2str", "Unused. To be removed after Cantera 2.3. "
|
|
"Use fmt::format instead.");
|
|
std::stringstream ss;
|
|
ss << n;
|
|
return ss.str();
|
|
}
|
|
|
|
std::string vec2str(const vector_fp& v, const std::string& fmt,
|
|
const std::string& sep)
|
|
{
|
|
char buf[64];
|
|
std::stringstream o;
|
|
for (size_t i = 0; i < v.size(); i++) {
|
|
SNPRINTF(buf, 63, fmt.c_str(), v[i]);
|
|
o << buf;
|
|
if (i != v.size() - 1) {
|
|
o << sep;
|
|
}
|
|
}
|
|
return o.str();
|
|
}
|
|
|
|
|
|
std::string lowercase(const std::string& s)
|
|
{
|
|
warn_deprecated("lowercase", "Use boost::algorithm::to_lower_copy instead. "
|
|
"To be removed after Cantera 2.3.");
|
|
std::string lc(s);
|
|
for (size_t i = 0; i < s.size(); i++) {
|
|
lc[i] = (char) tolower(s[i]);
|
|
}
|
|
return lc;
|
|
}
|
|
|
|
std::string stripws(const std::string& s)
|
|
{
|
|
warn_deprecated("stripws", "Use boost::algorithm::trim_copy instead. "
|
|
"To be removed after Cantera 2.3.");
|
|
return ba::trim_copy(s);
|
|
}
|
|
|
|
std::string stripnonprint(const std::string& s)
|
|
{
|
|
std::string ss = "";
|
|
for (size_t i = 0; i < s.size(); i++) {
|
|
if (isprint(s[i])) {
|
|
ss += s[i];
|
|
}
|
|
}
|
|
return ss;
|
|
}
|
|
|
|
compositionMap parseCompString(const std::string& ss,
|
|
const std::vector<std::string>& names)
|
|
{
|
|
compositionMap x;
|
|
for (size_t k = 0; k < names.size(); k++) {
|
|
x[names[k]] = 0.0;
|
|
}
|
|
|
|
size_t start = 0;
|
|
size_t stop = 0;
|
|
size_t left = 0;
|
|
while (stop < ss.size()) {
|
|
size_t colon = ss.find(':', left);
|
|
if (colon == npos) {
|
|
break;
|
|
}
|
|
size_t valstart = ss.find_first_not_of(" \t\n", colon+1);
|
|
stop = ss.find_first_of(", ;\n\t", valstart);
|
|
std::string name = ba::trim_copy(ss.substr(start, colon-start));
|
|
if (!names.empty() && x.find(name) == x.end()) {
|
|
throw CanteraError("parseCompString",
|
|
"unknown species '" + name + "'");
|
|
}
|
|
if (getValue(x, name, 0.0) != 0.0) {
|
|
throw CanteraError("parseCompString",
|
|
"Duplicate key: '" + name + "'.");
|
|
}
|
|
|
|
double value;
|
|
try {
|
|
value = fpValueCheck(ss.substr(valstart, stop-colon-1));
|
|
} catch (CanteraError& err) {
|
|
// If we have a key containing a colon, we expect this to fail. In
|
|
// this case, take the current substring as part of the key and look
|
|
// to the right of the next colon for the corresponding value.
|
|
// Otherwise, this is an invalid composition string.
|
|
std::string testname = ss.substr(start, stop-colon-1);
|
|
if (testname.find_first_of(" \n\t") != npos) {
|
|
// Space, tab, and newline are never allowed in names
|
|
throw;
|
|
} else if (ss.substr(valstart, stop-colon-1).find(':') != npos) {
|
|
left = colon + 1;
|
|
stop = 0; // Force another iteration of this loop
|
|
continue;
|
|
} else {
|
|
throw;
|
|
}
|
|
}
|
|
x[name] = value;
|
|
start = ss.find_first_not_of(", ;\n\t", stop+1);
|
|
left = start;
|
|
}
|
|
if (left != start) {
|
|
throw CanteraError("parseCompString", "Unable to parse key-value pair:"
|
|
"\n'{}'", ss.substr(start, stop));
|
|
}
|
|
if (stop != npos && !ba::trim_copy(ss.substr(stop)).empty()) {
|
|
throw CanteraError("parseCompString", "Found non-key:value data "
|
|
"in composition string: '" + ss.substr(stop) + "'");
|
|
}
|
|
return x;
|
|
}
|
|
|
|
int intValue(const std::string& val)
|
|
{
|
|
return std::atoi(ba::trim_copy(val).c_str());
|
|
}
|
|
|
|
doublereal fpValue(const std::string& val)
|
|
{
|
|
doublereal rval;
|
|
std::stringstream ss(val);
|
|
ss.imbue(std::locale("C"));
|
|
ss >> rval;
|
|
return rval;
|
|
}
|
|
|
|
doublereal fpValueCheck(const std::string& val)
|
|
{
|
|
std::string str = ba::trim_copy(val);
|
|
if (str.empty()) {
|
|
throw CanteraError("fpValueCheck", "string has zero length");
|
|
}
|
|
int numDot = 0;
|
|
int numExp = 0;
|
|
char ch;
|
|
int istart = 0;
|
|
ch = str[0];
|
|
if (ch == '+' || ch == '-') {
|
|
istart = 1;
|
|
}
|
|
for (size_t i = istart; i < str.size(); i++) {
|
|
ch = str[i];
|
|
if (isdigit(ch)) {
|
|
} else if (ch == '.') {
|
|
numDot++;
|
|
if (numDot > 1) {
|
|
throw CanteraError("fpValueCheck",
|
|
"string has more than one .");
|
|
}
|
|
if (numExp > 0) {
|
|
throw CanteraError("fpValueCheck",
|
|
"string has decimal point in exponent");
|
|
}
|
|
} else if (ch == 'e' || ch == 'E' || ch == 'd' || ch == 'D') {
|
|
numExp++;
|
|
str[i] = 'E';
|
|
if (numExp > 1) {
|
|
throw CanteraError("fpValueCheck",
|
|
"string has more than one exp char");
|
|
}
|
|
ch = str[i+1];
|
|
if (ch == '+' || ch == '-') {
|
|
i++;
|
|
}
|
|
} else {
|
|
throw CanteraError("fpValueCheck",
|
|
"Trouble processing string, " + str);
|
|
}
|
|
}
|
|
return fpValue(str);
|
|
}
|
|
|
|
std::string wrapString(const std::string& s, const int len)
|
|
{
|
|
warn_deprecated("wrapString", "Unused. To be removed after Cantera 2.3.");
|
|
int count=0;
|
|
std::string r;
|
|
for (size_t n = 0; n < s.size(); n++) {
|
|
if (s[n] == '\n') {
|
|
count = 0;
|
|
} else {
|
|
count++;
|
|
}
|
|
if (count > len && s[n] == ' ') {
|
|
r += "\n ";
|
|
count = 0;
|
|
}
|
|
r += s[n];
|
|
}
|
|
return r;
|
|
}
|
|
|
|
std::string parseSpeciesName(const std::string& nameStr, std::string& phaseName)
|
|
{
|
|
std::string s = ba::trim_copy(nameStr);
|
|
phaseName = "";
|
|
size_t ibegin = s.find_first_not_of(" ;\n\t");
|
|
if (ibegin != std::string::npos) {
|
|
s = s.substr(ibegin,s.size());
|
|
size_t icolon = s.find(':');
|
|
size_t iend = s.find_first_of(" ;\n\t");
|
|
if (icolon != std::string::npos) {
|
|
phaseName = s.substr(0, icolon);
|
|
s = s.substr(icolon+1, s.size());
|
|
icolon = s.find(':');
|
|
if (icolon != std::string::npos) {
|
|
throw CanteraError("parseSpeciesName()", "two colons in name: " + nameStr);
|
|
}
|
|
}
|
|
if (iend != std::string::npos) {
|
|
throw CanteraError("parseSpeciesName()",
|
|
"Species name has \" ;/\n/\t\" in the middle of it: " + nameStr);
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
doublereal strSItoDbl(const std::string& strSI)
|
|
{
|
|
std::vector<std::string> v;
|
|
tokenizeString(strSI, v);
|
|
doublereal fp = 1.0;
|
|
size_t n = v.size();
|
|
if (n > 2 || n < 1) {
|
|
throw CanteraError("strSItoDbl",
|
|
"number of tokens is too high");
|
|
} else if (n == 2) {
|
|
fp = toSI(v[1]);
|
|
}
|
|
doublereal val = fpValueCheck(v[0]);
|
|
return val * fp;
|
|
}
|
|
|
|
void tokenizeString(const std::string& in_val, std::vector<std::string>& v)
|
|
{
|
|
std::string val = ba::trim_copy(in_val);
|
|
v.clear();
|
|
if (val.empty()) {
|
|
// In this case, prefer v to be empty instead of split's behavior of
|
|
// returning a vector with one element that is the empty string.
|
|
return;
|
|
}
|
|
ba::split(v, val, ba::is_space(), ba::token_compress_on);
|
|
}
|
|
|
|
size_t copyString(const std::string& source, char* dest, size_t length)
|
|
{
|
|
const char* c_src = source.c_str();
|
|
size_t N = std::min(length, source.length()+1);
|
|
size_t ret = (length >= source.length() + 1) ? 0 : source.length() + 1;
|
|
std::copy(c_src, c_src + N, dest);
|
|
if (length != 0) {
|
|
dest[length-1] = '\0';
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
}
|