Jump to content
  • Advertisement
Sign in to follow this  
random_thinker

C++ report template 'code generation'?

This topic is 4830 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have a general C/C++ question. Here is the situation. I have a struct with a mixture of data. Call the struct Person. Within person I have name, address, age, etc. struct Person { string name; string address; int age; . . . } Contractor; I then create a vector of structs, say: vector<Contractor> C(5); To access normally, I would use the notation: C.name. Now, for report writing, I set a series of templates, using class Transformer. Class Transformer uses a method Parse(istream &, ostream &), such that I initialize as: Transformer T; T.Parse(input,output); I want to now have a text file as a template to arrange the data in a report form. This has something like a from letter style: "Dear $name, I recently contacted you at $address and discovered your age was $age ..... etc etc." So this file is T.Parsed(), and at each point where a $ occurs, the folloing string is read into the variable, string field; So that field = "name", then field = "address", then field = "age", etc etc. Now, my question is, is there a way to link the contents of field to the vector C to get at the data contained there? For example: 'C.field' >>> is somehow recognized as >>> 'C.name' by the program? Or must this connection be handled in some other way?

Share this post


Link to post
Share on other sites
Advertisement
There is no simple or automatic way of doing it. In the end you will have to write code that accesses the correct data depending of the name of the field. The fact that the types are mixed makes it even more difficult.

Share this post


Link to post
Share on other sites
something like the following, not too sure about what you mean exactly, a more explicit example would be better..


struct Person
{
std::string name;
std::string address;
int age;

template< typename T >
void set( std::string fieldname, T data )
{
if( fieldname == "age" ) age = data;
else if( fieldname == "name" ) name = data;
else if( fieldname == "address" ) address = data;
}
};




Person p;
p.set<int>( "age", 25 );
p.set<const char*>( "name", "bob" );
p.set<const char*>( "address", "5 somedrive" );


Cheers
-Danu

EDIT: Example assumes you wont try something stupid [smile] like assigning int data member to a std::string data member

[Edited by - silvermace on July 26, 2005 1:53:15 AM]

Share this post


Link to post
Share on other sites
The above code by silvermace won't work. Can't compare strings with ==, and you're assigning to things of the wrong type (even though it's inside an if statement that isn't expected to be entered).

Share this post


Link to post
Share on other sites
Thanks Guys, I'm still struggling with this. To simplify things a little, I'm only looking for output, the vectors of structs are already loaded.

Would it be possible to just convert all the struct member data to string data for output only, then access this through an array or hash table, by doing string comparisons? So that a lookup of a hash table key value would link to the data? Or alternatively use an array index on a string data array? In this scenario it would be useful to have a method that automatically converts the struct members to the new string data structure, and links the struct member names to the data.

Cheers,

random

Share this post


Link to post
Share on other sites
You might want to check out scripts systems like PHP... I'm absolutely not an expert in this field, but if I recall correctly you could do things like :

echo "Dear $name, blah blah $address, blah blah your age $age"

and it would actually resolve the $variables for you and replace them with their actual value.

I'm not sure how helpful it is for you, but there must be some PHP engine somewhere that you could use to parse your strings? Like transfer your C++ struct to a set of php variables, and bingo, just include your template and voila, you've got your output!

Hope this helps

Eric

Share this post


Link to post
Share on other sites
Quote:
Original post by Andrew Russell
The above code by silvermace won't work. Can't compare strings with ==, and you're assigning to things of the wrong type (even though it's inside an if statement that isn't expected to be entered).


  • std::string by value, yes you can.
    from SGI std::string reference documentation:

    template <class charT, class traits, class Alloc>
    bool operator==(const charT* s1,
    const basic_string<charT, traits, Alloc>& s2)

    -- basic_string String equality. A global function, not a member function.

  • assignment of incompatable types will throw a compiler error, std::string assignment to a const char* will do a strcpy by deafault. This is an example anyway so I just tried to make it simple, but I admit, that isnt as clear as it could be.


Cheers
-Danu

Share this post


Link to post
Share on other sites
Something like this? (not tested! Also, feel free to convert to an OO style.)


typedef std::map<std::string, std::string> DataItem;

template <typename T>
void addItem(const DataItem& di, const std::string& key, const T& value) {
di[key] = boost::lexical_cast<std::string>(value);
}

typedef std::pair<std::string, std::string> ReportItem;
typedef std::vector<ReportItem> ReportTemplate;
// pairs of plain text, followed by key name, since a report template logically
// alternates between these two (you may need to fill in blank items for the
// plain text)

struct TemplateOutputHelper {
ostream* target;
DataItem* info;
void operator() (const ReportItem& ri) {
(*target) << ri.first << (*info)[ri.second];
}
TemplateOutputHelper(ostream* target, DataItem* info) : target(target), info(info) {}
}

ostream& operator<< (ostream& os, const std::pair<ReportTemplate, DataItem>& report) {
std::for_each(report.first.begin(), report.first.end(), TemplateOutputHelper(&os, &report.second));
return os;
}

// And now we can do:
ReportTemplate t = parse(input);
vector<DataItem> contractors;
// populate the contractors with info, and the vector with contractors

