For me, the way to go would be interfaces and a Factory. One that returns references to interfaces behind which various classes can hide. The classes that do the actual grunt work all need to be registered with the Factory so it knows which class to instantiate given a set of parameters.
Note: instead of interfaces you could also use abstract base classes, but the drawback there is that for single inheritance languages it limits you to a single base class.
TRepresentationType = (rtImage, rtTable, rtGraph, ...);
Factory.RegisterReader(TJSONReader, 'json');
Factory.RegisterReader(TXMLReader, 'xml');
Factory.RegisterWriter(TPDFWriter, 'pdf');
Factory.RegisterWriter(TPowerPointWriter, 'ppt');
Factory.RegisterWriter(TWordWriter, 'doc');
Factory.RegisterWriter(TWordWriter, 'docx');
Factory.RegisterRepresentation(TPNGImage, rtImage, 'png');
Factory.RegisterRepresentation(TGIFImage, rtImage, 'gif');
Factory.RegisterRepresentation(TJPGImage, rtImage, 'jpg');
Factory.RegisterRepresentation(TCsvTable, rtTable, 'csv');
Factory.RegisterRepresentation(THTMLTable, rtTable, 'html');
Factory.RegisterRepresentation(TBarChart, rtTGraph, 'bar');
Factory.RegisterRepresentation(TPieChart, rtTGraph, 'pie');
Code is in Delphi (Pascal) syntax as that is the language with which I am most familiar.
After all implementing classes are registered with the factory, you should be able to request an interface reference to an instance of such a class. For example:
Factory.GetReader('SomeFileName.xml');
Factory.GetWriter('SomeExportFileName.ppt');
Factory.GetRepresentation(rtTable, 'html');
should return an IReader reference to an instance of TXMLReader; an IWriter reference to an instance of TPowerPointWriter and an IRepresentation reference to an instance of THTMLTable.
Now all the rendering engine needs to do, is tie everything together:
procedure Render(
aDataFile: string;
aExportFile: string;
aRepresentationType: TRepresentationType;
aFormat: string;
);
var
Reader: IReader;
Writer: IWriter;
Representation: IRepresentation;
begin
Reader := Factory.GetReaderFor(aDataFile);
Writer := Factory.GetWriterFor(aExportFile);
Representation := Factory.GetRepresentationFor(aRepresentationType, aFormat);
Representation.ConstructFrom(Reader);
Writer.SaveToFile(Representation);
end;
The IReader interface should provide methods to read the data needed by IRepresentation implementers to construct the representation of the data. Similarly IRepresentation should provide methods that IWriter implementers need to export the data representation to the requested export file format.
Assuming the data in your files is tabular in nature, IReader and its supporting interfaces could look like:
IReader = interface(IInterface)
function MoveNext: Boolean;
function GetCurrent: IRow;
end;
IRow = interface(IInterface)
function MoveNext: Boolean;
function GetCurrent: ICol;
end;
ICol = interface(IInterface)
function GetName: string;
function GetValue: Variant;
end;
Iterating over a table would then be a matter of
while Reader.MoveNext do
begin
Row := Reader.GetCurrent;
while Row.MoveNext do
begin
Col := Row.GetCurrent;
// Do something with the column's name or value
end;
end;
As the representations can be images, graphs and textual in nature, IRepresentation would probably have similar methods to IReader to traverse a constructed table and it would have methods to get the images and graphs, for example as a stream of bytes. It would be up to the IWriter implementers to encode the table values and the image/graph bytes as required by the export target.