/** * @file ctml.cpp * Definitions for functions to read and write CTML. * */ // Copyright 2002 California Institute of Technology #include "cantera/base/ctml.h" //@{ #define CTML_VERSION_1_4_1 //@} #include "cantera/base/global.h" #include "cantera/base/stringUtils.h" #include #include #include using namespace std; using namespace Cantera; namespace ctml { std::string FP_Format = "%23.15E"; std::string INT_Format = "%8d"; //==================================================================================================================== //! Convert a floating point value from a string to a double /*! * @param val String value input * * @return Returns a double */ static doublereal fpValue(std::string val) { return atof(stripws(val).c_str()); } //==================================================================================================================== // This function adds a child node with the name, "bool", with a value // consisting of a single bool /* * This function will add a child node to the current XML node, with the * name "bool". It will have a title attribute, and the body * of the XML node will be filled out with a single bool * * Example: * * Code snippet: * @verbatim const XML_Node &node; std::string titleString = "doSpecialOp"; bool value = true; addBool(node, titleString, value); @endverbatim * * Creates the following the snippet in the XML file: * @verbatim true <\integer> <\parentNode> @endverbatim * * @param node reference to the XML_Node object of the parent XML element * @param titleString String name of the title attribute * @param value Value - single bool * * @todo I don't think this is used. Figure out what is used for writing bools, * and codify that. */ void addBool(Cantera::XML_Node& node, const std::string& title, const bool val) { std::string v = (val ? "true" : "false"); XML_Node& f = node.addChild("bool", v); f.addAttribute("title", title); } //==================================================================================================================== // This function adds a child node with the name, "integer", with a value // consisting of a single integer /* * This function will add a child node to the current XML node, with the * name "integer". It will have a title attribute, and the body * of the XML node will be filled out with a single integer * * Example: * * Code snippet: * @verbatim const XML_Node &node; std::string titleString = "maxIterations"; int value = 1000; std::string typeString = "optional"; std::string units = ""; addInteger(node, titleString, value, typeString, units); @endverbatim * * Creates the following the snippet in the XML file: * @verbatim 100 <\integer> <\parentNode> @endverbatim * * @param node reference to the XML_Node object of the parent XML element * @param titleString String name of the title attribute * @param value Value - single integer * @param unitsString String name of the Units attribute. The default is to * have an empty string. * @param typeString String type. This is an optional parameter. The default * is to have an empty string. */ void addInteger(Cantera::XML_Node& node, const std::string& title, const int val, const std::string units, const std::string type) { #ifdef CTML_VERSION_1_4 XML_Node& f = node.addChild("integer", val); f.addAttribute("title", title); #else XML_Node& f = node.addChild(title, val); #endif f.addAttribute("vtype", "integer"); if (type != "") { f.addAttribute("type",type); } if (units != "") { f.addAttribute("units",units); } } //==================================================================================================================== // This function adds a child node with the name, "intArray", with a value // consisting of a comma separated list of integers /* * This function will add a child node to the current XML node, with the * name "intArray". It will have a title attribute, and the body * of the XML node will be filled out with a comma separated list of * integers * * Example: * * @verbatim const XML_Node &node; std::string titleString = "additionalCases"; int n = 3; int cases[3] = [3, 6, 10]; std::string typeString = "optional"; std::string units = ""; addIntegerArray(node, titleString, n, &cases[0], typeString, units); @endverbatim * * Creates the following the snippet in the XML file: * @verbatim 3, 6, 10 <\intArray> <\parentNode> @endverbatim * * * @param node reference to the XML_Node object of the parent XML element * @param titleString String name of the title attribute * @param n Length of the integer vector. * @param values Pointer to a vector of integers * @param unitsString String name of the Units attribute. This is an optional * parameter. The default is to * have an empty string. * @param typeString String type. This is an optional parameter. The default * is to have an empty string. * @param minval Minimum allowed value of the int. This is an optional * parameter. The default is the * special double, Cantera::Undef, which means to ignore the * entry. * @param maxval Maximum allowed value of the int. This is an optional * parameter. The default is the * special double, Cantera::Undef, which means to ignore the * entry. * * @todo I don't think this is used. Figure out what is used for writing integers, * and codify that. unitsString shouldn't be here, since it's an int. * typeString should be codified as to its usage. */ void addIntegerArray(Cantera::XML_Node& node, const std::string& title, const size_t n, const int* const vals, const std::string units, const std::string type, const doublereal minval, const doublereal maxval) { std::string v = ""; for (size_t i = 0; i < n; i++) { v += int2str(vals[i],INT_Format); if (i == n-1) { v += "\n"; } else if (i > 0 && (i+1) % 3 == 0) { v += ",\n"; } else { v += ", "; } } #ifdef CTML_VERSION_1_4 XML_Node& f = node.addChild("intArray",v); f.addAttribute("title",title); #else XML_Node& f = node.addChild(title, v); #endif if (type != "") { f.addAttribute("type",type); } f.addAttribute("size", static_cast(n)); #ifndef CTML_VERSION_1_4 f.addAttribute("vtype", "intArray"); #endif if (units != "") { f.addAttribute("units",units); } if (minval != Undef) { f.addAttribute("min",minval); } if (maxval != Undef) { f.addAttribute("max",maxval); } } //==================================================================================================================== // This function adds a child node with the name, "float", with a value // consisting of a single floating point number /* * This function will add a child node to the current XML node, with the * name "float". It will have a title attribute, and the body * of the XML node will be filled out with a single float * * Example: * * Code snippet: * @verbatim const XML_Node &node; std::string titleString = "activationEnergy"; double value = 50.3; doublereal maxval = 1.0E3; doublereal minval = 0.0; std::string typeString = "optional"; std::string unitsString = "kcal/gmol"; addFloat(node, titleString, value, unitsString, typeString, minval, maxval); @endverbatim * * Creates the following the snippet in the XML file: * @verbatim 50.3 <\float> <\parentNode> @endverbatim * * @param node reference to the XML_Node object of the parent XML element * @param titleString String name of the title attribute * @param value Value - single integer * @param unitsString String name of the Units attribute. The default is to * have an empty string. * @param typeString String type. This is an optional parameter. The default * is to have an empty string. * * @todo minval and maxval should be codified. typeString should be codified * as to its usage. */ void addFloat(Cantera::XML_Node& node, const std::string& title, const doublereal val, const std::string units, const std::string type, const doublereal minval, const doublereal maxval) { #ifdef CTML_VERSION_1_4 XML_Node& f = node.addChild("float", val, ctml::FP_Format); f.addAttribute("title", title); #else XML_Node& f = node.addChild(title, val, ctml::FP_Format); #endif if (type != "") { f.addAttribute("type",type); } if (units != "") { f.addAttribute("units",units); } f.addAttribute("vtype", "float"); if (minval != Undef) { f.addAttribute("min",minval); } if (maxval != Undef) { f.addAttribute("max",maxval); } } //==================================================================================================================== // This function adds a child node with the name, "floatArray", with a value // consisting of a comma separated list of floats /* * This function will add a child node to the current XML node, with the * name "floatArray". It will have a title attribute, and the body * of the XML node will be filled out with a comma separated list of * integers * * Example: * * Code snippet: * @verbatim const XML_Node &node; std::string titleString = "additionalTemperatures"; int n = 3; int Tcases[3] = [273.15, 298.15, 373.15]; std::string typeString = "optional"; std::string units = "Kelvin"; addFloatArray(node, titleString, n, &cases[0], typeString, units); @endverbatim * * Creates the following the snippet in the XML file: * @verbatim 273.15, 298.15, 373.15 <\floatArray> <\parentNode> @endverbatim * * @param node reference to the XML_Node object of the parent XML element * @param titleString String name of the title attribute * @param n Length of the doubles vector. * @param values Pointer to a vector of doubles * @param unitsString String name of the Units attribute. This is an optional * parameter. The default is to * have an empty string. * @param type String type. This is an optional parameter. The default * is to have an empty string. * @param minval Minimum allowed value of the int. This is an optional * parameter. The default is the * special double, Cantera::Undef, which means to ignore the * entry. * @param maxval Maximum allowed value of the int. This is an optional * parameter. The default is the * special double, Cantera::Undef, which means to ignore the * entry. * * @todo typeString should be codified as to its usage. */ void addFloatArray(Cantera::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) { size_t i; std::string v = ""; for (i = 0; i < n; i++) { v += fp2str(vals[i],FP_Format); if (i == n-1) { v += "\n"; } else if (i > 0 && (i+1) % 3 == 0) { v += ",\n"; } else { v += ", "; } } XML_Node& f = node.addChild("floatArray",v); f.addAttribute("title",title); if (type != "") { f.addAttribute("type",type); } f.addAttribute("size", double(n)); if (units != "") { f.addAttribute("units",units); } if (minval != Undef) { f.addAttribute("min",minval); } if (maxval != Undef) { f.addAttribute("max",maxval); } } //==================================================================================================================== // This function adds a child node with the name given by the first parameter with a value // consisting of a comma separated list of floats /* * This function will add a child node to the current XML node, with the * name given in the list. It will have a title attribute, and the body * of the XML node will be filled out with a comma separated list of * integers * * Example: * * Code snippet: * @verbatim const XML_Node &node; std::string titleString = "additionalTemperatures"; int n = 3; int Tcases[3] = [273.15, 298.15, 373.15]; std::string typeString = "optional"; std::string units = "Kelvin"; addNamedFloatArray(node, titleString, n, &cases[0], typeString, units); @endverbatim * * Creates the following the snippet in the XML file: * @verbatim 273.15, 298.15, 373.15 <\additionalTemperatures> <\parentNode> @endverbatim * * @param node reference to the XML_Node object of the parent XML element * @param name Name of the XML node * @param n Length of the doubles vector. * @param values Pointer to a vector of doubles * @param unitsString String name of the Units attribute. This is an optional * parameter. The default is to * have an empty string. * @param type String type. This is an optional parameter. The default * is to have an empty string. * @param minval Minimum allowed value of the int. This is an optional * parameter. The default is the * special double, Cantera::Undef, which means to ignore the * entry. * @param maxval Maximum allowed value of the int. This is an optional * parameter. The default is the * special double, Cantera::Undef, which means to ignore the * entry. * * @todo I don't think this is used. Figure out what is used for writing integers, * and codify that. unitsString shouldn't be here, since it's an int. * typeString should be codified as to its usage. */ void addNamedFloatArray(Cantera::XML_Node& node, const std::string& name, const int n, const doublereal* const vals, const std::string units, const std::string type, const doublereal minval, const doublereal maxval) { int i; std::string v = ""; for (i = 0; i < n; i++) { v += fp2str(vals[i],FP_Format); if (i == n-1) { v += "\n"; } else if (i > 0 && (i+1) % 3 == 0) { v += ",\n"; } else { v += ", "; } } XML_Node& f = node.addChild(name, v); if (type != "") { f.addAttribute("type",type); } /* * Add vtype, which indicates the type of the value. Here we specify it as a list of floats separated * by commas, with a length given by size attribute. */ f.addAttribute("vtype", "floatArray"); f.addAttribute("size", n); if (units != "") { f.addAttribute("units", units); } if (minval != Undef) { f.addAttribute("min", minval); } if (maxval != Undef) { f.addAttribute("max", maxval); } } //==================================================================================================================== // This function adds a child node with the name string with a string value // to the current node /* * This function will add a child node to the current XML node, with the * name "string". It will have a title attribute, and the body * of the XML node will be filled out with the valueString argument verbatim. * * Example: * * Code snippet: * @verbatim const XML_Node &node; addString(XML_Node& node, std::string titleString, std::string valueString, std::string typeString); @endverbatim * * Creates the following the snippet in the XML file: * @verbatim valueString <\string> @endverbatim * * @param node reference to the XML_Node object of the parent XML element * @param valueString Value string to be used in the new XML node. * @param titleString String name of the title attribute * @param typeString String type. This is an optional parameter. */ void addString(Cantera::XML_Node& node, const std::string& titleString, const std::string& valueString, const std::string typeString) { XML_Node& f = node.addChild("string", valueString); f.addAttribute("title", titleString); if (typeString != "") { f.addAttribute("type", typeString); } } XML_Node* getByTitle(const Cantera::XML_Node& node, const std::string& title) { XML_Node* s = node.findByAttr("title", title); if (!s) { return 0; } if (s->parent() == &node) { return s; } return 0; } //==================================================================================================================== // This function reads a child node with the name string and returns // its xml value as the return string /* * If the child XML_node named "name" doesn't exist, the empty string is returned. * * Code snippet: * @verbatim const XML_Node &parent; string name = "vacency_species"; string valueString = getChildValue(parent, name std::string typeString); @endverbatim * * returns valueString = "O(V)" * * from the following the snippet in the XML file: * * @verbatim O(V) <\vancencySpecies> @endverbatim * * @param parent parent reference to the XML_Node object of the parent XML element * @param name Name of the child XML_Node to read the value from. * * @return String value of the child XML_Node */ std::string getChildValue(const Cantera::XML_Node& parent, const std::string& nameString) { if (!parent.hasChild(nameString)) { return ""; } return parent(nameString); } //==================================================================================================================== // This function reads a child node with the name, "string", with a specific // title attribute named "titleString" /* * This function will read a child node to the current XML node, with the * name "string". It must have a title attribute, named titleString, and the body * of the XML node will be read into the valueString output argument. * * Example: * * Code snippet: * @verbatim const XML_Node &node; getString(XML_Node& node, std::string titleString, std::string valueString, std::string typeString); @endverbatim * * Reads the following the snippet in the XML file: * @verbatim valueString <\string> @endverbatim * * @param node Reference to the XML_Node object of the parent XML element * @param titleString String name of the title attribute of the child node * @param valueString Value string that is found in the child node. output variable * @param typeString String type. This is an optional output variable. It is filled * with the attribute "type" of the XML entry. */ void getString(const Cantera::XML_Node& node, const std::string& titleString, std::string& valueString, std::string& typeString) { valueString = ""; typeString = ""; XML_Node* s = getByTitle(node, titleString); if (s) if (s->name() == "string") { valueString = (*s).value(); typeString = (*s)["type"]; return; } } //==================================================================================================================== // This function attempts to read a named child node and returns with the contents in the value string. // title attribute named "titleString" /* * This function will read a child node to the current XML node, with the * name "string". It must have a title attribute, named titleString, and the body * of the XML node will be read into the valueString output argument. * * If the child node is not found then the empty string is returned. * * Example: * * Code snippet: * @verbatim const XML_Node &node; std::string valueString; std::string typeString; std::string nameString = "timeIncrement"; getString(XML_Node& node, nameString, valueString, valueString, typeString); @endverbatim * * Reads the following the snippet in the XML file: * * * @verbatim valueString <\nameString> @endverbatim * * or alternatively as a retrofit and special case, it also reads the following case * * @verbatim valueString <\string> @endverbatim * * @param node Reference to the XML_Node object of the parent XML element * @param nameString Name of the XML Node input variable * @param valueString Value string that is found in the child node. output variable * @param typeString String type. This is an optional output variable. It is filled * with the attribute "type" of the XML entry. output variable */ void getNamedStringValue(const Cantera::XML_Node& node, const std::string& nameString, std::string& valueString, std::string& typeString) { valueString = ""; typeString = ""; if (node.hasChild(nameString)) { XML_Node& xc = node.child(nameString); valueString = xc.value(); typeString = xc["type"]; } else { XML_Node* s = getByTitle(node, nameString); if (s) { if (s->name() == "string") { valueString = (*s).value(); typeString = (*s)["type"]; return; } } } } //==================================================================================================================== // Get a vector of integer values from a child element. /* * Returns a std::map containing a keyed values for child XML_Nodes * of the current node with the name, "integer". * In the keyed mapping there will be a list of titles vs. values * for all of the XML nodes. * The integer XML_nodes are expected to be in a particular form created * by the function addInteger(). One value per XML_node is expected. * * * Example: * * Code snippet: * @verbatim const XML_Node &State_XMLNode; std::map v; getinteger(State_XMLNode, v); @endverbatim * * reads the corresponding XML file: * * @verbatim 1 <\integer> 2 <\integer> 3 <\integer> <\state> @endverbatim * * Will produce the mapping: * * v["i1"] = 1 * v["i2"] = 2 * v["i3"] = 3 * * * @param node Current XML node to get the values from * @param v Output map of the results. */ void getIntegers(const Cantera::XML_Node& node, std::map& v) { std::vector f; node.getChildren("integer",f); int n = static_cast(f.size()); integer x; std::string typ, title, vmin, vmax; for (int i = 0; i < n; i++) { const XML_Node& fi = *(f[i]); x = atoi(fi().c_str()); title = fi["title"]; vmin = fi["min"]; vmax = fi["max"]; if (vmin != "") if (fi["max"] != "") { v[title] = x; } } } //==================================================================================================================== // Get a vector of floating-point values from a child element. /* * Returns a std::map containing a keyed values for child XML_Nodes * of the current node with the name, "float". * In the keyed mapping there will be a list of titles vs. values * for all of the XML nodes. * The float XML_nodes are expected to be in a particular form created * by the function addFloat(). One value per XML_node is expected. * * * Example: * * Code snippet: * @verbatim const XML_Node &State_XMLNode; std::map v; bool convert = true; getFloats(State_XMLNode, v, convert); @endverbatim * * reads the corresponding XML file: * * @verbatim 32.4 <\float> 1. <\float> 100. <\float> <\state> @endverbatim * * Will produce the mapping: * * v["a1"] = 32.4 * v["a2"] = 1.0E-6 * v["a3"] = 100. * * * @param node Current XML node to get the values from * @param v Output map of the results. * @param convert Turn on conversion to SI units */ void getFloats(const Cantera::XML_Node& node, std::map& v, const bool convert) { std::vector f; node.getChildren("float",f); int n = static_cast(f.size()); doublereal x, x0, x1, fctr; std::string typ, title, units, vmin, vmax; for (int i = 0; i < n; i++) { const XML_Node& fi = *(f[i]); x = atof(fi().c_str()); x0 = Undef; x1 = Undef; typ = fi["type"]; title = fi["title"]; units = fi["units"]; vmin = fi["min"]; vmax = fi["max"]; if (vmin != "") { x0 = atof(vmin.c_str()); if (x < x0 - Tiny) { writelog("\nWarning: value "+fi()+" is below lower limit of " +vmin+".\n"); } } if (fi["max"] != "") { x1 = atof(vmax.c_str()); if (x > x1 + Tiny) { writelog("\nWarning: value "+fi()+" is above upper limit of " +vmax+".\n"); } } fctr = (convert ? toSI(units) : 1.0); // toSI(typ,units); v[title] = fctr*x; } } //==================================================================================================================== // Get a floating-point value from a child element. /* * Returns a double value for the child named 'name' of element 'parent'. If * 'type' is supplied and matches a known unit type, unit * conversion to SI will be done if the child element has an attribute * 'units'. * * Note, it's an error for the child element not to exist. * * Example: * * Code snippet: * @verbatim const XML_Node &State_XMLNode; doublereal pres = OneAtm; if (state_XMLNode.hasChild("pressure")) { pres = getFloat(State_XMLNode, "pressure", "toSI"); } @endverbatim * * reads the corresponding XML file: * @verbatim 101325.0 <\state> @endverbatim * * @param parent reference to the XML_Node object of the parent XML element * @param name Name of the XML child element * @param type String type. Currently known types are "toSI" and "actEnergy", * and "" , for no conversion. The default value is "" * which implies that no conversion is allowed. */ doublereal getFloat(const Cantera::XML_Node& parent, const std::string& name, const std::string type) { if (!parent.hasChild(name)) throw CanteraError("getFloat (called from XML Node \"" + parent.name() + "\"): ", "no child XML element named \"" + name + "\" exists"); const XML_Node& node = parent.child(name); return getFloatCurrent(node, type); } //==================================================================================================================== // Get a floating-point value from the current XML element /* * Returns a doublereal value from the current element. If * 'type' is supplied and matches a known unit type, unit * conversion to SI will be done if the child element has an attribute 'units'. * * Note, it's an error for the child element not to exist. * * Example: * * Code snippet: * @verbatim const XML_Node &State_XMLNode; doublereal pres = OneAtm; if (state_XMLNode.hasChild("pressure")) { XML_Node *pres_XMLNode = State_XMLNode.getChild("pressure"); pres = getFloatCurrent(pres_XMLNode, "toSI"); } @endverbatim * * Rreads the corresponding XML file: * @verbatim 101325.0 <\state> @endverbatim * * @param currXML reference to the current XML_Node object * @param type String type. Currently known types are "toSI" and "actEnergy", * and "" , for no conversion. The default value is "", * which implies that no conversion is allowed. */ doublereal getFloatCurrent(const Cantera::XML_Node& node, const std::string type) { doublereal x, x0, x1, fctr = 1.0; string units, vmin, vmax; x = atof(node().c_str()); x0 = Undef; x1 = Undef; units = node["units"]; vmin = node["min"]; vmax = node["max"]; if (vmin != "") { x0 = atof(vmin.c_str()); if (x < x0 - Tiny) { writelog("\nWarning: value "+node()+" is below lower limit of " +vmin+".\n"); } } if (node["max"] != "") { x1 = atof(vmax.c_str()); if (x > x1 + Tiny) { writelog("\nWarning: value "+node()+" is above upper limit of " +vmax+".\n"); } } // Note, most type's of converters default to toSI() type atm. // This may change and become more specific in the future. if (type == "actEnergy" && units != "") { fctr = actEnergyToSI(units); } else if (type == "toSI" && units != "") { fctr = toSI(units); } else if (type == "temperature" && units != "") { fctr = toSI(units); } else if (type == "density" && units != "") { fctr = toSI(units); } else if (type == "pressure" && units != "") { fctr = toSI(units); } else if (type != "" && units != "") { fctr = toSI(units); #ifdef DEBUG_MODE writelog("\nWarning: conversion toSI() was done on node value " + node.name() + "but wasn't explicity requested. Type was \"" + type + "\"\n"); #endif } // Note, below currently produces a lot of output due to transport blocks. // This needs to be addressed. #ifdef DEBUG_MODE_MORE else if (type == "" && units != "") { writelog("\nWarning: XML node " + node.name() + "has a units attribute, \"" + units + "\"," "but no conversion was done because the getFloat() command didn't have a type\n"); } #endif return fctr*x; } //==================================================================================================================== bool getOptionalFloat(const Cantera::XML_Node& parent, const std::string& name, doublereal& fltRtn, const std::string type) { if (parent.hasChild(name)) { fltRtn= getFloat(parent, name, type); return true; } return false; } //==================================================================================================================== // Get an optional floating-point value from a child element. /* * Returns a doublereal value for the child named 'name' of element 'parent'. If * 'type' is supplied and matches a known unit type, unit * conversion to SI will be done if the child element has an attribute * 'units'. * * * * Example: * * Code snippet: * @verbatim const XML_Node &State_XMLNode; doublereal pres = OneAtm; bool exists = getOptionalFloat(State_XMLNode, "pressure", pres, "toSI"); @endverbatim * * reads the corresponding XML file: * @verbatim 101325.0 <\state> @endverbatim * * @param parent reference to the XML_Node object of the parent XML element * @param name Name of the XML child element * @param fltRtn Float Return. It will be overridden if the XML * element exists. * @param type String type. Currently known types are "toSI" * and "actEnergy", * and "" , for no conversion. The default value is "", * which implies that no conversion is allowed. * * @return returns true if the child element named "name" exists */ doublereal getFloatDefaultUnits(const Cantera::XML_Node& parent, std::string name, std::string defaultUnits, std::string type) { doublereal fctr = 1.0; if (defaultUnits == "") { throw CanteraError("getFloatDefaultUnits", "need to supply an actual value of defaultUnits"); } if (type == "actEnergy") { fctr = actEnergyToSI(defaultUnits); } else if (type == "toSI") { fctr = toSI(defaultUnits); } else if (defaultUnits == "temperature") { fctr = toSI(defaultUnits); } else if (type == "density") { fctr = toSI(defaultUnits); } else if (type == "pressure") { fctr = toSI(defaultUnits); } else { throw CanteraError("getFloatDefaultUnits", "type of units must be supplied and understood"); } doublereal val = getFloat(parent, name, type); val /= fctr; return val; } //==================================================================================================================== // Get an optional model name from a named child node. /* * Returns the model name attribute for the child named 'nodeName' of element 'parent'. * Note, it's optional for the child node to exist * * Example: * * Code snippet: * @verbatim std::string modelName = ""; bool exists = getOptionalModel(transportNode, "compositionDependence", modelName); @endverbatim * * Reads the corresponding XML file: * * @verbatim @endverbatim * * On return modelName is set to "Solvent_Only". * * @param parent Reference to the XML_Node object of the parent XML element * @param nodeName Name of the XML child element * @param modelName On return this contains the contents of the model attribute * * @return True if the nodeName XML node exists. False otherwise. */ bool getOptionalModel(const Cantera::XML_Node& parent, const std::string nodeName, std::string& modelName) { if (parent.hasChild(nodeName)) { const XML_Node& node = parent.child(nodeName); modelName = node["model"]; return true; } return false; } //==================================================================================================================== // Get an integer value from a child element. /* * Returns an integer value for the child named 'name' of element 'parent'. * * Note, it's an error for the child element not to exist. * * Example: * * Code snippet: * @verbatim const XML_Node &State_XMLNode; int number = 1; if (state_XMLNode.hasChild("NumProcs")) { number = getInteger(State_XMLNode, "numProcs"); } @endverbatim * * reads the corresponding XML file: * @verbatim 10 <\numProcs> <\state> @endverbatim * * @param parent reference to the XML_Node object of the parent XML element * @param name Name of the XML child element */ int getInteger(const Cantera::XML_Node& parent, std::string name) { if (!parent.hasChild(name)) { throw CanteraError("getInteger (called from XML Node \"" + parent.name() + "\"): ", "no child XML element named " + name); } const XML_Node& node = parent.child(name); int x, x0, x1; string units, vmin, vmax; x = atoi(node().c_str()); x0 = -9999999; x1 = 9999999; vmin = node["min"]; vmax = node["max"]; if (vmin != "") { x0 = atoi(vmin.c_str()); if (x < x0) { writelog("\nWarning: value "+node()+" is below lower limit of " +vmin+".\n"); } } if (node["max"] != "") { x1 = atoi(vmax.c_str()); if (x > x1) { writelog("\nWarning: value "+node()+" is above upper limit of " +vmax+".\n"); } } return x; } //==================================================================================================================== // This function reads the current node or a child node of the current node // with the default name, "floatArray", with a value field // consisting of a comma separated list of floats /* * This function will read either the current XML node or a child node * to the current XML node, with the * name "floatArray". It will have a title attribute, and the body * of the XML node will be filled out with a comma separated list of * doublereals. * Get an array of floats from the XML Node. The argument field * is assumed to consist of an arbitrary number of comma * separated floats, with an arbitrary amount of white space * separating each field. * If the node array has an units attribute field, then * the units are used to convert the floats, iff convert is true. * * Example: * * Code snippet: * @verbatim const XML_Node &State_XMLNode; vector_fp v; bool convert = true; unitsString = ""; nodeName="floatArray"; getFloatArray(State_XMLNode, v, convert, unitsString, nodeName); @endverbatim * * reads the corresponding XML file: * * @verbatim 32.4, 1, 100. <\floatArray> <\state> @endverbatim * * Will produce the vector * * v[0] = 32.4 * v[1] = 1.0 * v[2] = 100. * * * @param node XML parent node of the floatArray * @param v Output vector of floats containing the floatArray information. * @param convert Conversion to SI is carried out if this boolean is * True. The default is true. * @param unitsString String name of the type attribute. This is an optional * parameter. The default is to have an empty string. * The only string that is recognized is actEnergy. * Anything else has no effect. This affects what * units converter is used. * @param nodeName XML Name of the XML node to read. * The default value for the node name is floatArray * * @return Returns the number of floats read * * @note change the v to a std::vector to eliminate a doxygen error. No idea why doxygen needs this. */ size_t getFloatArray(const Cantera::XML_Node& node, std::vector & v, const bool convert, const std::string unitsString, const std::string nodeName) { std::string::size_type icom; string numstr; doublereal dtmp; string nn = node.name(); const Cantera::XML_Node* readNode = &node; if (nn != nodeName) { vector ll; node.getChildren(nodeName, ll); if (ll.size() == 0) { throw CanteraError("getFloatArray", "wrong xml element type/name: was expecting " + nodeName + "but accessed " + node.name()); } else { readNode = ll[0]; ll.clear(); readNode->getChildren("floatArray", ll); if (ll.size() > 0) { readNode = ll[0]; } } } v.clear(); doublereal vmin = Undef, vmax = Undef; doublereal funit = 1.0; /* * Get the attributes field, units, from the XML node */ std::string units = (*readNode)["units"]; if (units != "" && convert) { if (unitsString == "actEnergy" && units != "") { funit = actEnergyToSI(units); } else if (unitsString != "" && units != "") { funit = toSI(units); } } if ((*readNode)["min"] != "") { vmin = atofCheck((*readNode)["min"].c_str()); } if ((*readNode)["max"] != "") { vmax = atofCheck((*readNode)["max"].c_str()); } doublereal vv; std::string val = readNode->value(); while (1 > 0) { icom = val.find(','); if (icom != string::npos) { numstr = val.substr(0,icom); val = val.substr(icom+1,val.size()); dtmp = atofCheck(numstr.c_str()); v.push_back(dtmp); } else { /* * This little bit of code is to allow for the * possibility of a comma being the last * item in the value text. This was allowed in * previous versions of Cantera, even though it * would appear to be odd. So, we keep the * possibilty in for backwards compatibility. */ if (!val.empty()) { dtmp = atofCheck(val.c_str()); v.push_back(dtmp); } break; } vv = v.back(); if (vmin != Undef && vv < vmin - Tiny) { writelog("\nWarning: value "+fp2str(vv)+ " is below lower limit of " +fp2str(vmin)+".\n"); } if (vmax != Undef && vv > vmax + Tiny) { writelog("\nWarning: value "+fp2str(vv)+ " is above upper limit of " +fp2str(vmin)+".\n"); } } for (size_t n = 0; n < v.size(); n++) { v[n] *= funit; } return v.size(); } //==================================================================================================================== size_t getNamedFloatArray(const Cantera::XML_Node& parentNode, const std::string& nodeName, std::vector & v, const bool convert, const std::string unitsString) { std::string::size_type icom; std::string numstr; doublereal dtmp; std::string nn = parentNode.name(); v.clear(); const Cantera::XML_Node* readNode = parentNode.findByName(nodeName); if (!readNode) { return 0; } doublereal vmin = Undef; doublereal vmax = Undef; doublereal funit = 1.0; /* * Get the attributes field, units, from the XML node */ std::string units = (*readNode)["units"]; if (units != "" && convert) { if (unitsString == "actEnergy" && units != "") { funit = actEnergyToSI(units); } else if (unitsString != "" && units != "") { funit = toSI(units); } } if ((*readNode)["min"] != "") { vmin = atofCheck((*readNode)["min"].c_str()); } if ((*readNode)["max"] != "") { vmax = atofCheck((*readNode)["max"].c_str()); } size_t expectedSize = 0; nn = (*readNode)["size"]; expectedSize = atoi(nn.c_str()); nn = (*readNode)["vtype"]; if (nn != "floatArray") { throw CanteraError("getNamedFloatArray", "node named " + nodeName + "didn't have correct vtype"); } doublereal vv; std::string val = readNode->value(); while (1 > 0) { icom = val.find(','); if (icom != string::npos) { numstr = val.substr(0,icom); val = val.substr(icom+1,val.size()); dtmp = atofCheck(numstr.c_str()); v.push_back(dtmp); } else { /* * This little bit of code is to allow for the * possibility of a comma being the last * item in the value text. This was allowed in * previous versions of Cantera, even though it * would appear to be odd. So, we keep the * possibilty in for backwards compatibility. */ size_t nlen = strlen(val.c_str()); if (nlen > 0) { dtmp = atofCheck(val.c_str()); v.push_back(dtmp); } break; } vv = v.back(); if (vmin != Undef && vv < vmin - Tiny) { writelog("\nWarning: value "+fp2str(vv)+ " is below lower limit of " +fp2str(vmin)+".\n"); } if (vmax != Undef && vv > vmax + Tiny) { writelog("\nWarning: value "+fp2str(vv)+ " is above upper limit of " +fp2str(vmin)+".\n"); } } size_t nv = v.size(); for (size_t n = 0; n < nv; n++) { v[n] *= funit; } if (nv != expectedSize) { throw CanteraError("getNamedFloatArray", "node named " + nodeName + "didn't have correct number of floats" + int2str(expectedSize) + " vs " + int2str(nv)); } return nv; } //==================================================================================================================== // This routine is used to interpret the value portions of XML // elements that contain colon separated pairs. /* * These are used, for example, in describing the element * composition of species. * * H:4 C:1 * * The string is first separated into a string vector according * to the location of white space. Then each string is again * separated into two parts according to the location of a * colon in the string. The first part of the string is * used as the key, while the second part of the string is * used as the value, in the return map. * It is an error to not find a colon in each string pair. * * @param node Current node * @param m Output Map containing the pairs of values found * in the XML Node */ void getMap(const Cantera::XML_Node& node, std::map& m) { std::vector v; getStringArray(node, v); std::string key, val; int n = static_cast(v.size()); string::size_type icolon; for (int i = 0; i < n; i++) { icolon = v[i].find(":"); if (icolon == string::npos) { throw CanteraError("getMap","missing colon in map entry (" +v[i]+")"); } key = v[i].substr(0,icolon); val = v[i].substr(icolon+1, v[i].size()); m[key] = val; } } //==================================================================================================================== // This function interprets the value portion of an XML element // as a series of "Pairs" separated by white space. /* * Each pair consists of nonwhite-space characters. * The first ":" found in the pair string is used to separate * the string into two parts. The first part is called the "key" * The second part is called the "val". * String vectors of key[i] and val[i] are returned in the * argument list. * Warning: No spaces are allowed in each pair. Quotes get included as part * of the string. * Example: @verbatim red:112 blue:34 green:banana @endverbatim * * Returns: * key val * 0: "red" "112" * 1: "blue" "34" * 2: "green" "banana" * * * @param node XML Node * @param key Vector of keys for each entry * @param val Vector of values for each entry * * @return Returns the number of pairs found */ int getPairs(const Cantera::XML_Node& node, std::vector& key, std::vector& val) { vector v; getStringArray(node, v); int n = static_cast(v.size()); string::size_type icolon; for (int i = 0; i < n; i++) { icolon = v[i].find(":"); if (icolon == string::npos) { throw CanteraError("getPairs","Missing a colon in the Pair entry (" +v[i]+")"); } key.push_back(v[i].substr(0,icolon)); val.push_back(v[i].substr(icolon+1, v[i].size())); //cout << "getPairs: " << key.back() << " " << val.back() << endl; } return n; } //==================================================================================================================== // This function interprets the value portion of an XML element // as a series of "Matrix ids and entries" separated by white space. /* * Each pair consists of nonwhite-space characters. * The first two ":" found in the pair string is used to separate * the string into three parts. The first part is called the first * key. The second part is the second key. Both parts must match * an entry in the keyString1 and keyString2, respectively, * in order to provide a location to * place the object in the matrix. * The third part is called the value. It is expected to be * a double. It is translated into a double and placed into the * correct location in the matrix. * * Warning: No spaces are allowed in each triplet. Quotes are part * of the string. * Example * keyString = red, blue, black, green * * red:green:112 * blue:black:3.3E-23 * * * * Returns: * retnValues(0, 3) = 112 * retnValues(1, 2) = 3.3E-23 * * * @param node XML Node containing the information for the matrix * @param keyStringRow Key string for the row * @param keyStringCol Key string for the column entries * @param returnValues Return Matrix. * @param convert If this is true, and if the node has a units * attribute, then conversion to si units is carried * out. Default is true. * @param matrixSymmetric If true entries are made so that the matrix * is always symmetric. Default is false. */ void getMatrixValues(const Cantera::XML_Node& node, const std::vector& keyStringRow, const std::vector& keyStringCol, Cantera::Array2D& retnValues, const bool convert, const bool matrixSymmetric) { size_t szKey1 = keyStringRow.size(); size_t szKey2 = keyStringCol.size(); size_t nrow = retnValues.nRows(); size_t ncol = retnValues.nColumns(); if (szKey1 > nrow) { throw CanteraError("getMatrixValues", "size of key1 greater than numrows"); } if (szKey2 > ncol) { throw CanteraError("getMatrixValues", "size of key2 greater than num cols"); } if (matrixSymmetric) { if (nrow != ncol) { throw CanteraError("getMatrixValues", "nrow != ncol for a symmetric matrix"); } } /* * Get the attributes field, units, from the XML node * and determine the conversion factor, funit. */ doublereal funit = 1.0; string units = node["units"]; if (units != "" && convert) { funit = toSI(units); } string key1; string key2; string rmm; string val; vector v; getStringArray(node, v); string::size_type icolon; for (size_t i = 0; i < v.size(); i++) { icolon = v[i].find(":"); if (icolon == string::npos) { throw CanteraError("getMatrixValues","Missing two colons (" +v[i]+")"); } key1 = v[i].substr(0,icolon); rmm = v[i].substr(icolon+1, v[i].size()); icolon = rmm.find(":"); if (icolon == string::npos) { throw CanteraError("getMatrixValues","Missing one colon (" +v[i]+")"); } key2 = rmm.substr(0,icolon); val = rmm.substr(icolon+1, rmm.size()); size_t icol = -1; size_t irow = -1; for (size_t j = 0; j < szKey1; j++) { if (key1 == keyStringRow[j]) { irow = j; break; } } if (irow == npos) { throw CanteraError("getMatrixValues","Row not matched by string: " + key1); } for (size_t j = 0; j < szKey2; j++) { if (key2 == keyStringCol[j]) { icol = j; break; } } if (icol == npos) { throw CanteraError("getMatrixValues","Col not matched by string: " + key2); } double dval = atofCheck(val.c_str()); dval *= funit; /* * Finally, insert the value; */ retnValues(irow, icol) = dval; if (matrixSymmetric) { retnValues(icol, irow) = dval; } } } //==================================================================================================================== // This function interprets the value portion of an XML element // as a string. It then separates the string up into tokens // according to the location of white space. /* * The separate tokens are returned in the string vector * * @param node Node to get the value from * @param v Output vector containing the string tokens */ void getStringArray(const Cantera::XML_Node& node, std::vector& v) { std::string val = node.value(); tokenizeString(val, v); } //==================================================================================================================== // This function reads a child node with the default name, "floatArray", with a value // consisting of a comma separated list of floats /* * This function will read a child node to the current XML node, with the * name "floatArray". It will have a title attribute, and the body * of the XML node will be filled out with a comma separated list of * doublereals. * Get an array of floats from the XML Node. The argument field * is assumed to consist of an arbitrary number of comma * separated floats, with an arbitrary amount of white space * separating each field. * If the node array has an units attribute field, then * the units are used to convert the floats, iff convert is true. * This function is a wrapper around the function getFloatArray(). * * Example: * * Code snippet: * @verbatim const XML_Node &State_XMLNode; vector_fp v; bool convert = true; unitsString = ""; nodeName="floatArray"; getFloatArray(State_XMLNode, v, convert, unitsString, nodeName); @endverbatim * * reads the corresponding XML file: * * @verbatim 32.4, 1, 100. <\floatArray> <\state> @endverbatim * * Will produce the vector * * v[0] = 32.4 * v[1] = 1.0 * v[2] = 100. * * * @param node XML parent node of the floatArray * @param typeString Returns the type attribute of the current node. * @param xmin Returns the minimum value attribute of the * current node. * @param xmax Returns the maximum value attribute of the * current node. * @param coeffs Output vector of floats containing the floatArray information. */ void getFunction(const Cantera::XML_Node& node, std::string& type, doublereal& xmin, doublereal& xmax, std::vector & coeffs) { const XML_Node& c = node.child("floatArray"); coeffs.clear(); getFloatArray(c,coeffs); xmin = Cantera::Undef; xmin = Cantera::Undef; if (node["min"] != "") { xmin = fpValue(node["min"]); } if (node["max"] != "") { xmax = fpValue(node["max"]); } type = node["type"]; } //==================================================================================================================== }