1 /**
2  * @file ctml.cpp
3  * Definitions for functions to read and write CTML.
4  */
5 
6 // This file is part of Cantera. See License.txt in the top-level directory or
7 // at https://cantera.org/license.txt for license and copyright information.
8 
9 #include "cantera/base/ctml.h"
10 #include "cantera/base/stringUtils.h"
11 #include "cantera/base/Array.h"
12 #include "cantera/base/ctexceptions.h"
13 #include "cantera/base/global.h"
14 
15 using namespace std;
16 
17 namespace Cantera
18 {
19 std::string FP_Format = "%23.15E";
20 
addFloat(XML_Node & node,const std::string & title,const doublereal val,const std::string & units,const std::string & type,const doublereal minval,const doublereal maxval)21 void addFloat(XML_Node& node, const std::string& title,
22               const doublereal val, const std::string& units,
23               const std::string& type, const doublereal minval,
24               const doublereal maxval)
25 {
26     XML_Node& f = node.addChild(title, val, FP_Format);
27     if (type != "") {
28         f.addAttribute("type",type);
29     }
30     if (units != "") {
31         f.addAttribute("units",units);
32     }
33     f.addAttribute("vtype", "float");
34     if (minval != Undef) {
35         f.addAttribute("min",minval);
36     }
37     if (maxval != Undef) {
38         f.addAttribute("max",maxval);
39     }
40 }
41 
addFloatArray(XML_Node & node,const std::string & title,const size_t n,const doublereal * const vals,const std::string & units,const std::string & type,const doublereal minval,const doublereal maxval)42 void addFloatArray(XML_Node& node, const std::string& title, const size_t n,
43                    const doublereal* const vals, const std::string& units,
44                    const std::string& type,
45                    const doublereal minval, const doublereal maxval)
46 {
47     std::string v = "";
48     for (size_t i = 0; i < n; i++) {
49         v += fmt::sprintf(FP_Format, vals[i]);
50         if (i == n-1) {
51             v += "\n";
52         } else if (i > 0 && (i+1) % 3 == 0) {
53             v += ",\n";
54         } else {
55             v += ", ";
56         }
57     }
58     XML_Node& f = node.addChild("floatArray",v);
59     f.addAttribute("title",title);
60     if (type != "") {
61         f.addAttribute("type",type);
62     }
63     f.addAttribute("size", double(n));
64     if (units != "") {
65         f.addAttribute("units",units);
66     }
67     if (minval != Undef) {
68         f.addAttribute("min",minval);
69     }
70     if (maxval != Undef) {
71         f.addAttribute("max",maxval);
72     }
73 }
74 
addNamedFloatArray(XML_Node & node,const std::string & name,const size_t n,const doublereal * const vals,const std::string units,const std::string type,const doublereal minval,const doublereal maxval)75 void addNamedFloatArray(XML_Node& node, const std::string& name, const size_t n,
76                         const doublereal* const vals, const std::string units,
77                         const std::string type, const doublereal minval,
78                         const doublereal maxval)
79 {
80     std::string v = "";
81     for (size_t i = 0; i < n; i++) {
82         v += fmt::sprintf(FP_Format, vals[i]);
83         if (i == n-1) {
84             v += "\n";
85         } else if (i > 0 && (i+1) % 3 == 0) {
86             v += ",\n";
87         } else {
88             v += ", ";
89         }
90     }
91     XML_Node& f = node.addChild(name, v);
92     if (type != "") {
93         f.addAttribute("type",type);
94     }
95 
96     // Add vtype, which indicates the type of the value. Here we specify it as a
97     // list of floats separated by commas, with a length given by size
98     // attribute.
99     f.addAttribute("vtype", "floatArray");
100 
101     f.addAttribute("size", n);
102     if (units != "") {
103         f.addAttribute("units", units);
104     }
105     if (minval != Undef) {
106         f.addAttribute("min", minval);
107     }
108     if (maxval != Undef) {
109         f.addAttribute("max", maxval);
110     }
111 }
112 
addString(XML_Node & node,const std::string & titleString,const std::string & valueString,const std::string & typeString)113 void addString(XML_Node& node, const std::string& titleString,
114                const std::string& valueString,
115                const std::string& typeString)
116 {
117     XML_Node& f = node.addChild("string", valueString);
118     f.addAttribute("title", titleString);
119     if (typeString != "") {
120         f.addAttribute("type", typeString);
121     }
122 }
123 
getByTitle(const XML_Node & node,const std::string & title)124 XML_Node* getByTitle(const XML_Node& node, const std::string& title)
125 {
126     XML_Node* s = node.findByAttr("title", title);
127     if (s && s->parent() == &node) {
128         return s;
129     }
130     return 0;
131 }
132 
getChildValue(const XML_Node & parent,const std::string & nameString)133 std::string getChildValue(const XML_Node& parent, const std::string& nameString)
134 {
135     if (!parent.hasChild(nameString)) {
136         return "";
137     }
138     return parent(nameString);
139 }
140 
getString(const XML_Node & node,const std::string & titleString,std::string & valueString,std::string & typeString)141 void getString(const XML_Node& node, const std::string& titleString, std::string& valueString,
142                std::string& typeString)
143 {
144     XML_Node* s = getByTitle(node, titleString);
145     if (s && s->name() == "string") {
146         valueString = s->value();
147         typeString = s->attrib("type");
148     } else {
149         valueString = "";
150         typeString = "";
151     }
152 }
153 
getIntegers(const XML_Node & node,std::map<std::string,int> & v)154 void getIntegers(const XML_Node& node,
155                  std::map<std::string, int>& v)
156 {
157     std::vector<XML_Node*> f = node.getChildren("integer");
158     for (size_t i = 0; i < f.size(); i++) {
159         const XML_Node& fi = *f[i];
160         if (fi["min"] != "" && fi["max"] != "") {
161             v[fi["title"]] = fi.int_value();
162         }
163     }
164 }
165 
getFloat(const XML_Node & parent,const std::string & name,const std::string & type)166 doublereal getFloat(const XML_Node& parent,
167                     const std::string& name,
168                     const std::string& type)
169 {
170     if (!parent.hasChild(name)) {
171         throw CanteraError("getFloat (called from XML Node \"" +
172                            parent.name() + "\"): ",
173                            "no child XML element named \"" + name + "\" exists");
174     }
175     const XML_Node& node = parent.child(name);
176     return getFloatCurrent(node, type);
177 }
178 
getFloatCurrent(const XML_Node & node,const std::string & type)179 doublereal getFloatCurrent(const XML_Node& node, const std::string& type)
180 {
181     doublereal fctr = 1.0;
182     doublereal x = node.fp_value();
183     const string& units = node["units"];
184     const string& vmin = node["min"];
185     const string& vmax = node["max"];
186     if (vmin != "" && x < fpValue(vmin) - Tiny) {
187         writelog("\nWarning: value "+node.value()+" is below lower limit of "
188                  +vmin+".\n");
189     }
190     if (node["max"] != "" && x > fpValue(vmax) + Tiny) {
191         writelog("\nWarning: value "+node.value()+" is above upper limit of "
192                  +vmax+".\n");
193     }
194     // Note, most types of converters default to toSI() type atm.
195     // This may change and become more specific in the future.
196     if (type == "actEnergy" && units != "") {
197         fctr = actEnergyToSI(units);
198     } else if (type == "toSI" && units != "") {
199         fctr = toSI(units);
200     } else if (type == "temperature" && units != "") {
201         fctr = toSI(units);
202     } else if (type == "density" && units != "") {
203         fctr = toSI(units);
204     } else if (type == "pressure" && units != "") {
205         fctr = toSI(units);
206     } else if (type != "" && units != "") {
207         fctr = toSI(units);
208         writelog("\nWarning: conversion toSI() was done on node value " + node.name() +
209                  "but wasn't explicitly requested. Type was \"" + type + "\"\n");
210     }
211     return fctr*x;
212 }
213 
getOptionalFloat(const XML_Node & parent,const std::string & name,doublereal & fltRtn,const std::string & type)214 bool getOptionalFloat(const XML_Node& parent,
215                       const std::string& name,
216                       doublereal& fltRtn,
217                       const std::string& type)
218 {
219     if (parent.hasChild(name)) {
220         fltRtn = getFloat(parent, name, type);
221         return true;
222     }
223     return false;
224 }
225 
getOptionalModel(const XML_Node & parent,const std::string & nodeName,std::string & modelName)226 bool getOptionalModel(const XML_Node& parent, const std::string& nodeName,
227                       std::string& modelName)
228 {
229     if (parent.hasChild(nodeName)) {
230         modelName = parent.child(nodeName)["model"];
231         return true;
232     }
233     return false;
234 }
235 
getInteger(const XML_Node & parent,const std::string & name)236 int getInteger(const XML_Node& parent, const std::string& name)
237 {
238     if (!parent.hasChild(name)) {
239         throw CanteraError("getInteger (called from XML Node \"" +
240                            parent.name() + "\"): ",
241                            "no child XML element named " + name);
242     }
243     const XML_Node& node = parent.child(name);
244     int x = node.int_value();
245     const string& vmin = node["min"];
246     const string& vmax = node["max"];
247     if (vmin != "" && x < intValue(vmin)) {
248         writelog("\nWarning: value "+node.value()+" is below lower limit of "
249                  +vmin+".\n");
250     }
251     if (node["max"] != "" && x > intValue(vmax)) {
252         writelog("\nWarning: value "+node.value()+" is above upper limit of "
253                  +vmax+".\n");
254     }
255     return x;
256 }
257 
getFloatArray(const XML_Node & node,vector_fp & v,const bool convert,const std::string & unitsString,const std::string & nodeName)258 size_t getFloatArray(const XML_Node& node, vector_fp & v,
259                      const bool convert, const std::string& unitsString,
260                      const std::string& nodeName)
261 {
262     const XML_Node* readNode = &node;
263     if (node.name() != nodeName) {
264         vector<XML_Node*> ll = node.getChildren(nodeName);
265         if (ll.size() == 0) {
266             throw CanteraError("getFloatArray",
267                                "wrong XML element type/name: was expecting "
268                                + nodeName + "but accessed " + node.name());
269         } else {
270             readNode = ll[0];
271             ll = readNode->getChildren("floatArray");
272             if (ll.size() > 0) {
273                 readNode = ll[0];
274             }
275         }
276     }
277 
278     v.clear();
279     doublereal vmin = Undef, vmax = Undef;
280     doublereal funit = 1.0;
281 
282     // Get the attributes field, units, from the XML node
283     std::string units = readNode->attrib("units");
284     if (units != "" && convert) {
285         if (unitsString == "actEnergy" && units != "") {
286             funit = actEnergyToSI(units);
287         } else if (unitsString != "" && units != "") {
288             funit = toSI(units);
289         }
290     }
291 
292     if (readNode->attrib("min") != "") {
293         vmin = fpValueCheck(readNode->attrib("min"));
294     }
295     if (readNode->attrib("max") != "") {
296         vmax = fpValueCheck(readNode->attrib("max"));
297     }
298 
299     std::string val = readNode->value();
300     while (true) {
301         size_t icom = val.find(',');
302         if (icom != string::npos) {
303             string numstr = val.substr(0,icom);
304             val = val.substr(icom+1,val.size());
305             v.push_back(fpValueCheck(numstr));
306         } else {
307             // This little bit of code is to allow for the possibility of a
308             // comma being the last item in the value text. This was allowed in
309             // previous versions of Cantera, even though it would appear to be
310             // odd. So, we keep the possibility in for backwards compatibility.
311             if (!val.empty()) {
312                 v.push_back(fpValueCheck(val));
313             }
314             break;
315         }
316         doublereal vv = v.back();
317         if (vmin != Undef && vv < vmin - Tiny) {
318             writelog("\nWarning: value {} is below lower limit of {}.\n",
319                      vv, vmin);
320         }
321         if (vmax != Undef && vv > vmax + Tiny) {
322             writelog("\nWarning: value {} is above upper limit of {}.\n",
323                      vv, vmax);
324         }
325     }
326     for (size_t n = 0; n < v.size(); n++) {
327         v[n] *= funit;
328     }
329     return v.size();
330 }
331 
getMap(const XML_Node & node,std::map<std::string,std::string> & m)332 void getMap(const XML_Node& node, std::map<std::string, std::string>& m)
333 {
334     std::vector<std::string> v;
335     getStringArray(node, v);
336     for (size_t i = 0; i < v.size(); i++) {
337         size_t icolon = v[i].find(":");
338         if (icolon == string::npos) {
339             throw CanteraError("getMap","missing colon in map entry ("
340                                +v[i]+")");
341         }
342         m[v[i].substr(0,icolon)] = v[i].substr(icolon+1, v[i].size());
343     }
344 }
345 
getPairs(const XML_Node & node,std::vector<std::string> & key,std::vector<std::string> & val)346 int getPairs(const XML_Node& node, std::vector<std::string>& key,
347              std::vector<std::string>& val)
348 {
349     vector<string> v;
350     getStringArray(node, v);
351     int n = static_cast<int>(v.size());
352     for (int i = 0; i < n; i++) {
353         size_t icolon = v[i].find(":");
354         if (icolon == string::npos) {
355             throw CanteraError("getPairs","Missing a colon in the Pair entry ("
356                                +v[i]+")");
357         }
358         key.push_back(v[i].substr(0,icolon));
359         val.push_back(v[i].substr(icolon+1, v[i].size()));
360     }
361     return n;
362 }
363 
getMatrixValues(const XML_Node & node,const std::vector<std::string> & keyStringRow,const std::vector<std::string> & keyStringCol,Array2D & retnValues,const bool convert,const bool matrixSymmetric)364 void getMatrixValues(const XML_Node& node,
365                      const std::vector<std::string>& keyStringRow,
366                      const std::vector<std::string>& keyStringCol,
367                      Array2D& retnValues, const bool convert,
368                      const bool matrixSymmetric)
369 {
370     if (keyStringRow.size() > retnValues.nRows()) {
371         throw CanteraError("getMatrixValues",
372                            "size of key1 greater than numrows");
373     } else if (keyStringCol.size() > retnValues.nColumns()) {
374         throw CanteraError("getMatrixValues",
375                            "size of key2 greater than num cols");
376     } else if (matrixSymmetric && retnValues.nRows() != retnValues.nColumns()) {
377         throw CanteraError("getMatrixValues",
378                            "nrow != ncol for a symmetric matrix");
379     }
380 
381     // Get the attributes field, units, from the XML node and determine the
382     // conversion factor, funit.
383     doublereal funit = 1.0;
384     if (convert && node["units"] != "") {
385         funit = toSI(node["units"]);
386     }
387 
388     vector<string> v;
389     getStringArray(node, v);
390     for (size_t i = 0; i < v.size(); i++) {
391         size_t icolon = v[i].find(":");
392         if (icolon == string::npos) {
393             throw CanteraError("getMatrixValues","Missing two colons ("
394                                +v[i]+")");
395         }
396         string key1 = v[i].substr(0,icolon);
397         string rmm = v[i].substr(icolon+1, v[i].size());
398 
399         icolon = rmm.find(":");
400         if (icolon == string::npos) {
401             throw CanteraError("getMatrixValues","Missing one colon ("
402                                +v[i]+")");
403         }
404 
405         size_t irow = find(keyStringRow.begin(), keyStringRow.end(), key1)
406                       - keyStringRow.begin();
407         if (irow == keyStringRow.size()) {
408             throw CanteraError("getMatrixValues","Row not matched by string: "
409                                + key1);
410         }
411 
412         string key2 = rmm.substr(0,icolon);
413         size_t icol = find(keyStringCol.begin(), keyStringCol.end(), key2)
414                       - keyStringCol.begin();
415         if (icol == keyStringCol.size()) {
416             throw CanteraError("getMatrixValues","Col not matched by string: "
417                                + key2);
418         }
419         double dval = fpValueCheck(rmm.substr(icolon+1, rmm.size())) * funit;
420 
421         // Finally, insert the value;
422         retnValues(irow, icol) = dval;
423         if (matrixSymmetric) {
424             retnValues(icol, irow) = dval;
425         }
426     }
427 }
428 
getStringArray(const XML_Node & node,std::vector<std::string> & v)429 void getStringArray(const XML_Node& node, std::vector<std::string>& v)
430 {
431     tokenizeString(node.value(), v);
432 }
433 
434 }
435