// Yes, I'm writing a simple for loop now, after doing all that advanced
// functional stuff before... :)
for (int i = 0; i < contractors.size(); ++i) {
ostream& output(contractors["filename"]);
output << std::make_pair(t, contractors);
}

Share this post


Link to post
Share on other sites
Quote:
Original post by silvermace
Quote:
Original post by Andrew Russell
The above code by silvermace won't work. Can't compare strings with ==, and you're assigning to things of the wrong type (even though it's inside an if statement that isn't expected to be entered).


  • std::string by value, yes you can.
    from SGI std::string reference documentation:

    template <class charT, class traits, class Alloc>
    bool operator==(const charT* s1,
    const basic_string<charT, traits, Alloc>& s2)

    -- basic_string String equality. A global function, not a member function.

  • assignment of incompatable types will throw a compiler error, std::string assignment to a const char* will do a strcpy by deafault. This is an example anyway so I just tried to make it simple, but I admit, that isnt as clear as it could be.


Cheers
-Danu

Whoops, didn't see your use of std::string.

However the assignment method in your code (although it may work in the simple example) is a completly non-scalable solution, relying on implicit casts being available from every T to every other T.

Share this post


Link to post
Share on other sites
Hi All,

Thanks for your help. I've considered the suggestions and here's what I've come up with:

For string translation, I have a method 'MapValue' in class Load<whatever>, in this case 'LoadLiquid' ('LoadLiquid' is responsible for finding the appropriate data file and struct and building the vector and map containers based upon key (index) data).

template <typename T>
void LoadLiquid::MapValue(StringMap & smap, string key, T value)
{
stringstream s;
s << value;
smap[key] = s.str();
}

where 'StringMap' is:

typedef map<string,string> StringMap;

This seems to be a pretty effective way to translate just about any basic type to a string.

I then assemble the StringMaps in a vector.

typedef vector<StringMap> StringMapVector;

This allows me to iterate a page by page report of the contents of the data vector, 'LoadLiquidVector'.

I then introduce two other methods within Load<whatever> to return the data vector and the map vector. This way I can access both forms of data for either computation or output.

The data and map vectors are built within a private 'Load' method. The syntax for this is quite straightforward, an excerpt is below:

void LoadLiquid::Load()
{
.
.
.
...various initializaton routines here...
.
.
.
// Load structure from state file.
while ( !I.fail() )
{
V_.resize(i+1); //
S_.resize(i+1);

if ( projectid_ == I.GetString() && mixid_ == I.GetString() ) // Look for keys first
{
V_.projectid = projectid_;
MapValue(S_,"projectid",V_.projectid);

V_.mixid = mixid_;
MapValue(S_,"mixid",V_.mixid);

I.GetData(V_.id); // These lines install the data from a parsed file
MapValue(S_,"id",V_.id); // These lines construct the map for future output.

I.GetData(V_.source);
MapValue(S_,"source",V_.source);

.
.
.
.
}
}

By doing this, my compiled-in report formats (that were becoming thousands of cout lines) almost disappear, and I can use a text template system, that can be changed at runtime. For example, the template text file:

REPORT OF LIQUID CONSTITUENTS, PER M3.
Record : ~0,L,0,rec_no of ~0,L,0,rec_tot.
Units : Systeme International (SI).

Project ID: ~0,L,0,projectid
Mixture ID: ~0,L,0,mixid
Type : ~0,L,0,id
Source : ~0,L,0,source

Data for ~0,L,0,id

UNITS QTY COV PDF

Mass kg ~12,R,1,ssd_mass ~12,R,3,ssd_mass_cov ~12,R,1,ssd_mass_pdf
Density kg/m3 ~12,R,1,ssd_density ~12,R,3,ssd_density_cov ~12,R,1,ssd_density_pdf
Halide m/m ~12,R,3,total_halide ~12,R,3,total_halide_cov ~12,R,1,total_halide_pdf
Sulfate m/m ~12,R,3,total_sulfate ~12,R,3,total_sulfate_cov ~12,R,1,total_sulfate_pdf

Analysis of ~0,L,0,id

UNITS QTY

Volume m3 ~12,R,3,ssd_volume
Halide kg ~12,R,1,ssd_total_halide_mass
Sulfate kg ~12,R,1,ssd_total_sulfate_mass
SSD Mass kg ~12,R,1,ssd_mass

Translates to:

REPORT OF LIQUID CONSTITUENTS, PER M3.
Record : of .
Units : Systeme International (SI).

Project ID: TKN Research Project
Mixture ID: TKN1994
Type : Water
Source : Teply, Kersner and Novak (1994)

Data for Water

UNITS QTY COV PDF

Mass kg 215 0.01 lognormal
Density kg/m3 1000 0.05 normal
Halide m/m 0.002 0.01 normal
Sulfate m/m 0.01 0.01 normal

Analysis of Water

UNITS QTY

Volume m3 0.215
Halide kg 0.43
Sulfate kg 2.15
SSD Mass kg 215

During runtime, either to the display, or to file or both. This format syntax is very similar to ANSI Common Lisp.

Thanks for the tips, the system is running crudely, tomorrow I've got to integrate it into the exisiting system, and remove all those cout lines. I'm beginning to wonder whether I need the data structs now, but there are a lot of calculations done behind the scenes that require different types, so I guess I'll keep them.

Best to all,

random.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!