861 lines
29 KiB
C++
Executable file
861 lines
29 KiB
C++
Executable file
|
|
/**
|
|
* @file misc.cpp
|
|
*
|
|
*
|
|
*/
|
|
|
|
#ifdef WIN32
|
|
#pragma warning(disable:4786)
|
|
#pragma warning(disable:4503)
|
|
#endif
|
|
|
|
#include "global.h"
|
|
#include "ctexceptions.h"
|
|
#include "stringUtils.h"
|
|
#include "units.h"
|
|
#include "xml.h"
|
|
#include "ctml.h"
|
|
#include "SpeciesThermoFactory.h"
|
|
#include "ThermoFactory.h"
|
|
#include "FalloffFactory.h"
|
|
#include "logger.h"
|
|
|
|
#undef DEBUG_PATHS
|
|
|
|
#include <fstream>
|
|
using namespace std;
|
|
|
|
namespace Cantera {
|
|
|
|
/**
|
|
* Class to hold global data. Class Application is the top-level
|
|
* class that stores data that should persist for the duration of
|
|
* the process. The class should not be instantiated directly;
|
|
* instead, it is instantiated as needed by the functions declared
|
|
* here. At most one instance is created, and it is not destroyed
|
|
* until the process terminates.
|
|
*/
|
|
class Application {
|
|
public:
|
|
Application() : linelen(0), stop_on_error(false),
|
|
tmp_dir("."), sleep("1")
|
|
{
|
|
// if TMP or TEMP is set, use it for the temporary
|
|
// directory
|
|
char* tmpdir = getenv("TMP");
|
|
if (tmpdir == 0)
|
|
tmpdir = getenv("TEMP");
|
|
if (tmpdir != 0)
|
|
tmp_dir = string(tmpdir);
|
|
|
|
// if SLEEP is set, use it as the sleep time
|
|
char* sleepstr = getenv("SLEEP");
|
|
if (sleepstr != 0) {
|
|
sleep = string(sleepstr);
|
|
}
|
|
|
|
// install a default logwriter that writes to standard
|
|
// output / standard error
|
|
logwriter = new Logger();
|
|
// HTML log files
|
|
xmllog = 0;
|
|
current = 0;
|
|
loglevel = 0;
|
|
}
|
|
|
|
/// Delete any open XML trees, the logwriter, and
|
|
/// the XML log, if any.
|
|
virtual ~Application() {
|
|
map<string, XML_Node*>::iterator pos;
|
|
for (pos = xmlfiles.begin(); pos != xmlfiles.end(); ++pos) {
|
|
pos->second->unlock();
|
|
delete pos->second;
|
|
pos->second = 0;
|
|
}
|
|
delete logwriter;
|
|
if (xmllog) {
|
|
write_logfile("orphan");
|
|
//delete xmllog;
|
|
}
|
|
}
|
|
|
|
vector<string> inputDirs;
|
|
vector<string> errorMessage;
|
|
vector<string> warning;
|
|
vector<string> errorRoutine;
|
|
string msglog;
|
|
size_t linelen;
|
|
bool stop_on_error;
|
|
map<string, string> options;
|
|
string tmp_dir;
|
|
map<string, XML_Node*> xmlfiles;
|
|
string sleep;
|
|
Logger* logwriter;
|
|
XML_Node *xmllog, *current;
|
|
int loglevel;
|
|
vector<int> loglevels;
|
|
vector<string> loggroups;
|
|
};
|
|
|
|
|
|
|
|
/// Return a pointer to the one and only instance of class Application
|
|
Application* app();
|
|
|
|
void setDefaultDirectories();
|
|
|
|
/// Pointer to the single Application instance
|
|
static Application* s_app = 0;
|
|
|
|
/**
|
|
* Definition of the static member of the Unit class.
|
|
*/
|
|
Unit* Unit::s_u = 0;
|
|
|
|
static void appinit() {
|
|
if (s_app == 0) {
|
|
s_app = new Application;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete all global data. It should be called at the end of the
|
|
* application if leak checking is to be done.
|
|
*/
|
|
void appdelete() {
|
|
if (s_app) {
|
|
delete s_app;
|
|
s_app = 0;
|
|
}
|
|
SpeciesThermoFactory::deleteFactory();
|
|
ThermoFactory::deleteFactory();
|
|
FalloffFactory::deleteFalloffFactory();
|
|
Unit::deleteUnit();
|
|
}
|
|
|
|
Application* app() {
|
|
if (s_app == 0) {
|
|
s_app = new Application;
|
|
setDefaultDirectories();
|
|
}
|
|
return s_app;
|
|
}
|
|
|
|
|
|
XML_Node* get_XML_File(string file) {
|
|
string path = "";
|
|
/*
|
|
try {
|
|
path = findInputFile(file);
|
|
}
|
|
catch (CanteraError) {
|
|
string::size_type idot = file.rfind('.');
|
|
string ext = "";
|
|
if (idot != string::npos) {
|
|
ext = file.substr(idot, file.size());
|
|
string ctifile = file.substr(0,idot)+".cti";
|
|
try {
|
|
path = findInputFile(ctifile);
|
|
}
|
|
catch (CanteraError) {
|
|
path = findInputFile(file);
|
|
}
|
|
}
|
|
else
|
|
path = findInputFile(file);
|
|
}
|
|
*/
|
|
// The code above will try to process a cti file if the xml
|
|
// file is not found. But I (dgg) don't think it makes much sense,
|
|
// so it is replaced by:
|
|
path = findInputFile(file);
|
|
//
|
|
|
|
string ff = path;
|
|
if (app()->xmlfiles.find(path)
|
|
== app()->xmlfiles.end()) {
|
|
/*
|
|
* Check whether or not the file is XML. If not, it will
|
|
* be first processed with the preprocessor. We determine
|
|
* whether it is an XML file by looking at the file extension.
|
|
*/
|
|
string::size_type idot = path.rfind('.');
|
|
string ext;
|
|
if (idot != string::npos) {
|
|
ext = path.substr(idot, path.size());
|
|
} else {
|
|
ext = "";
|
|
idot = path.size();
|
|
}
|
|
if (ext != ".xml" && ext != ".ctml") {
|
|
/*
|
|
* We will assume that we are trying to open a cti file.
|
|
* First, determine the name of the xml file, ff, derived from
|
|
* the cti file.
|
|
* In all cases, we will write the xml file to the current
|
|
* directory.
|
|
*/
|
|
string::size_type islash = path.rfind('/');
|
|
if (islash != string::npos)
|
|
ff = string("./")+path.substr(islash+1,idot-islash - 1) + ".xml";
|
|
else {
|
|
ff = string("./")+path.substr(0,idot) + ".xml";
|
|
}
|
|
#ifdef DEBUG_PATHS
|
|
cout << "get_XML_File(): Expected location of xml file = "
|
|
<< ff << endl;
|
|
#endif
|
|
/*
|
|
* Do a search of the existing XML trees to determine if we have
|
|
* already processed this file. If we have, return a pointer to
|
|
* the processed xml tree.
|
|
*/
|
|
if (app()->xmlfiles.find(ff) != app()->xmlfiles.end()) {
|
|
#ifdef DEBUG_PATHS
|
|
cout << "get_XML_File(): File, " << ff << ", was previously read."
|
|
<< " Retrieving the storred xml tree." << endl;
|
|
#endif
|
|
return s_app->xmlfiles[ff];
|
|
}
|
|
/*
|
|
* Ok, we didn't find the processed XML tree. Do the conversion
|
|
* to xml, possibly overwriting the file, ff, in the process.
|
|
*/
|
|
ctml::ct2ctml(path.c_str());
|
|
}
|
|
else {
|
|
ff = path;
|
|
}
|
|
/*
|
|
* Take the XML file ff, open it, and process it, creating an
|
|
* XML tree, and then adding an entry in the map. We will store
|
|
* the absolute pathname as the key for this map.
|
|
*/
|
|
ifstream s(ff.c_str());
|
|
XML_Node* x = new XML_Node("doc");
|
|
if (s) {
|
|
x->build(s);
|
|
x->lock();
|
|
s_app->xmlfiles[ff] = x;
|
|
}
|
|
else {
|
|
string estring = "cannot open "+ff+" for reading.";
|
|
estring += "Note, this error indicates a possible configuration problem.";
|
|
throw CanteraError("get_XML_File", estring);
|
|
}
|
|
}
|
|
/*
|
|
* Return the XML node pointer. At this point, we are sure that the
|
|
* lookup operation in the return statement will return a valid
|
|
* pointer.
|
|
*/
|
|
return s_app->xmlfiles[ff];
|
|
}
|
|
|
|
void close_XML_File(string file) {
|
|
if (file == "all") {
|
|
map<string, XML_Node*>::iterator
|
|
b = app()->xmlfiles.begin(), e = app()->xmlfiles.end();
|
|
for(; b != e; ++b) {
|
|
b->second->unlock();
|
|
delete b->second;
|
|
s_app->xmlfiles.erase(b->first);
|
|
}
|
|
}
|
|
else if (app()->xmlfiles.find(file)
|
|
!= app()->xmlfiles.end()) {
|
|
s_app->xmlfiles[file]->unlock();
|
|
delete s_app->xmlfiles[file];
|
|
s_app->xmlfiles.erase(file);
|
|
}
|
|
}
|
|
|
|
void setTmpDir(string tmp) { app()->tmp_dir = tmp; }
|
|
string tmpDir() { appinit(); return app()->tmp_dir; }
|
|
string sleep() { appinit(); return app()->sleep; }
|
|
|
|
|
|
/**
|
|
* Return the number of errors that have been encountered so far.
|
|
* \ingroup errorhandling
|
|
*/
|
|
int nErrors() {
|
|
return static_cast<int>(app()->errorMessage.size());
|
|
}
|
|
|
|
/**
|
|
* popError eliminates the last error message that Cantera
|
|
* has saved. Cantera saves a stack of exceptions that it
|
|
* has caught in the Application class. This routine eliminates
|
|
* the last exception to be added to that stack.
|
|
* \ingroup errorhandling
|
|
*/
|
|
void popError() {
|
|
appinit();
|
|
if (nErrors() > 0) {
|
|
s_app->errorMessage.pop_back();
|
|
s_app->errorRoutine.pop_back();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the last error message.
|
|
* This routine will retrieve the last error message and return
|
|
* it in the return string.
|
|
* \ingroup errorhandling
|
|
*/
|
|
string lastErrorMessage() {
|
|
appinit();
|
|
if (nErrors() > 0) {
|
|
string head =
|
|
"\n\n************************************************\n"
|
|
" Cantera Error! \n"
|
|
"************************************************\n\n";
|
|
return head+string("\nProcedure: ")+s_app->errorRoutine.back()
|
|
+string("\nError: ")+s_app->errorMessage.back();
|
|
}
|
|
else {
|
|
return "<no Cantera error>";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints all of the error messages to stream f.
|
|
* Write out to ostream, f, all of the saved error messages.
|
|
* Cantera saves a stack of exceptions that it
|
|
* has caught in the Application class. This routine writes
|
|
* out all of the error messages to ostream f, and then
|
|
* clears them from internal storage.
|
|
* \ingroup errorhandling
|
|
*/
|
|
void showErrors(ostream& f) {
|
|
appinit();
|
|
int i = static_cast<int>(s_app->errorMessage.size());
|
|
if (i == 0) return;
|
|
f << endl << endl;
|
|
f << "************************************************" << endl;
|
|
f << " Cantera Error! " << endl;
|
|
f << "************************************************" << endl
|
|
<< endl;
|
|
int j;
|
|
for (j = 0; j < i; j++) {
|
|
f << endl;
|
|
f << "Procedure: " << s_app->errorRoutine[j] << endl;
|
|
f << "Error: " << s_app->errorMessage[j] << endl;
|
|
}
|
|
f << endl << endl;
|
|
s_app->errorMessage.clear();
|
|
s_app->errorRoutine.clear();
|
|
}
|
|
|
|
/**
|
|
* Print all of the error messages using function writelog.
|
|
* Write out all of the saved error messages to the log device.
|
|
* Cantera saves a stack of exceptions that it
|
|
* has caught in the Application class. This routine writes
|
|
* out all of the error messages to the log, usually stdout,
|
|
* and then clears them from internal storage.
|
|
* \ingroup errorhandling
|
|
*/
|
|
void showErrors() {
|
|
appinit();
|
|
int i = static_cast<int>(s_app->errorMessage.size());
|
|
if (i == 0) return;
|
|
writelog("\n\n");
|
|
writelog("************************************************\n");
|
|
writelog(" Cantera Error! \n");
|
|
writelog("************************************************\n\n");
|
|
int j;
|
|
for (j = 0; j < i; j++) {
|
|
writelog("\n");
|
|
writelog(string("Procedure: ")+ s_app->errorRoutine[j]+" \n");
|
|
writelog(string("Error: ")+s_app->errorMessage[j]+" \n");
|
|
}
|
|
writelog("\n\n");
|
|
s_app->errorMessage.clear();
|
|
s_app->errorRoutine.clear();
|
|
}
|
|
|
|
/**
|
|
* Set an error condition in the application class without
|
|
* throwing an exception.
|
|
* This routine adds an error message to the end of the stack
|
|
* of errors that Cantera accumulates in the Application
|
|
* class.
|
|
* \ingroup errorhandling
|
|
*/
|
|
void setError(string r, string msg) {
|
|
appinit();
|
|
s_app->errorMessage.push_back(msg);
|
|
s_app->errorRoutine.push_back(r);
|
|
}
|
|
|
|
/// @defgroup inputfiles Input File Handling
|
|
/// The properties of phases and interfaces are specified in
|
|
/// text files. These procedures handle various aspects of reading
|
|
/// these files.
|
|
|
|
/**
|
|
* Set the default directories for input files. Cantera searches
|
|
* for input files along a path that includes platform-specific
|
|
* default locations, and possibly user-specified locations. This
|
|
* function installs the platform-specific directories on the
|
|
* search path. It is invoked at startup by appinit(), and never
|
|
* should need to be called by user programs.
|
|
*
|
|
* The current directory (".") is always searched first. Then, on
|
|
* Windows platforms, if environment variable COMMONPROGRAMFILES
|
|
* is set (which it should be on Win XP or Win 2000), then
|
|
* directories under this one will be added to the search
|
|
* path. The Cantera Windows installer installs data files to this
|
|
* location.
|
|
*
|
|
* On the Mac, directory '/Applications/Cantera/data' is added to the
|
|
* search path.
|
|
*
|
|
* On any platform, if environment variable CANTERA_DATA is set to a
|
|
* directory name, then this directory is added to the search path.
|
|
*
|
|
* Finally, the location where the data files were installed when
|
|
* Cantera was built is added to the search path.
|
|
*
|
|
* Additional directories may be added by calling function addDirectory.
|
|
* @ingroup inputfiles
|
|
*/
|
|
void setDefaultDirectories() {
|
|
appinit();
|
|
vector<string>& dirs = s_app->inputDirs;
|
|
|
|
// always look in the local directory first
|
|
dirs.push_back(".");
|
|
|
|
|
|
#ifdef WIN32
|
|
//
|
|
// Under Windows, the Cantera setup utility puts data files in
|
|
// a directory 'Cantera\data' below the one the environment
|
|
// variable COMMONPROGRAMFILES points to. (This is usually
|
|
// C:\Program Files\Common Files.) If this environment
|
|
// variable is defined, then this directory is assumed to
|
|
// exist and is added to the search path.
|
|
//
|
|
const char* comfiles = getenv("COMMONPROGRAMFILES");
|
|
if (comfiles != 0) {
|
|
string cfiles = string(comfiles);
|
|
|
|
// remove quotes if necessary
|
|
if (cfiles[0] == '\'')
|
|
cfiles = cfiles.substr(1,1000);
|
|
if (cfiles[cfiles.size()-1] == '\'') cfiles[cfiles.size()-1] = '\n';
|
|
|
|
string datadir = string(comfiles) + "/Cantera/data";
|
|
string tmpldir = string(comfiles) + "/Cantera/templates";
|
|
dirs.push_back(datadir);
|
|
dirs.push_back(tmpldir);
|
|
}
|
|
#endif
|
|
|
|
#ifdef DARWIN
|
|
//
|
|
// add a default data location for Mac OS X
|
|
//
|
|
if (DARWIN > 0)
|
|
dirs.push_back("/Applications/Cantera/data");
|
|
#endif
|
|
|
|
//
|
|
// if environment variable CANTERA_DATA is defined, then add
|
|
// it to the search path
|
|
//
|
|
if (getenv("CANTERA_DATA") != 0) {
|
|
string datadir = string(getenv("CANTERA_DATA"));
|
|
dirs.push_back(datadir);
|
|
}
|
|
|
|
// CANTERA_DATA is defined in file config.h. This file is written
|
|
// during the build process (unix), and points to the directory
|
|
// specified by the 'prefix' option to 'configure', or else to
|
|
// /usr/local/cantera.
|
|
#ifdef CANTERA_DATA
|
|
string datadir = string(CANTERA_DATA);
|
|
dirs.push_back(datadir);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/// Add a directory to the input file search path.
|
|
/// @ingroup inputfiles
|
|
void addDirectory(string dir) {
|
|
appinit();
|
|
if (s_app->inputDirs.size() == 0) setDefaultDirectories();
|
|
string d = stripnonprint(dir);
|
|
size_t m, n = s_app->inputDirs.size();
|
|
|
|
// don't add if already present
|
|
for (m = 0; m < n; m++)
|
|
if (d == s_app->inputDirs[m]) return;
|
|
|
|
s_app->inputDirs.push_back(stripnonprint(dir));
|
|
}
|
|
|
|
/*!
|
|
* This routine will search for a file in the default
|
|
* locations specified for the application.
|
|
* See the routine setDefaultDirectories() listed above.
|
|
*
|
|
* The default set of directories specified for the application
|
|
* will be searched if a '/' or an '\\' is not found in
|
|
* name. If either is found then a relative path name is
|
|
* presumed and the default directories are not searched.
|
|
*
|
|
* The presence of the file is determined by whether the file
|
|
* can be opened for reading by the current user.
|
|
*
|
|
* \return
|
|
*
|
|
* The absolute path name of the first matching
|
|
* file is returned. If a relative path name
|
|
* is indicated, the relative path name is returned.
|
|
*
|
|
* If the file is not found, a message is written to
|
|
* stdout and a CanteraError exception is thrown.
|
|
*/
|
|
string findInputFile(string name) {
|
|
appinit();
|
|
string::size_type islash = name.find('/');
|
|
string::size_type ibslash = name.find('\\');
|
|
string inname;
|
|
vector<string>& dirs = s_app->inputDirs;
|
|
if (dirs.size() == 0) setDefaultDirectories();
|
|
|
|
int nd;
|
|
if (islash == string::npos && ibslash == string::npos) {
|
|
nd = static_cast<int>(dirs.size());
|
|
int i;
|
|
inname = "";
|
|
for (i = 0; i < nd; i++) {
|
|
inname = dirs[i] + "/" + name;
|
|
ifstream fin(inname.c_str());
|
|
if (fin) {
|
|
fin.close();
|
|
return inname;
|
|
}
|
|
}
|
|
string msg;
|
|
msg = "\nInput file " + name
|
|
+ " not found in director";
|
|
msg += (nd == 1 ? "y " : "ies ");
|
|
for (i = 0; i < nd; i++) {
|
|
msg += "\n'" + dirs[i] + "'";
|
|
if (i < nd-1) msg += ", ";
|
|
}
|
|
msg += "\n\n";
|
|
msg += "To fix this problem, either:\n";
|
|
msg += " a) move the missing files into the local directory;\n";
|
|
msg += " b) define environment variable CANTERA_DATA to\n";
|
|
msg += " point to the directory containing the file.";
|
|
throw CanteraError("findInputFile", msg);
|
|
return "";
|
|
}
|
|
//else {
|
|
return name;
|
|
//}
|
|
}
|
|
|
|
doublereal toSI(string unit) {
|
|
doublereal f = Unit::units()->toSI(unit);
|
|
if (f) return f;
|
|
else return 1.0;
|
|
}
|
|
|
|
doublereal actEnergyToSI(string unit) {
|
|
doublereal f = Unit::units()->actEnergyToSI(unit);
|
|
if (f) return f;
|
|
else return 1.0;
|
|
}
|
|
|
|
|
|
string canteraRoot() {
|
|
char* ctroot = 0;
|
|
ctroot = getenv("CANTERA_ROOT");
|
|
if (ctroot != 0) { return string(ctroot); }
|
|
else {
|
|
#ifdef CANTERA_ROOT
|
|
return string(CANTERA_ROOT);
|
|
#else
|
|
return "";
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
// exceptions
|
|
|
|
CanteraError::CanteraError(string proc, string msg) {
|
|
setError(proc, msg);
|
|
}
|
|
|
|
ArraySizeError::ArraySizeError(string proc, int sz, int reqd) :
|
|
CanteraError(proc, "Array size ("+int2str(sz)+
|
|
") too small. Must be at least "+int2str(reqd)) {}
|
|
|
|
ElementRangeError::ElementRangeError(string func, int m, int mmax) :
|
|
CanteraError(func, "Element index " + int2str(m) +
|
|
" outside valid range of 0 to " + int2str(mmax-1)) {}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
//
|
|
// Warnings
|
|
//
|
|
//////////////////////////////////////////////////////////
|
|
|
|
/// Print a warning when a deprecated method is called.
|
|
/// @param classnm Class the method belongs to
|
|
/// @param oldnm Name of the deprecated method
|
|
/// @param newnm Name of the method users should use instead
|
|
void deprecatedMethod(string classnm, string oldnm, string newnm) {
|
|
writelog(">>>> WARNING: method "+oldnm+" of class "+classnm
|
|
+" is deprecated.\n");
|
|
writelog(" Use method "+newnm+" instead.\n");
|
|
writelog(" (If you want to rescue this method from deprecated\n");
|
|
writelog(" status, see http://www.cantera.org/deprecated.html)");
|
|
}
|
|
|
|
|
|
void removeAtVersion(string func, string version) {
|
|
if (version >= "CANTERA_VERSION") {
|
|
writelog("Removed procedure: "+func+"\n");
|
|
writelog("Removed in version: "+version+"\n");
|
|
throw CanteraError("removeAtVersion","procedure has been removed.");
|
|
}
|
|
}
|
|
|
|
|
|
/// @defgroup logs Diagnostic Output
|
|
///
|
|
/// Writing diagnostic information to the screen or to a file.
|
|
/// It is often useful to be able to write diagnostic messages to
|
|
/// the screen or to a file. Cantera provides two sets of
|
|
/// procedures for this purpose. The first set is designed to
|
|
/// write text messages to the screen to document the progress of
|
|
/// a complex calculation, such as a flame simulation.The second
|
|
/// set writes nested lists in HTML format. This is useful to
|
|
/// print debugging output for a complex calculation that calls
|
|
/// many different procedures.
|
|
|
|
|
|
/// @defgroup textlogs Writing messages to the screen
|
|
/// @ingroup logs
|
|
|
|
/// Write a message to the screen. The string may be of any
|
|
/// length, and may contain end-of-line characters. This method is
|
|
/// used throughout Cantera to write log messages. It can also be
|
|
/// called by user programs. The advantage of using writelog over
|
|
/// writing directly to the standard output is that messages
|
|
/// written with writelog will display correctly even when Cantera
|
|
/// is used from MATLAB or other application that do not have a
|
|
/// standard output stream. @ingroup textlogs
|
|
void writelog(const string& msg) {
|
|
app()->logwriter->write(msg);
|
|
}
|
|
|
|
/// test
|
|
/// @ingroup textlogs
|
|
void writelog(const char* msg) {writelog(string(msg));}
|
|
|
|
/// Write an error message and terminate execution. test.
|
|
/// @ingroup textlogs
|
|
void error(const string& msg) {
|
|
app()->logwriter->error(msg);
|
|
}
|
|
|
|
/// test
|
|
/// @ingroup textlogs
|
|
int userInterface() {
|
|
appinit();
|
|
return app()->logwriter->env();
|
|
}
|
|
|
|
/// Install a logger. Called by the language interfaces to install an
|
|
/// appropriate logger.
|
|
/// @see Logger.
|
|
/// @ingroup textlogs
|
|
void setLogger(Logger* logwriter) {
|
|
appinit();
|
|
delete s_app->logwriter;
|
|
s_app->logwriter = logwriter;
|
|
}
|
|
|
|
#ifdef WITH_HTML_LOGS
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
///
|
|
/// @defgroup HTML_logs Writing HTML Logfiles
|
|
/// @ingroup logs
|
|
///
|
|
/// These functions are designed to allow writing HTML diagnostic
|
|
/// messages in a manner that allows users to control how much
|
|
/// diagnostic output to print. It works like this: Suppose you
|
|
/// have function A that invokes function B that invokes function
|
|
/// C. You want to be able to print diagnostic messages just from
|
|
/// function A, or from A and B, or from A, B, and C, or to turn
|
|
/// off printing diagnostic messages altogether. All you need to
|
|
/// do is call 'beginLogGroup' within function A, and specify a
|
|
/// loglevel value. Then in B, call beginLogGroup again, but
|
|
/// without an explicit value for loglevel. By default, the
|
|
/// current level is decremented by one in beginLogGroup. If it
|
|
/// is <= 0, no log messages are written. Thus, if each function
|
|
/// begins with beginLogGroup and calls endLogGroup before
|
|
/// returning, then setting loglevel = 3 will cause messages from
|
|
/// A, B, and C to be written (in nested HTML lists), loglevel =
|
|
/// 2 results in messages only being written from A and B, etc.
|
|
///
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/// Create a new group for log messages. Usually this is called
|
|
/// upon entering the function, with the title parameter equal to
|
|
/// the name of the function or method. Subsequent messages
|
|
/// written with addLogEntry will appear grouped under this
|
|
/// heading, until endLogGroup() is called.
|
|
/// @ingroup HTML_logs
|
|
void beginLogGroup(string title, int loglevel) {
|
|
appinit();
|
|
if (loglevel != -99) s_app->loglevel = loglevel;
|
|
else s_app->loglevel--;
|
|
s_app->loglevels.push_back(s_app->loglevel);
|
|
s_app->loggroups.push_back(title);
|
|
if (s_app->loglevel <= 0) return;
|
|
if (s_app->xmllog == 0) {
|
|
s_app->xmllog = new XML_Node("html");
|
|
s_app->current = &s_app->xmllog->addChild("ul");
|
|
}
|
|
s_app->current = &s_app->current->addChild("li","<b>"+title+"</b>");
|
|
s_app->current = &s_app->current->addChild("ul");
|
|
}
|
|
|
|
/// Add an entry to the log file. Entries appear in the form "tag:
|
|
/// value".
|
|
/// @ingroup HTML_logs
|
|
void addLogEntry(string tag, string value) {
|
|
if (s_app->loglevel > 0 && s_app->current)
|
|
s_app->current->addChild("li",tag+": "+value);
|
|
}
|
|
|
|
/// Add an entry to the log file. Entries appear in the form "tag:
|
|
/// value".
|
|
/// @ingroup HTML_logs
|
|
void addLogEntry(string tag, doublereal value) {
|
|
if (s_app->loglevel > 0 && s_app->current)
|
|
s_app->current->addChild("li",tag+": "+fp2str(value));
|
|
}
|
|
|
|
/// Add an entry to the log file. Entries appear in the form "tag:
|
|
/// value".
|
|
/// @ingroup HTML_logs
|
|
void addLogEntry(string tag, int value) {
|
|
if (s_app->loglevel > 0 && s_app->current)
|
|
s_app->current->addChild("li",tag+": "+int2str(value));
|
|
}
|
|
|
|
/// Add an entry to the log file.
|
|
/// @ingroup HTML_logs
|
|
void addLogEntry(string msg) {
|
|
if (s_app->loglevel > 0 && s_app->current)
|
|
s_app->current->addChild("li",msg);
|
|
}
|
|
|
|
/// Close the current group of log messages. This is typically
|
|
/// called just before leaving a function or method, to close the
|
|
/// group of messages that were output from this
|
|
/// function. Subsequent messages written with addLogEntry will
|
|
/// appear at the next-higher level in the outline, unless
|
|
/// beginLogGroup is called first to create a new group.
|
|
/// @ingroup HTML_logs
|
|
void endLogGroup(string title) {
|
|
if (s_app->loglevel > 0) {
|
|
s_app->current = s_app->current->parent();
|
|
s_app->current = s_app->current->parent();
|
|
}
|
|
s_app->loglevel = s_app->loglevels.back();
|
|
s_app->loglevels.pop_back();
|
|
if (title != "" && title != s_app->loggroups.back()) {
|
|
writelog("Logfile error."
|
|
"\n beginLogGroup: "+ s_app->loggroups.back()+
|
|
"\n endLogGroup: "+title+"\n");
|
|
write_logfile("logerror");
|
|
//s_app->loggroups.clear();
|
|
//s_app->loglevels.clear();
|
|
}
|
|
else if (s_app->loggroups.size() == 1) {
|
|
write_logfile(s_app->loggroups.back()+"_log");
|
|
s_app->loggroups.clear();
|
|
s_app->loglevels.clear();
|
|
}
|
|
else
|
|
s_app->loggroups.pop_back();
|
|
}
|
|
|
|
/// Write the HTML log file. Log entries are stored in memory in
|
|
/// an XML tree until this function is called, which writes the
|
|
/// tree to a file and clears the entries stored in memory. The
|
|
/// output file will have the name specified in the 'file'
|
|
/// argument. If this argument has no extension, the extension
|
|
/// '.html' will be appended. Also, if the file already exists, an
|
|
/// integer will be appended to the name so that no existing log
|
|
/// file will be overwritten. will be appended to the name.
|
|
/// @ingroup HTML_logs
|
|
void write_logfile(string file) {
|
|
if (!s_app->xmllog) {
|
|
return;
|
|
}
|
|
string::size_type idot = file.rfind('.');
|
|
string ext = "";
|
|
string nm = file;
|
|
if (idot != string::npos) {
|
|
ext = file.substr(idot, file.size());
|
|
nm = file.substr(0,idot);
|
|
}
|
|
else {
|
|
ext = ".html";
|
|
nm = file;
|
|
}
|
|
|
|
// see if file exists. If it does, find an integer that
|
|
// can be appended to the name to create the name of a file
|
|
// that does not exist.
|
|
string fname = nm + ext;
|
|
ifstream f(fname.c_str());
|
|
if (f) {
|
|
int n = 0;
|
|
while (1 > 0) {
|
|
n++;
|
|
fname = nm + int2str(n) + ext;
|
|
ifstream f(fname.c_str());
|
|
if (!f) break;
|
|
}
|
|
}
|
|
|
|
// Now we have a file name that does not correspond to any
|
|
// existing file. Open it as an output stream, and dump the
|
|
// XML (HTML) tree to it.
|
|
|
|
if (s_app->xmllog) {
|
|
ofstream f(fname.c_str());
|
|
// go to the top of the tree, and write it all.
|
|
s_app->xmllog->root().write(f);
|
|
f.close();
|
|
writelog("Log file " + fname + " written.\n");
|
|
delete s_app->xmllog;
|
|
s_app->xmllog = 0;
|
|
s_app->current = 0;
|
|
}
|
|
}
|
|
|
|
#endif // WITH_HTML_LOGS
|
|
}
|
|
|