NextTrain format file library

NextTrainFile.cpp

Committer:
rinosh2
Date:
2010-11-17
Revision:
1:f89f955130c7
Parent:
0:1e951aba6a7e
Child:
2:0aa41f709144

File content as of revision 1:f89f955130c7:

///////////////////////////////////////////////////////////////////////////////
// NextTrainFile: NextTrain file parser   by rinos 2010
///////////////////////////////////////////////////////////////////////////////

#include "NextTrainFile.h"
#include <time.h>
#include <stdlib.h>

////////////////////////////////////////////////////////////////////////////////
// defines
const int MAX_LINE_BUF = 256;

////////////////////////////////////////////////////////////////////////////////
// NextTrainFile

NextTrainFile::NextTrainFile(const char* ntFile) : m_fp(0), m_shortopt(false) {
	if(ntFile) open(ntFile);
}

NextTrainFile::~NextTrainFile(){
	close();
}

////////////////////////////////////////////////////////////////////////////////
// internal funcs

int getLineType(char ch){
	if(ch == '#')	return NextTrainFile::LINE_TITLE;
	if(ch <  '0')	return NextTrainFile::LINE_COMMENT;
	if(ch <= '9')	return NextTrainFile::LINE_TIME;
	if(ch <  'A')	return NextTrainFile::LINE_COMMENT;
	if(ch <= 'Z')	return NextTrainFile::LINE_OPTION;
	if(ch == '[')	return NextTrainFile::LINE_WEEK;
	if(ch <  'a')	return NextTrainFile::LINE_COMMENT;
	if(ch <= 'z')	return NextTrainFile::LINE_OPTION;
	return NextTrainFile::LINE_COMMENT;
}
int getOptionID(char ch){
	if(ch <  'A')	return -1;
	if(ch <= 'Z')	return ch - 'A';
	if(ch <  'a')	return -1;
	if(ch <= 'z')	return ch - 'a' + 26;
	return -1;
}

int getWeekMask(char* str){
	const char WEEK_DELIM[] = " []\t\r\n";
	const char* WEEK_LIST[] = { // week + holiday
		"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN", "HOL"
	};
	
	int ret = 0;
	char * p = strtok(str, WEEK_DELIM);
	while(p){
		for(int index = 0; index < 8 ; ++index){ // week7 + holiday
//			if(stricmp(p, WEEK_LIST[index]) == 0){
			if(strcmp(p, WEEK_LIST[index]) == 0){
				ret |= 1 << index;
				break;
			}
		}
		p = strtok(0, WEEK_DELIM);
	}
	return ret;
}

////////////////////////////////////////////////////////////////////////////////
// Open NextTrain format file and read option data
NextTrainFile::Status NextTrainFile::open(const char* ntFile){
	Status ret = S_SUCCESS;

	// open check
	if(m_fp) return S_ALREADY_OPEN;
	m_fp = fopen(ntFile, "r");
	if(!m_fp) return S_OPEN_ERROR;
	
	// Read option info
	memset(m_index, 0xff, sizeof(m_index));
	int pos = 0;
	int left = MAX_OPTBUF - 1;
	char line[MAX_LINE_BUF];
	while(fgets(line, sizeof(line), m_fp)){
		switch(getLineType(*line)){
		case LINE_TITLE:
		case LINE_TIME:
		case LINE_WEEK:
			// Don't cache (for saving the memory)
			break;

		case LINE_OPTION:
			{
				// Decode option / e.g. "a:Express;EX"
				int optID = getOptionID(line[0]); // 0..51
				if(optID < 0 || line[1] != ':') continue; // assert?

				const char OPTION_DELIM[] = " \t\r\n";
				char* p = strtok(line + 2, OPTION_DELIM);
				if(!p) continue; // skip empty option

				int len = strlen(p) + 1;
				if(len > left){
					ret = S_OPTION_OVERFLOW; // MAX_OPTBUF is too small...
					continue;
				}
				m_index[optID] = (unsigned char)pos;
				memcpy(m_optbuf + pos, p, len);
				pos  += len;
				left -= len;
			}
			break;
		}
	}
	return ret;
}
NextTrainFile::Status NextTrainFile::close(){
	if(m_fp){
		fclose(m_fp); // no error check...
		m_fp = 0;
	}
	return S_SUCCESS;
}

