How to write data of type A to format of type B
I'm implementing a thing that generates results and writes them to a file of certain format.
Fairly simple, but I want this to be dynamic.
I'll throw down a few classes.
Data - base class for all results
DataFile - base class for all file format types, has methodaddData(Data * data)
ErrorData - derived from Data, contains data about an error.
InfoData - derived from Data, contains generic information.XmlFile - derived from DataFile, contains Data in XML format.
BinaryFile - derived from DataFile, contains data in binary format.My quest开发者_开发百科ion is this:
Where do i put the implementation on how to write ErrorData into an XmlFile?
What I don't like to see in the answer:
- A new member function to Data, DataFile, ErrorData or XmlFile (because this means that i need to add those each time I add a new Data or DataFile derived class)
- Type casting to derived types.
- General ugliness :)
I know my basic C++ virtualization and things, no need to be ultra specific.
I do appreciate a few code pieces though, they leave less ambiguity.
I had some thoughts on making a DataWriter base class and derive classes from that which would know how to write Data of certain type to a DataFile of certain type, but I'm a bit uncertain of the specifics of that.
EDIT:
I'll clarify a bit more in the form of an example.
Let's have 2 new file formats, FormatATxtFile and FormatBTxtFile.
Let's assume that we have an InfoData object and it has the parameters:
Line number of the message : 34
Message content : Hello World
The object, written into FormatATxtFile, looks like this in the file:
Line:34;Txt:Hello World;Type:Info
And in FormatBTxtFile it would look something like this:
@Info,34,Hello World
A sort of way to export data into a different format. I don't need Importing, at least now.
What the code using this would look like:
DataFile * file = DataFileFactory::createFile(type);
std::vector<Data*> data = generateData();
file->setData(data);
file->writeTo("./FileName"); // correct end is added by DataFile type, ie .txt
Edit:
It seems that I didn't make clear enough what problems arise with Xml and binary file formats. I'm sorry.
Let's use the same InfoData object as above and push it to the XmlFile format. It might produce something like this, under a certain element:
<InfoLog>
<Info Line="34">Hello World</Info>
</InfoLog>
Let's assume that the ErrorData class would have these parameters:
Line number of the error : 56
Error text : LINK : fatal error LNK1168 10 Lines prior to error message: text1... 10 Lines after error message: text2... Entire log af what happened: text3...
Now when this is pushed into an XML format, it would need to be something totally different.
<Problems>
<Error>
<TextBefore>text1...</TextBefore>
<Text line = 56>
LINK : fatal error LNK1168
</Text>
<TextAfter>text1...</TextAfter>
</Error>
...
</Problems>
The entire file might look something like this:
<Operation>
<InfoLog>
<Info Line="34">Hello World</Info>
<Info Line="96">Goodbye cruel World</Info>
</InfoLog>
<Problems>
<Error>
<TextBefore>text1...</TextBefore>
<Text line = 56>
LINK : fatal error LNK1168
</Text>
<TextAfter>text1...</TextAfter>
</Error>
<Error>
<TextBefore>sometext</TextBefore>
<Text line = 59>
Out of cheese error
</Text>
<TextAfter>moretext</TextAfter>
</Error>
</Problems>
</Operation>
Instead of trying to find a place to put this inside a class, what about a new function?
void copyData(const ErrorData *data, DataFile *output)
{
// ...
}
You can then overload this function for whatever data types you want to convert.
Alternately, you could perhaps use a template:
template<typename A, typename B> copyData(const A *data, const B *output);
Then you can specialise the template for specific types you need to support.
Do like the standard library does - go with virtual
functions / operators. We all can use istream&
and extract what we want from it with operator>>
, while we totally don't care for the underlying stream, be it cin
, fstream
or a stringstream
. And rather take your data
by reference then (Data& data
).
If you consider the code below, it presents a minimal illustration of how to arbitrarily combine generic field access with generic field streaming - factoring your various requirements. If the applicability or utility's not clear, do let me know....
#include <iostream>
#include <string>
struct X
{
int i;
double d;
template <typename Visitor>
void visit(Visitor& visitor)
{
visitor(i, "i");
visitor(d, "d");
}
};
struct XML
{
XML(std::ostream& os) : os_(os) { }
template <typename T>
void operator()(const T& x, const char name[]) const
{
os_ << '<' << name << '>' << x << "</" << name << ">\n";
}
std::ostream& os_;
};
struct Delimiter
{
Delimiter(std::ostream& os,
const std::string& kvs = "=", const std::string& fs = "|")
: os_(os), kvs_(kvs), fs_(fs)
{ }
template <typename T>
void operator()(const T& x, const char name[]) const
{
os_ << name << kvs_ << x << fs_;
}
std::ostream& os_;
std::string kvs_, fs_;
};
int main()
{
X x;
x.i = 42;
x.d = 3.14;
XML xml(std::cout);
Delimiter delimiter(std::cout);
x.visit(xml);
x.visit(delimiter);
}
I've been playing a little with your question, and that's what I've come up:
#include <iostream>
#include <list>
#include <map>
#include <string>
using namespace std;
class DataFile;
class Data {
public:
virtual void serializeTo(DataFile*) = 0;
};
class DataFile {
public:
void addData(Data* d) {
_data.push_back(d);
}
virtual void accept(string paramName, string paramValue) {
_map[paramName] = paramValue;
}
virtual void writeTo(string const& filename) = 0;
protected:
list<Data*> _data;
map<string, string> _map;
};
class FormatATxtFile: public DataFile {
public:
void writeTo(string const& filename) {
cout << "writing to " << filename << ".txt:" << endl;
for(list<Data*>::iterator it = _data.begin(); it != _data.end(); ++it) {
(*it)->serializeTo(this);
cout << "Line:" << _map["Line"] << "; "
<< "Txt:" << _map["Txt"] << "; "
<< "Type: " << _map["Type"]
<< endl;
}
cout << endl;
}
};
class FormatBTxtFile: public DataFile {
public:
void writeTo(string const& filename) {
cout << "writing to " << filename << ".b-txt" << endl;
for(list<Data*>::iterator it = _data.begin(); it != _data.end(); ++it) {
(*it)->serializeTo(this);
cout << "@" << _map["Type"] << "," << _map["Line"] << "," << _map["Txt"]
<< endl;
}
cout << endl;
}
};
class InfoData: public Data {
public:
void serializeTo(DataFile* storage) {
storage->accept("Line", line);
storage->accept("Txt", txt);
storage->accept("Type", "Info");
}
string line;
string txt;
};
int main()
{
FormatATxtFile fileA;
FormatBTxtFile fileB;
InfoData info34;
info34.line = "34";
info34.txt = "Hello World";
InfoData info39;
info39.line = "39";
info39.txt = "Goodbye cruel World";
fileA.addData(&info34);
fileA.addData(&info39);
fileB.addData(&info34);
fileB.addData(&info39);
fileA.writeTo("./Filename");
fileB.writeTo("./Filename");
}
actually, it doesn't really write to file but it's easy to change to suit your needs.
The output of this sample code is:
writing to ./Filename.txt: Line:34;
Txt:Hello World; Type: Info Line:39;
Txt:Goodbye cruel World; Type: Infowriting to ./Filename.b-txt
@Info,34,Hello World
@Info,39,Goodbye cruel World
As you see, Data
need to provide the DataFile
with parameters identified by a name and a value, and each specialization of DataFile
will handle it the way it likes.
HTH
精彩评论