1 /**
2  *  @file stringUtils.cpp
3  * Contains definitions for string manipulation functions
4  *       within Cantera.
5  */
6 
7 // This file is part of Cantera. See License.txt in the top-level directory or
8 // at https://cantera.org/license.txt for license and copyright information.
9 
10 //@{
11 #include "cantera/base/ct_defs.h"
12 
13 #ifdef _MSC_VER
14 #define SNPRINTF _snprintf
15 #else
16 #define SNPRINTF snprintf
17 #endif
18 //@}
19 
20 #include "cantera/base/stringUtils.h"
21 #include "cantera/base/ctexceptions.h"
22 #include "cantera/base/utilities.h"
23 #include "cantera/base/global.h"
24 
25 #include <boost/algorithm/string.hpp>
26 #include <sstream>
27 
28 namespace ba = boost::algorithm;
29 
30 namespace Cantera
31 {
32 
vec2str(const vector_fp & v,const std::string & fmt,const std::string & sep)33 std::string vec2str(const vector_fp& v, const std::string& fmt,
34                     const std::string& sep)
35 {
36     char buf[64];
37     std::stringstream o;
38     for (size_t i = 0; i < v.size(); i++) {
39         SNPRINTF(buf, 63, fmt.c_str(), v[i]);
40         o << buf;
41         if (i != v.size() - 1) {
42             o << sep;
43         }
44     }
45     return o.str();
46 }
47 
stripnonprint(const std::string & s)48 std::string stripnonprint(const std::string& s)
49 {
50     std::string ss = "";
51     for (size_t i = 0; i < s.size(); i++) {
52         if (isprint(s[i])) {
53             ss += s[i];
54         }
55     }
56     return ss;
57 }
58 
parseCompString(const std::string & ss,const std::vector<std::string> & names)59 compositionMap parseCompString(const std::string& ss,
60                                const std::vector<std::string>& names)
61 {
62     compositionMap x;
63     for (size_t k = 0; k < names.size(); k++) {
64         x[names[k]] = 0.0;
65     }
66 
67     size_t start = 0;
68     size_t stop = 0;
69     size_t left = 0;
70     while (stop < ss.size()) {
71         size_t colon = ss.find(':', left);
72         if (colon == npos) {
73             break;
74         }
75         size_t valstart = ss.find_first_not_of(" \t\n", colon+1);
76         stop = ss.find_first_of(", ;\n\t", valstart);
77         std::string name = ba::trim_copy(ss.substr(start, colon-start));
78         if (!names.empty() && x.find(name) == x.end()) {
79             throw CanteraError("parseCompString",
80                 "unknown species '" + name + "'");
81         }
82 
83         double value;
84         try {
85             value = fpValueCheck(ss.substr(valstart, stop-valstart));
86         } catch (CanteraError&) {
87             // If we have a key containing a colon, we expect this to fail. In
88             // this case, take the current substring as part of the key and look
89             // to the right of the next colon for the corresponding value.
90             // Otherwise, this is an invalid composition string.
91             std::string testname = ss.substr(start, stop-start);
92             if (testname.find_first_of(" \n\t") != npos) {
93                 // Space, tab, and newline are never allowed in names
94                 throw;
95             } else if (ss.substr(valstart, stop-valstart).find(':') != npos) {
96                 left = colon + 1;
97                 stop = 0; // Force another iteration of this loop
98                 continue;
99             } else {
100                 throw;
101             }
102         }
103         if (getValue(x, name, 0.0) != 0.0) {
104             throw CanteraError("parseCompString",
105                                "Duplicate key: '" + name + "'.");
106         }
107 
108         x[name] = value;
109         start = ss.find_first_not_of(", ;\n\t", stop+1);
110         left = start;
111     }
112     if (left != start) {
113         throw CanteraError("parseCompString", "Unable to parse key-value pair:"
114             "\n'{}'", ss.substr(start, stop));
115     }
116     if (stop != npos && !ba::trim_copy(ss.substr(stop)).empty()) {
117         throw CanteraError("parseCompString", "Found non-key:value data "
118             "in composition string: '" + ss.substr(stop) + "'");
119     }
120     return x;
121 }
122 
intValue(const std::string & val)123 int intValue(const std::string& val)
124 {
125     return std::atoi(ba::trim_copy(val).c_str());
126 }
127 
fpValue(const std::string & val)128 doublereal fpValue(const std::string& val)
129 {
130     doublereal rval;
131     std::stringstream ss(val);
132     ss.imbue(std::locale("C"));
133     ss >> rval;
134     return rval;
135 }
136 
fpValueCheck(const std::string & val)137 doublereal fpValueCheck(const std::string& val)
138 {
139     std::string str = ba::trim_copy(val);
140     if (str.empty()) {
141         throw CanteraError("fpValueCheck", "string has zero length");
142     }
143     int numDot = 0;
144     int numExp = 0;
145     char ch;
146     int istart = 0;
147     ch = str[0];
148     if (ch == '+' || ch == '-') {
149         if (str.size() == 1) {
150             throw CanteraError("fpValueCheck", "string ends in '{}'", ch);
151         }
152         istart = 1;
153     }
154     for (size_t i = istart; i < str.size(); i++) {
155         ch = str[i];
156         if (isdigit(ch)) {
157         } else if (ch == '.') {
158             numDot++;
159             if (numDot > 1) {
160                 throw CanteraError("fpValueCheck",
161                                    "string has more than one .");
162             }
163             if (numExp > 0) {
164                 throw CanteraError("fpValueCheck",
165                                    "string has decimal point in exponent");
166             }
167         } else if (ch == 'e' || ch == 'E' || ch == 'd' || ch == 'D') {
168             numExp++;
169             str[i] = 'E';
170             if (numExp > 1) {
171                 throw CanteraError("fpValueCheck",
172                                    "string has more than one exp char");
173             } else if (i == str.size() - 1) {
174                 throw CanteraError("fpValueCheck",
175                                    "string ends in '{}'", ch);
176             }
177             ch = str[i+1];
178             if (ch == '+' || ch == '-') {
179                 if (i + 1 == str.size() - 1) {
180                     throw CanteraError("fpValueCheck",
181                                        "string ends in '{}'", ch);
182                 }
183                 i++;
184             }
185         } else {
186             throw CanteraError("fpValueCheck",
187                                "Trouble processing string, " + str);
188         }
189     }
190     return fpValue(str);
191 }
192 
parseSpeciesName(const std::string & nameStr,std::string & phaseName)193 std::string parseSpeciesName(const std::string& nameStr, std::string& phaseName)
194 {
195     std::string s = ba::trim_copy(nameStr);
196     phaseName = "";
197     size_t ibegin = s.find_first_not_of(" ;\n\t");
198     if (ibegin != std::string::npos) {
199         s = s.substr(ibegin,s.size());
200         size_t icolon = s.find(':');
201         size_t iend = s.find_first_of(" ;\n\t");
202         if (icolon != std::string::npos) {
203             phaseName = s.substr(0, icolon);
204             s = s.substr(icolon+1, s.size());
205             icolon = s.find(':');
206             if (icolon != std::string::npos) {
207                 throw CanteraError("parseSpeciesName",
208                                    "two colons in name: '{}'", nameStr);
209             }
210         }
211         if (iend != std::string::npos) {
212             throw CanteraError("parseSpeciesName", "Species name has "
213                                "\" ;/\n/\t\" in the middle of it: '{}'", nameStr);
214         }
215     }
216     return s;
217 }
218 
strSItoDbl(const std::string & strSI)219 doublereal strSItoDbl(const std::string& strSI)
220 {
221     std::vector<std::string> v;
222     tokenizeString(strSI, v);
223     doublereal fp = 1.0;
224     size_t n = v.size();
225     if (n > 2 || n < 1) {
226         throw CanteraError("strSItoDbl",
227                            "number of tokens is too high");
228     } else if (n == 2) {
229         fp = toSI(v[1]);
230     }
231     doublereal val = fpValueCheck(v[0]);
232     return val * fp;
233 }
234 
tokenizeString(const std::string & in_val,std::vector<std::string> & v)235 void tokenizeString(const std::string& in_val, std::vector<std::string>& v)
236 {
237     std::string val = ba::trim_copy(in_val);
238     v.clear();
239     if (val.empty()) {
240         // In this case, prefer v to be empty instead of split's behavior of
241         // returning a vector with one element that is the empty string.
242         return;
243     }
244     ba::split(v, val, ba::is_space(), ba::token_compress_on);
245 }
246 
copyString(const std::string & source,char * dest,size_t length)247 size_t copyString(const std::string& source, char* dest, size_t length)
248 {
249     const char* c_src = source.c_str();
250     size_t N = std::min(length, source.length()+1);
251     size_t ret = (length >= source.length() + 1) ? 0 : source.length() + 1;
252     std::copy(c_src, c_src + N, dest);
253     if (length != 0) {
254         dest[length-1] = '\0';
255     }
256     return ret;
257 }
258 
trimCopy(const std::string & input)259 std::string trimCopy(const std::string &input) {
260     return ba::trim_copy(input);
261 }
262 
toLowerCopy(const std::string & input)263 std::string toLowerCopy(const std::string &input) {
264     return ba::to_lower_copy(input);
265 }
266 
caseInsensitiveEquals(const std::string & input,const std::string & test)267 bool caseInsensitiveEquals(const std::string &input, const std::string &test) {
268     return ba::iequals(input, test);
269 }
270 
271 }
272