Udicus File Manager: Implementation of a translator

Table of Contents

User Docs:
Basic Features
Addt. Features
Tips & Tricks

Dev Docs:
UDCS
Prog. Tools
Alignment

GUI
Known Bugs
Expansion

SourceForge Logo

This page will quickly describe how to go about writing another file translator. This is meant as a guide only. If you run into trouble, remember to read the code to the file translators already provided, I'd suggest you start byreading Universal.cpp and Universal.h; they are the simplest and easiest.

Concepts:
All files are immediately stored in the UdcsChannel struct (see Udcs and UdcsChannel Class documentation) as soon as they are loaded. The UDCS file format, as well as the UdcsChannel class, are very lenient as to what information they allow themselves to hold, and this is done purposely to allow for the most flexible and versatile file format we could come up with. In any case, the best thing you can do when you're writing a file translator is to try to fill out all variables as best you can, and try to make your UdcsChannel as easy as possible to understand for the export functions for other file formats.

Hooking things up:
Alright, before we do anything else, we need to edit Udcs.h and Udcs.cpp in the Udcs\ directory. Towards the top of the Udcs.h file, you'll see:
const string UdcsFileTypeNames[] = ...
You'll need to add the name of the file translator you are going to write somewhere between "Udcs" and "NULL", then add the extension of the files you want to translate in
const string UdcsFileTypeExts[] = ...
in a position that respects the order of the first array of strings (UdcsFileTypeNames[]).
At the very top of Udcs.h, add an #include statement for the header file of your file tranlator.

Now look at the bottom of Udcs.cpp, in the method Udcs::read(...). In the first, add this:

else if ( filename.substr(filename.find_last_of('.'), filename.size()) == ".[YOUR_TRANSLATOR_EXT]") {
pd_set_text(string("Importing [YOUR_TRANSLATOR] file..."));
Udcs* ret = new [YOUR_TRANSLATOR]();
delete this;
return ret->read(filename);
}

before "throw UdcsException(string("Unrecognized file extension, cannot continue."));", and replace [YOUR_TRANSLATOR] by the name of the class of your file translator, and [YOUR_TRANSLATOR_EXT] by the extension that your file format will use.

Finally, look at Udcs::toFile(...) and add:

else if (extension == ".[YOUR_TRANSLATOR_EXT]" || extension == ".[YOUR_TRANSLATOR_EXT_IN_ALLCAPS]") {
pd_set_text(string("Writing [YOUR_TRANSLATOR] file..."));
[YOUR_TRANSLATOR] t(*this);
t.toFile(fd);
while(!t.file.empty())
t.file.pop_front();
}

before:

else {
throw UdcsException(string("Unrecognized file extension, Cannot continue."));
}

replacing [YOUR_TRANSLATOR] with the name of the class of your file translator, and [YOUR_TRANSLATOR_EXT] with the extension that your file format will use.

Your file translator class:
Let's look at parts of Universal.h to get started:

#ifndef UNIVERSAL_H
#define UNIVERSAL_H
#include "stdafx.h"
#include "Udcs.h"

class Universal : public Udcs {
public:
Universal();
Universal(Udcs& a);
Udcs* read(string& filename);
void toFile(FILE* fd);
};

#endif

Implementing the constructor and destructor to do the things listed below is essential for every file translator, otherwise it simply won't work correctly:

Universal::Universal() {
this->file_type = "Universal";
}

Universal::Universal(Udcs& a) {
for (list<UdcsChannel*>::iterator t = a.file.begin(); t != a.file.end(); ++t) {
this->file.push_back((*t));
}
this->file_type = a.file_type;
this->file_name = a.file_name;
}

Reading/Importing a file:
Alright, now that we have the basics covered, this is what you need to do in the read() method of your file translator class:

  • open the file described by the filename parameter. This will be your input file, and you should expect it to have the extension that you indicated at the top of Udcs.h
  • copy filename to this->file_name and assign the name of your file format to this->file_type.

Now we're ready to read in channels. Depending on the file format you're dealing with, you'll be reading in only one channel for each file (easy/fun -- this is the Embla file format), several channels sequentially (medium/enjoyable -- this is Udcs file format), several channel ssynchronously (hard/challenging -- this is VitalView), several channels asynchronously (very hard/painful -- this is ClockLab). For each channel you'll read in, you need to dynamically allocate a UdcsChannel instance, dynamically allocate a UdcsChannel.valid_map array, fill everything in (see UdcsChannel documentation), and finally push its pointer to the back of the Udcs.file list. In the very end, you must "return this;" to signal a successful import.

Tricks and Pitfalls:
You have to use temporary files to store your data in. This is to make sure you're not loading masses of data into RAM, and either slowing the computer down, or getting booted from the process table because the computer ran out of RAM. Also, you need to use the fopen, fwrite, fread, fclose, fseek, ftell, etc. set of functions to deal with the files, so that other file translators will be able to deal with the temporary file that you prepared. When importing a file, you need to have a little buffer allocated (consider using the DATA_BUFFER_SIZE constant, which is 8192), so that you can read in a bunch of data, and then write it out to your temp file, while doing any necessary translation in between. Remember that the temp file can only contain raw data values as described by this UdcsChannel.data_type flag that you set, because that's what will be expected by other translators. Also: make sure you rewind your files after you use them (ie. fseek(fd, 0L, SEEK_SET); ).

If you run into file reading or opening or anything else errors, you can simply bail out by throwing a UdcsException:

throw UdcsException(string("error text"));

This will display an Error Dialog containing the "error text", and make the execution thread wait for the next command in the top most dialog, the main channel list view. All UdcsChannels you have read in will not show up in the channel list view, and you should also use a "delete this;" statement before throwing an exception, to prevent memory leaks.

Writing/Exporting a file:
The methods to write a file format are completely dependant on the file format you're trying to write, so we can't really give you a set of instructions here. Yet, there's one thing you always have to look out for: You could come accross any data_type, unit, scale, etc, when writing a file, so don't make ANY assumption on what you're going to get, and prepare to either process all possible channels, or throw UdcsExceptions, informing the user that you refuse to process a certain type of channel. Also you cannot assume that just because the first channel was of one type, that all the others are going to be of the same type

Enjoy expanding Udicus and Good Luck!!

© 2001 SRI International, Inc., contact skander@skander.com