/*-*- c++ -*-****************************************************************** * GraceTMPL Library * Copyright (C) 2001,2002 Andy Thaller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *****************************************************************************/ #ifndef __GRACETMPL_H__ #define __GRACETMPL_H__ #include #include #include extern "C" { #include } /** @brief Namespace for classes used to store datasets in xmgrace files. * * The datasets are organized in graphs. At least one graph is saved per file * depending on the template used (if any). * */ namespace GraceTMPL { /** @brief convenience type definition */ typedef std::vector StringVec; /** @brief convenience type definition */ typedef std::vector String2Vec; /** @brief convenience type definition */ typedef std::map StringVecMap; /** @brief convenience type definition */ typedef std::map StringVec2Map; /** @brief convenience type definition */ typedef std::map StringMap; /** @brief convenience type definition */ typedef std::map StringStringMap; /** @brief Removes any occurences of $NAME or ${CONTEXT::NAME} in a string. */ std::string smashVars(const std::string &from); /** @brief String factory formatting double values */ std::string stringNum(double d, const char *fmt= "%lg"); /** @brief String factory formatting double values */ std::string stringNum(float d, const char *fmt= "%g"); /** @brief String factory formatting double values */ std::string stringNum(long i, const char *fmt= "%ld"); /** @brief String factory formatting double values */ std::string stringNum(int i, const char *fmt= "%d"); /** @brief Hierarchical environment system. * * A (named) Environment holds a number of variablename/value pairs. * The environment's name can be seen as a context. As such, it can be * contained in other Environment objects as a contextname/Environment * pair. */ class Environment { std::string name_; //< Context name Environment *parent_; //< The enclosing Environment std::map variable_; //< Variablename/value pairs std::map context_; //< Contextname/Environment pairs int usage_; //< Usage count (possibly useless?) public: /** @brief Default constructor. */ Environment(Environment *parent= 0) : name_(""), parent_(parent), usage_(1) {}; /** @brief Clears all variables - not the contexts */ void clear() { variable_.clear(); //context_.clear(); } /** @brief Set the parent Environment */ void setParent(Environment *parent) { parent_= parent; if (parent_ && name_!="") parent_->add(name_,this); } /** @brief Set the context name */ void setName(const std::string &name) { name_= name; if (parent_ && name_!="") parent_->add(name_,this); } /** @brief Set an environment variable */ void set(const std::string &name,const std::string &value) { variable_[name]= value; } /** @brief set a named context - ensures consistency */ void add(const std::string &name,Environment *value) { if (context_[name]== value) return; context_[name]= value; value->setParent(this); value->setName(name); // cerr << "registered "<substitute(context,variable,fallback) or * context_[context]->substitute(context2,variable,fallback) * for expansion, if this should be necessary. */ std::string substitute(const std::string &context, const std::string &variable, const std::string &fallback); }; /** @brief Base class for any class that should use Environment * * This class provides transparent access to the Environment hierarchy. */ class EnvironmentUser { protected: /** @brief Pointer to the Environment */ Environment *env_; public: /** @brief Constructor */ EnvironmentUser() { env_= new Environment(); } /** @brief Destructor */ ~EnvironmentUser() { if (env_ && env_->unuse()) delete env_; } /** @brief Returns the pointer to the Environment */ Environment *env() { return env_; } /** @brief set a new Environment for this class * * Used internally to allow setting an Environment for a new Environment * user. */ void setenv(Environment *env) { if (!env) return; if (env_ && env_->unuse()) delete env_; env_= env->use(); } /** @brief Set a name/value pair - existing entries will be replaced */ void setenv(const std::string &name,const std::string &value) { if (!env_) return; env_->set(name,value); } /** @brief Set a name/value pair - existing entries will be replaced */ void setenv(const std::string &name,double value) { setenv(name,stringNum(value)); } /** @brief A wrapper function for Environment::expand() */ std::string expand(const std::string &str, int nests=20) { if (!env_) return ""; return env_->expand(str, nests); } }; /** @brief Container for x/y data pairs, optionally with dx and dy * * Beware that a Data object is not to be used more than once. Each data * object has it's own Environment and at some point of gracefile generation * the Data object is assigned with a certain name (e.g. "S0") - this is * done for all Data objects on the page and very essential for variable * substitution. */ class Data : public EnvironmentUser { /** @brief Used to store the string used for the legend */ std::string name_; /** @brief Number of data values (a.k. size of data fields) */ int n_; /** @brief Dataset's Number - defaults to 0*/ int setnum_; /** @brief Data fields used to store the values - NULL if not provided */ double *x_,*y_,*dx_,*dy_; /** @brief Used for offset correction */ double xoffs_, yoffs_; /** @brief Used for scaling the data */ double scale_; public: /** @brief Initialized a dummy dataset. * * This dummy dataset can be used to represent literal data. */ Data(); /** @brief Initialized the dataset's name and data fields. * * The name is used for the dataset's legend string. * N denotes the number of values in each x,y,dx and dy. * The latter two can be NULL pointers if errors are not of interest. * The data referenced by x,y,dx and dy is copied to internal data * structures. */ Data(const std::string &name, int n, const double *x, const double *y, const double *dx=0, const double *dy=0); /** @brief Returns the dataset's name */ std::string name() { return name_; } /** @brief Checks if at least one of x or y errors are present */ int hasErrors() { return (dy_ || dx_); } /** @brief Set the dataset's number. * * Called by GraceTMPL::Graph::save() when the dataset order is known. * Default is 0. */ void setNum(int i) { setnum_= i; } /** @brief Query dataset's number. */ int num() { return setnum_; } /** @brief Set the data's x-offset */ void setXOffset(double x) { xoffs_= x; } /** @brief Set the data's y-offset */ void setYOffset(double y) { yoffs_= y; } /** @brief Set a factor by which the data will be scaled */ void setScaling(double s) { scale_= s; } /** @brief Query autoscale information from the datset * * The dataset's maximum and minumum x and y values are stored in * xmin, xmax, ymin and ymax. If error values are present, they are * taken into account scaled by errorfac. Errorfac defaults to 1.0 * and can be set to 0.0 if errors are present and should not * be taken into account. Autoscaling can be turned of by prepending * and appending two spaces to the according axislabel: * use "\ \ mylabel\ \ " instead of "mylabel" or "\ \ \ \ " instead of * "" to turn off autoscaling. * * autoscale only honours points within the x/y range * [XMIN:XMAX]/[YMIN:YMAX]. */ void autoscale(double &xmin, double &xmax, double &ymin, double &ymax, double XMIN, double XMAX, double YMIN, double YMAX, double errorfac= 1.0); /** @brief Save information strings info file. * * DaSet contains the information strings relevant for this dataset. * if DaSet is empty or a NULL pointer NO information is being written * to the file. This is a mandatory behaviour for bare dataset saving. * * If a legend string ends with "$'" it is not replaced with the string * provided by the program. * * @todo Make this a stream */ void saveinfo(FILE *f, const StringVec *daSet=0); /** @brief Save data-fields info file. * * if correctLog is specified as !=0 the data will be cleaned of * any points with non-positive y or y-dy values. This way the * data is ready for a logarithic plot (dirty hack to avoid problem * in xmgrace). */ void savedata(FILE *f, int correctLog= 0); }; class Save; /** @brief Container for a graph holding some datasets * * In the normal case of operation, this class would not be instantiated * by the user. The preferred way to get a new graph object is to call * GraceTMPL::Save::addData(). * * Also note that it's in the application's responsibility to clean up * Data objects. Graph::~Graph() would not touch them! */ class Graph : public EnvironmentUser { /** @brief Used to store the graph's number */ int graphnum_; /** @brief References the saver instance */ Save* saver_; /** @brief Used to store the datasets */ std::vector dataVec_; /** @brief Used to store some parameters belonging to the graph */ std::vector params_; /** @brief Used to identify graph numbers holding literal data */ std::map literalData_; /** @brief Used for offset correction */ double xoffs_, yoffs_; /** @brief Used for scaling the data */ double scale_; /** @brief Is !=0 if underlying datasets should be corrected for * logplots */ int correctLog_; public: /** @brief Initialize the graph with optional logplot preparation. * * The saver must be a GraceTMPL::Save instance. If it is omitted * or given as 0 some things will not work as expected! * * If logplot is specified as !=0 all datasets in the graph will * be corrected for logarithmic plotting. */ Graph(Save *saver, int logplot=0); /** @brief Set the graph's number * * This method is used internally to set the graph's number * corresponding to g0..gN. */ void setGraph(int i) { graphnum_= i; } /** @brief Add a dataset to this graph * * */ void addData(Data *d) { dataVec_.push_back(d); d->env()->setParent(env()); } /** @brief Returns all datasets of this graph */ std::vector *data() { return &dataVec_; } /** @brief Set the graph's x-offset */ void setXOffset(double x) { xoffs_= x; } /** @brief Set the data's y-offset */ void setYOffset(double y) { yoffs_= y; } /** @brief Set a factor by which the data will be scaled */ void setScaling(double s) { scale_= s; } /** @brief Query the graph's x-offset */ double xoffset() { return xoffs_; } /** @brief Query the graph's y-offset */ double yoffset() { return yoffs_; } /** @brief Query the factor by which the data will be scaled */ double scale() { return scale_; } /** @brief Query if data will be corrected for logplots */ int correctLog() { return correctLog_; } /** @brief Add a variable to the graph's local parameters */ void addParam(const std::string &name,double value); /** @brief Prepare graph for saving - ensures consistency */ void saveprep(const StringVecMap *daSets); /** @brief Save information strings info file. * * DaGraph contains the information strings relevant for this graph. * * DaSet contains the information strings relevant for this dataset. * * DaStrings containes the string definition templates used to format * the graph's parameter strings. * * If DaGraph is empty or a NULL pointer NO information is being written * to the file. This is a mandatory behaviour for bare dataset saving. * * @todo Make this a stream */ void saveinfo(FILE *f, const StringVec *daGraph=0, const StringVecMap *daSets=0, const StringVecMap *daStrings= 0); /** @brief Save all datasets linked to this graph * * If dataonly is specified as !=0, no '@target ...' will be used to * address the target graph number. This is a mandatory behaviour * for bare dataset saving. * * @todo Make this a stream */ void savedata(FILE *f, StringMap *literalData, int dataonly= 0); }; /** @brief Container for a number of graphs to be saved in a xmgrace * compatible file format. * * For saving the data, an existing xmgrace file can be used as a template * by calling loadTemplate(templatename) * * So what happens in a typical livecycle of GraceTMPL usage? * * - The application creates a new GraceTMPL::Save object. * - Using GraceTMPL::Save::loadTemplate it then loads a gracefile. * Anything found in the template which cannot be associated with * a certain graph or set is saved as a string and preserved for * later, when the output files are produced. Anything regarding a * graph or set is stored in special data structures. And by the * way, all goes into GraceTMPL::Save::Template. Also, the doubly * indexed vector request_ is built which can be queried using * templateDataRequestInfo(). * - At this time, GraceTMPL knows how many graphs fit on each page * (or go to each file, resp.) and how many datasets each graph in * the template contains. The latter information is of a certain * 'diffuse' significance. On one hand side, a graph can take * @e any number of graphs. Literal data will @e always be * contained in the graph. Data stored by the application will be * formatted like the associated dataset in the template. If more * data is stored than datasets where contained in the template, * the data will be formatted in some nonpredictable way * (i.e. however grace should decide). * - Now the application creates a number of graphs and stores * datasets therein. How this is done, in a way depends on the * purpose of the template. * - If the Template requests certain data via "$=DATA" or "$'DATA" * the application will want to analyze whatever a call to * templateDataRequestInfo() returns. Creating one graph after * the other it will produce datasets as requested by the * template and store them within the graphs. * - Another approach is to just create one graph after the other, * happily storing any kind and count of datasets within the * graphs and trust that the template is properly formatted to * handle the data. * Upon creation, a graph is already assigned with a name, namely * "gX", with X being the graph number. * - The application can set any amount of name/value pairs within * the GraceTMPL::Save object, each of the graphs and any of the * datasets, as need may call for. * - When all is done, the application may call GraceTMPL::Save::save() * This will trigger the following actions: * - By expanding the filename template the name for the first * output file is generated. If the resulting filename starts * with pipe symbol ("|") and enablePipe(true) was set, it will * be interpreted as a process not a file and the output data * will be sent to the process. Note this is a security risk, so * by default pipe handling is disabled. * - To each graph in the template a graph as created by the * application is associated by a call to saveprep. At this * point, all sets contained in a graph get a name "sX" with X * being the set number. * - Now the output data is created. The common stuff is written * as taken from the template. Then, for each graph and set the * formatting stiff is written. Any string possibly containing a * variable (e.g. legend strings, axis labels, titles, strings, * etc.) is expanded properly. After that part, all data is * written to the output and the pipe or file is closed. * - If any graphs are yet unhandled, the saver will increase the * page number and repeat this procedure. */ class Save : public EnvironmentUser { protected: /** @brief Struct to give easy access to copy-data sources */ struct CopySrc { int g; ///< @brief graph and set number of source int s; ///< @brief graph and set number of source Data *src; ///< @brief pointer to source (not available when null) /** Contructor */ CopySrc() : g(-1),s(-1),src(0) {}; }; /** @brief convenience type definition */ typedef std::map CopyMap; /** @brief convenience type definition */ typedef std::map Copy2Map; /** @brief Struct to hold template data. * * Since the strings stored in this structure might be reused for multiple * puproses, they may not be altered in any way upon string expansion or * alike - make sure to always work on copies. */ struct Template { std::string filename_; ///< @brief where the template is from std::string header_; ///< @brief All lines beginning with '#' StringVec common_; ///< @brief Everything but graph, sets, strings String2Vec strings_; ///< @brief All strings but param templates StringVecMap graphs_; ///< @brief All information on graphs but sets StringVec2Map sets_; ///< @brief All information on sets StringVec2Map params_; ///< @brief All param templates String2Vec request_; ///< @brief All set requests StringMap data_; ///< @brief The original data from the template Copy2Map copy_; ///< @brief Flags a \ref dict_copy_data std::map ignore_; ///< @brieg graphs hat shouldnot get data int gpp_; ///< @brief graphs per page int ignores_; ///< @brief graphs to ignore but process anyway int useG0_; ///< @brief G0 has dataset format of all graphs int valid_; ///< @brief template is valid and can be used }; /** @brief Used to store template data * * @todo More than one template for more than one output file? * Probably not! */ Template tmpl_; /** Used to store all graphs */ std::vector graphsVec_; /** Used to store the output file's environment */ StringStringMap docEnvs_; /** Used to store the environment specific to the saver */ StringMap myEnv_; /** Used to store the filename template */ std::string nameTmpl_; /** Used to set flag if opening a pipe for output is allowed */ int allowPipe_; public: /** @brief Default constructor. Does nothing special. */ Save(); /** @brief Destructor. Cleans up things. */ virtual ~Save() { ; } /** @brief Create and store a new Graph within this object. */ virtual Graph *newGraph(int logplot= 0) { Graph *g= new Graph(this,logplot); if (!g) return 0; graphsVec_.push_back(g); return g; } /** @brief Used to query if a certain dataset should be copied * from another. * * Used internally only by Graph::saveprep */ int isCopydata(int g, int s); /** @brief Used to query the source set of a set copy operation. * * Used internally only by Graph::savedata() */ Data *copydata(int g, int s) { if (isCopydata(g,s)) return tmpl_.copy_[g][s].src; return 0; } /** @brief Used to set a reference to a dataset copy source. * * Used internally only by Graph::saveprep() */ void regCopydata(int g, int s, Data* src); /** @brief Used to clear all references to dataset copy sources. * * Used internally only. */ void clearCopydata(); /** @brief Returns the number of stored graphs */ virtual Graph *graph(int i) { if (i<0 || i>=int(graphsVec_.size())) return 0; return graphsVec_[i]; } /** @brief Returns the number of stored graphs */ virtual int graphs() { return graphsVec_.size(); } /** @brief Set the template for the output filename * * The accessible environment for the output template is * the same as for the whole file plus the environment of * the first graph in the file. * * @see save() */ void setOutputName(const std::string &name) { nameTmpl_= name; } /** @brief Enables writing output data to a pipe. * * If output to a pipe is enables and the outputname set with * setOutputName() starts with a '|' symbol, the rest of the * outputname will be interpreted as a process to which a pipe is * opened and the outputdata is written. This might not work as * expected if only works if no template has been loaded. For this * case, pipe support might be disabled in the future * @todo Decide upon pipe policy in case of missing template. */ void enablePipe(int i) { allowPipe_= i; } /** @brief Query the state of pipe handling */ int pipeEnabled() { return allowPipe_; } /** @brief Load an xmgrace file as template. * * In the template file, environment variables can be used in * a way of writing '$NAME' of ${NAME} with NAME consisting of * alphanumerical characters and the underscore ('_'). This will be * replaced by the string related to NAME. An environment variable * can be set by calling setenv(). Any environment variable belonging * to a certain graph is exported to the sheet using a naming style * of ${GRAPH::NAME} with GRAPH denoting the graph name (e.g. "g0", * "g1", etc.). Note this naming style explicitly requires the brackets * "{}". * * Additionally, the might be an arbitrary number of parameters * related to a graph. To specify the positions of these parameters * (if they should be printed at all) the template can contain string * objects of the form "PARAMg0:1" where g0 stands for graph "g0" and * 1 stands for set number 1 withing that graph. If any, at least two * strings per graph should be present, namely "PARAMg0:0" and * "PARAMg0:1" for graph 0, "PARAMg1:0" and "PARAMg1:1" for graph 1 * and so on. The positions of the last two strings of this kind will * be used to interpolate the positions of further strings, so there * should be enough space to hold the expected number of strings. * * @see setenv(), save() */ virtual int loadTemplate(const char *filename, int useS0= 0); /** @brief Query information about datasets the template requests. * * In each of the graphs defined within the template, the contained sets * can request the program using GraceTMPL to supply certain kind of data * for the according set. This is done by adding a file/data specifier to * the legendstring. A specifier starts with $' for sets which should keep * their name from the legend entry and with $= for sets which get their * name from the application. This is followed by a string of arbitrary * length which is actually passed to the application when it calls this * function. * * The result will be a 2-dimensional string vec with the first index * being the graph number and the second index the setnumber within that * graph. The setnumber must not necessarily represent the S* number as * known from XmGrace - literal data sets will be missing as they can not * be changed anyway. */ virtual String2Vec templateDataRequestInfo(); /** @brief Save all graphs to at least one file, optionally using a * template. * * If no template was loaded with loadTemplate(), each graph will result * in one datafile wich can be imported into an xmgrace-graph as ascii * data. * * In case a template was successfully loaded, it will be used to store * as many graphs in a file as specified in the template. The resulting * files can be loaded by the same xmgrace version used to generate the * template file. For notes on templates, see loadTemplate(). * * On saving, the environment variable '$p' will be expanded to the * file number currently written, while '$pz' is this same number * formatted with enough leading zeros to give a fixed-length string * for all written files. * To generate outputfilenames which can be sorted in ascending * order, include "$pz" in your filename-template (see setOutputName()). * '$P' will expand to the number of outputfiles necessary to save all * graphs. */ virtual void save(); }; } #endif