NextTrainFile::Status NextTrainFile::search(time_t dt, int offset){
	if(!m_fp) return S_OPEN_ERROR; // Closed

	// Set target time/week
	if(!dt) dt = time(0); // get current time
	struct tm* st = localtime(&dt);
	int week_flag = 1 << st->tm_wday; // TODO HOLIDAY!
	unsigned int target = st->tm_hour;
	if(target < HOUR_DIV) target += 24;
	target = target * 60 + st->tm_min;

	// Scan from head of file
	rewind(m_fp);
	int week_mask = -1; // default: all
	char line[MAX_LINE_BUF];
	const char TIME_LINE_DELIM[] = " \t\r\n";

	while(fgets(line, sizeof(line), m_fp)){
		switch(getLineType(*line)){
		case LINE_TITLE:
			if(week_mask & week_flag) {
				char* p1 = line + 1;
				while(*p1     && strchr(TIME_LINE_DELIM, *p1)) ++p1;
				char* p2 = p1 + strlen(p1);
				while(p1 < p2 && strchr(TIME_LINE_DELIM, *p2)) --p2;
				int len = p2 - p1;
				if(*p1 && ++len > sizeof(m_ni.m_title) - 1){
					len = sizeof(m_ni.m_title) - 1;
				}
				memcpy(m_ni.m_title, p1, len);
				m_ni.m_title[len] = 0;
			}
			break;

		case LINE_TIME:
			// Decode option / e.g. "5: ic14 ie19 hc33 ie39 ie54"
			if(week_mask & week_flag){
				// hour format check
				char* p = strtok(line, ":");
				if(!p) break; // Invalid hour line! (return error?)
				int hour = atoi(p);
				if(hour > 23 + HOUR_DIV) break; // Invalid time! (return error?)

				// check hour
				m_ni.m_hour = hour;
				if(hour < HOUR_DIV) hour += 24;
				unsigned int hm = hour * 60;
				if(hm + 60 < target) break; // passed

				// search target min
				while((p = strtok(0, TIME_LINE_DELIM)) != 0){
					// min format check
					char* opt = p;
					while(*p && !strchr("0123456789", *p)) ++p;
					if(!*p) continue; // Invalid min format! (return error?)
					m_ni.m_min = atoi(p);
					if(m_ni.m_min >= 60) continue; // Invalid min format! (return error?)

					// check min
					if(hm + m_ni.m_min <= target) continue; // passed

					if(offset-- > 0) continue; // view next
					
					// Found NextTrain
					//m_ni.m_hour = hour; // use 24:00 instead of 00:00
					m_ni.m_diff = (hm + m_ni.m_min - target) * 60 - st->tm_sec; // leap seconds (max 61)?
					char* dst  = m_ni.m_option;
					int   left = MAX_OPTION - 1;
					for(; opt < p ; ++opt){
						// check option format
						int optID = getOptionID(*opt);
						if(optID < 0 || m_index[optID] == 0xff) return S_INVALID_OPTION_ID;

						// append option string
						char* src = m_optbuf + m_index[optID];
						char* sep = strchr(src, ';');
						int   len;
						if(sep){
							if(m_shortopt){
								// abbreviation
								src = sep + 1;
								len = strlen(src);
							} else {
								// long description
								len = sep - src;
							}
						} else {
							// no separator
							len = strlen(src);
						}

						if(len > left) return S_OPTION_OVERFLOW; // MAX_OPTION is too small...
						memcpy(dst, src, len);
						dst  += len;
						left -= len;
					}
					*dst = 0;
					return S_SUCCESS;
				}
			}
			break;

		case LINE_WEEK:
			week_mask = getWeekMask(line);
			break;

		case LINE_OPTION:
			// skip (use cache)
			break;
		}
	}

	return S_NO_TRAIN; // failed
}