/*-*- 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 <vector>
#include <string>
#include <map>
extern "C" {
#include <stdio.h>
}
/** @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<std::string> StringVec;
/** @brief convenience type definition */
typedef std::vector<StringVec> String2Vec;
/** @brief convenience type definition */
typedef std::map<int,StringVec> StringVecMap;
/** @brief convenience type definition */
typedef std::map<int,StringVecMap> StringVec2Map;
/** @brief convenience type definition */
typedef std::map<std::string,std::string> StringMap;
/** @brief convenience type definition */
typedef std::map<std::string,StringMap*> 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<std::string,std::string> variable_; //< Variablename/value pairs
std::map<std::string,Environment *> 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 "<<name<<" within "<<name_<<"\n";
}
/** @brief do we really need this? */
Environment *use() { usage_++; return this; }
/** @brief do we really need this? */
int unuse() { return !(--usage_); }
/** @brief Expands a string containing variables
*
* Searches for syntactically correct variables in the provided string
* and substitutes them using substitute(context,variable,fallback).
*
* Valid variable syntax is either:
* - @c $variable or @c ${variable} within the current context
* - @c ${::variable} or @c ${std::variable} for the topmost context
* - @c ${context::variable} for any context - if the @c context is not
* known to the current context it is passed to the parent context,
* and so on. The @c 'context' can also be written as
* @c 'context1::context2::context3[..]' to address nested contexts
* - In case a variable cannot be expanded according to the above methods
* substitution will not take place and the (unresolvable) variable
* specification will be included in the destination string. This kind
* of 'fallback' can be changed by appending @c ':fallback' to
* @c 'variable' (note this only works when the ${} format is used), i.e.
* @c ${context::variable:fallback} or @c ${variable:fallback}
* - If a variable cannot be resolved in the current context it is always
* passed to the parent context. This behaviour might be configurable in
* the (near or distant) future.
*
* Nested variables are allowed: @c ${variable_${number}} will be expanded
* the following way: first, @c ${number} is expanded, say to @c 001.
* So, we get @c ${variable_001} as the result.
* Now, if @e nests is larger than 0, this method will recurse into
* itself (up to @e nests times) and expand @c ${variable_001} to
* whatever value @c $variable_001 yields.
*/
std::string expand(const std::string &, int nests=20);
/** @brief Substitutes a variable honouring its context
*
* This method also uses parent->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 <Data *> dataVec_;
/** @brief Used to store some parameters belonging to the graph */
std::vector <std::string> params_;
/** @brief Used to identify graph numbers holding literal data */
std::map<int, int> 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 *> *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<int,CopySrc> CopyMap;
/** @brief convenience type definition */
typedef std::map<int,CopyMap> 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<int, int> 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<Graph*> 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
syntax highlighted by Code2HTML, v. 0.9.1