Skip to content

Instantly share code, notes, and snippets.

@NoTimeForHero
Last active February 4, 2016 18:54
Show Gist options
  • Save NoTimeForHero/41322907bc6ad2cb3769 to your computer and use it in GitHub Desktop.
Save NoTimeForHero/41322907bc6ad2cb3769 to your computer and use it in GitHub Desktop.
Ruby extension based on C++ xBase Library for DBF support
// Include the Ruby headers and goodies
//#include "ruby.h"
#include <xbase64/xbase64.h>
#include "ruby.h"
#include <iostream>
#include <sys/stat.h>
#include <map>
#include <string>
#include <vector>
#include <time.h>
#include <algorithm>
#include <iconv.h>
#include <iconvpp.c>
#define LIB_VERSION "0.9.0 Alpha"
using namespace std;
struct field_type {
string name;
string type;
int fid;
int length;
};
struct filter {
string name;
string type;
VALUE value;
int fid;
char condition;
};
typedef std::map<std::string,field_type> fields_map;
typedef std::vector<filter> filter_map;
typedef std::vector<std::string> selected_fields;
static VALUE DBFLib;
static VALUE Record;
static VALUE read_record(VALUE,xbDbf*,fields_map*,bool);
extern "C" void Init_dbflib();
static VALUE lib_alloc(VALUE);
static VALUE lib_init(int,VALUE*,VALUE);
static VALUE lib_open(VALUE,VALUE);
static VALUE lib_find(int,VALUE*,VALUE);
static VALUE lib_show_field(int,VALUE*,VALUE);
static VALUE lib_path(VALUE);
static VALUE lib_fields(VALUE);
static VALUE lib_records(VALUE);
static VALUE lib_filters(VALUE);
static VALUE lib_enum(VALUE);
static VALUE lib_all(VALUE);
static VALUE lib_where(int,VALUE*,VALUE);
static VALUE lib_record_init(VALUE,VALUE);
static VALUE lib_record_call(int,VALUE*,VALUE);
static VALUE lib_record_attributes(VALUE);
static VALUE lib_record_to_json(int,VALUE*,VALUE);
static VALUE lib_close(VALUE);
static VALUE lib_version(VALUE);
static VALUE lib_manual_free(VALUE);
static VALUE lib_charset(VALUE,VALUE,VALUE);
static char * valueToChar(VALUE,bool);
class lib_storage {
private:
static const int signature = 0x734af; // I don't know why it needed
public:
xbXBase *x;
xbDbf *dbf;
string path;
bool isDbfLoaded;
bool needIconv;
string charsetSrc;
string charsetTrg;
iconvpp::converter *conver;
int size;
fields_map *fields;
filter_map *filters;
selected_fields *selected;
lib_storage() {
x = new xbXBase();
dbf = new xbDbf(x);
path = "";
isDbfLoaded = false;
needIconv = false;
}
~lib_storage() {
std::cout << "[C++] This destructor may be called only in manually mode..." << std::endl;
}
};
string iconvert(string from, string to, string text)
{
std::cout << from << " -> " << to << " : " << text << endl;
char *outbuf;
char *ip = (char *) text.c_str();
char *op = outbuf;
iconv_t cnv = iconv_open(to.c_str(), from.c_str());
if (cnv == (iconv_t) - 1) {
iconv_close(cnv);
return "";
}
//outbuf = (char*)malloc(6);
if ((outbuf = (char *) malloc(text.length()*2 + 1)) == NULL) {
iconv_close(cnv);
return "";
}
//size_t icount = text.length(), ocount = text.length()*2;
size_t icount = 5, ocount = 5*2;
if (iconv(cnv, &ip, &icount, &op, &ocount) != (size_t) - 1) {
outbuf[text.length()*2 - ocount] = '\0';
//text = outbuf;
}
//else{
//text = "";
//}
std::cout << from << " -> " << to << " result: " << text << endl;
//delete []outbuf;
iconv_close(cnv);
return text;
}
std::string cleaner(std::string input) {
input.erase(0, input.find_first_not_of(' ')); //prefixing spaces
input.erase(input.find_last_not_of(' ')+1); //surfixing spaces
return input;
}
// Converts ruby symbol or string to char array
static char * valueToChar(VALUE value, bool throwError = true) {
int type = TYPE(value);
switch (type) {
case T_STRING:
return RSTRING(value)->ptr;
case T_SYMBOL:
// calling intern function Symbol.to_s on argument
return RSTRING(rb_funcall(value, rb_intern("to_s"), 0))->ptr;
default:
if (throwError) rb_raise(rb_eTypeError, "can't convert this type to text");
return new char[0];
}
}
// The initialization method for this module
extern "C" void Init_dbflib() {
DBFLib = rb_define_class("DBFLib", rb_cObject);
rb_define_alloc_func(DBFLib, lib_alloc);
rb_define_private_method(DBFLib, "initialize", RUBY_METHOD_FUNC(lib_init), -1);
rb_define_singleton_method(DBFLib, "version", RUBY_METHOD_FUNC(lib_version), 0);
rb_define_method(DBFLib, "open", RUBY_METHOD_FUNC(lib_open), 1);
rb_define_method(DBFLib, "path", RUBY_METHOD_FUNC(lib_path), 0);
rb_define_method(DBFLib, "fields", RUBY_METHOD_FUNC(lib_fields), 0);
rb_define_method(DBFLib, "records", RUBY_METHOD_FUNC(lib_records), 0);
rb_define_method(DBFLib, "filters", RUBY_METHOD_FUNC(lib_filters), 0);
rb_define_method(DBFLib, "find", RUBY_METHOD_FUNC(lib_find), -1);
rb_define_method(DBFLib, "enum", RUBY_METHOD_FUNC(lib_enum), 0);
rb_define_method(DBFLib, "all", RUBY_METHOD_FUNC(lib_all), 0);
rb_define_method(DBFLib, "where", RUBY_METHOD_FUNC(lib_where), -1);
rb_define_method(DBFLib, "close", RUBY_METHOD_FUNC(lib_close), 0);
rb_define_method(DBFLib, "free", RUBY_METHOD_FUNC(lib_manual_free), 0);
rb_define_method(DBFLib, "charset", RUBY_METHOD_FUNC(lib_charset), 2);
rb_define_method(DBFLib, "show_field", RUBY_METHOD_FUNC(lib_show_field), -1);
//Record = rb_define_class_under(DBFLib, "Record", rb_cHash);
Record = rb_define_class_under(DBFLib, "Record", rb_cObject);
rb_define_private_method(Record, "initialize", RUBY_METHOD_FUNC(lib_record_init), 1);
rb_define_method(Record, "method_missing", RUBY_METHOD_FUNC(lib_record_call), -1);
rb_define_method(Record, "attributes", RUBY_METHOD_FUNC(lib_record_attributes), 0);
rb_define_method(Record, "to_json", RUBY_METHOD_FUNC(lib_record_to_json), -1);
// Need this library for Date type in xBase
rb_require("date");
}
static lib_storage *getData(VALUE self) {
lib_storage *storage;
Data_Get_Struct(self, lib_storage, storage);
return storage;
}
void lib_free(void *data) {
//std::cout << "C Free called" << std::endl;
ruby_xfree(data);
}
static VALUE lib_version(VALUE self) {
return rb_str_new2(LIB_VERSION);
}
static VALUE lib_alloc(VALUE self) {
lib_storage *data = new lib_storage();
//std::cout << "C Alloc called" << std::endl;
return Data_Wrap_Struct(self, NULL, lib_free, data);
}
static VALUE lib_init(int argc, VALUE *argv, VALUE self) {
if (argc > 1) rb_raise(rb_eArgError, "wrong number of arguments ( > 1 )");
lib_open(self, argv[0]);
return Qnil;
}
static VALUE lib_manual_free(VALUE self) {
getData(self)->dbf->~xbDbf();
getData(self)->fields->~map();
getData(self)->filters->~vector();
getData(self)->~lib_storage();
getData(self)->conver->~converter();
getData(self)->selected->~vector();
self = Qnil;
return Qnil;
}
static VALUE lib_close(VALUE self) {
getData(self)->dbf->CloseDatabase();
getData(self)->fields->clear();
getData(self)->filters->clear();
getData(self)->selected->clear();
getData(self)->isDbfLoaded = false;
return Qnil;
}
static VALUE lib_show_field(int argc, VALUE *argv, VALUE self) {
for (int i=0;i<argc;i++) {
if (TYPE(argv[i]) != T_STRING && TYPE(argv[i]) != T_SYMBOL) continue;
getData(self)->selected->push_back(string(valueToChar(argv[i])));
}
return INT2NUM(argc);
}
static VALUE lib_open(VALUE self, VALUE rbPath) {
Check_Type(rbPath, T_STRING); // must be a string
char *path = RSTRING(rbPath)->ptr;
//std::cout << "Failed must be here?" << (getData(self)->isDbfLoaded ? "true" : "false") << std::endl;
if (getData(self)->isDbfLoaded) lib_close(self);
getData(self)->fields = new map<string,field_type>();
getData(self)->filters = new vector<filter>();
getData(self)->selected = new vector<std::string>();
getData(self)->path = string(path);
getData(self)->isDbfLoaded = true;
xbXBase *x = getData(self)->x;
xbDbf *dbf = getData(self)->dbf;
xbShort status;
if(( status = dbf->OpenDatabase( path )) != XB_NO_ERROR ) {
rb_raise(rb_eIOError, x->GetErrorMessage(status));
}
fields_map *fields = getData(self)->fields;
fields->clear();
for (int i = 0; i < dbf->FieldCount(); i++ ) {
string type;
switch(dbf->GetFieldType(i)) {
case 'D':
type = "Date";
break;
case 'C':
type = "String";
break;
case 'N':
type = "Numeric";
break;
case 'M':
type = "Memo";
break;
default:
type = string("Unknown ") + dbf->GetFieldType(i);
}
field_type data;
data.name = dbf->GetFieldName(i);
data.type = type;
data.length = dbf->GetFieldLen(i);
data.fid = i;
fields->insert(fields->end(), std::pair<string,field_type>(dbf->GetFieldName(i), data));
}
if(( status = dbf->GetLastRecord()) != XB_NO_ERROR )
rb_raise(rb_eRuntimeError, x->GetErrorMessage(status));
getData(self)->size = dbf->GetCurRecNo();
if(( status = dbf->GetFirstRecord()) != XB_NO_ERROR )
rb_raise(rb_eRuntimeError, x->GetErrorMessage(status));
return Qtrue;
}
static VALUE lib_path(VALUE self) {
if (!getData(self)->isDbfLoaded) return Qnil;
return rb_str_new2(getData(self)->path.c_str());
}
static VALUE lib_fields(VALUE self) {
if (!getData(self)->isDbfLoaded) return Qnil;
VALUE result = rb_hash_new();
//xbDbf *dbf = getData(self)->dbf;
fields_map *fields = getData(self)->fields;
for (fields_map::iterator it = fields->begin(); it != fields->end(); ++it) {
VALUE data = rb_hash_new();
rb_hash_aset(data, rb_str_new2("Type"), rb_str_new2(it->second.type.c_str()));
rb_hash_aset(data, rb_str_new2("Length"), INT2NUM(it->second.length));
rb_hash_aset(data, rb_str_new2("ID"), INT2NUM(it->second.fid));
rb_hash_aset(result, rb_str_new2(it->second.name.c_str()), data);
}
return result;
}
static VALUE lib_charset(VALUE self, VALUE input, VALUE target) {
if (TYPE(input) != T_STRING || TYPE(target) != T_STRING) rb_raise(rb_eArgError, "All arguments must be a valid string!");
lib_storage *settings = getData(self);
settings->needIconv = true;
//settings->
string charsetSrc = valueToChar(input);
//settings->
string charsetTrg = valueToChar(target);
settings->conver = new iconvpp::converter(charsetTrg, charsetSrc);
return Qtrue;
}
static VALUE lib_records(VALUE self) {
if (!getData(self)->isDbfLoaded) return Qnil;
return INT2NUM(getData(self)->size);
}
static VALUE lib_filters(VALUE self) {
if (!getData(self)->isDbfLoaded) return Qnil;
filter_map *filters = getData(self)->filters;
VALUE data = rb_ary_new();
for (filter_map::iterator it = filters->begin(); it != filters->end(); ++it) {
string type = "???";
if (it->condition == 0) type = "=";
if (it->condition == 1) type = ">";
if (it->condition == 2) type = "<";
if (it->condition == 3) type = "<>";
VALUE element = rb_hash_new();
rb_hash_aset(element, rb_str_new2("field"), rb_str_new2(it->name.c_str()));
rb_hash_aset(element, rb_str_new2("type"), rb_str_new2(it->type.c_str()));
rb_hash_aset(element, rb_str_new2("value"), it->value);
rb_hash_aset(element, rb_str_new2("id"), INT2NUM(it->fid));
rb_hash_aset(element, rb_str_new2("condition"), rb_str_new2(type.c_str()));
rb_ary_push(data, element);
}
return data;
}
static VALUE lib_enum(VALUE self) {
if (!getData(self)->isDbfLoaded) return Qnil;
xbDbf *dbf = getData(self)->dbf;
for (int i = 0; i < dbf->FieldCount(); i++ ) {
RETURN_ENUMERATOR(self, 0, NULL);
rb_yield( rb_str_new2( dbf->GetFieldName(i) ));
}
return Qnil;
}
static VALUE read_record(VALUE self, xbDbf *dbf, fields_map *fields, bool only_selected) {
VALUE data = rb_hash_new();
lib_storage *settings = getData(self);
selected_fields *selected = settings->selected;;
for (fields_map::iterator it = fields->begin(); it != fields->end(); ++it) {
if (only_selected && std::find(selected->begin(),selected->end(), it->second.name) == selected->end()) continue;
xbShort fid = dbf->GetFieldNo(it->second.name.c_str());
if (it->second.type == "Numeric") {
int value = dbf->GetLongField(fid);
rb_hash_aset(data, rb_str_new2(it->second.name.c_str()), INT2NUM(value) );
continue;
}
if (it->second.type == "String") {
string value = cleaner(string(dbf->GetStringField(fid)));
if (settings->needIconv) getData(self)->conver->convert(value, value);
rb_hash_aset(data, rb_str_new2(it->second.name.c_str()), rb_str_new2( value.c_str() ));
continue;
}
if (it->second.type == "Date") {
string raw_date = cleaner(string(dbf->GetStringField(fid)));
if (raw_date.size() == 8) {
/*
VALUE *args = new VALUE[3];
args[0] = INT2NUM(atoi(raw_date.substr(0,4).c_str()));
args[1] = INT2NUM(atoi(raw_date.substr(4,2).c_str()));
args[2] = INT2NUM(atoi(raw_date.substr(6,2).c_str()));
VALUE date = rb_class_new_instance(3, args, rb_path2class("Date"));
date = rb_funcall(date, rb_intern("inspect"), 0);
*/
VALUE date = rb_str_new2(string(raw_date.substr(6,2) + "." + raw_date.substr(4,2) + "." + raw_date.substr(0,4)).c_str());
rb_hash_aset(data, rb_str_new2(it->second.name.c_str()), date);
//delete []args;
} else {
// strange date = nil
rb_hash_aset(data, rb_str_new2(it->second.name.c_str()), Qnil);
}
continue;
}
}
VALUE *args = new VALUE[1];
args[0] = data;
data = rb_class_new_instance(1, args, Record);
delete []args;
return data;
}
static VALUE lib_all(VALUE self) {
getData(self)->filters->clear();
getData(self)->selected->clear();
return self;
}
static VALUE lib_where(int argc, VALUE *argv, VALUE self) {
if (!getData(self)->isDbfLoaded) return Qnil;
if (TYPE(argv[0]) != T_STRING && TYPE(argv[0]) != T_SYMBOL) rb_raise(rb_eArgError,"xBase Field (first argument) must be a string or a symbol!");
if (argc < 2 || argc > 3) rb_raise(rb_eArgError,"Too much parameters to filter (2 or 3 required)");
fields_map *fields = getData(self)->fields;
filter_map *filters = getData(self)->filters;
char condition = 0;
char *fieldName = valueToChar(argv[0]);
string type;
int fid = 0;
VALUE value = argv[1];
if (argc == 3) {
if (TYPE(argv[2]) != T_STRING && TYPE(argv[2]) != T_SYMBOL) rb_raise(rb_eArgError,"Condition (third argument) must be a string or a symbol!");
char *rType = valueToChar(argv[2]);
if (strcmp(rType, ">")==0 && strlen(rType)==1) condition = 1;
if (strcmp(rType, "<")==0 && strlen(rType)==1) condition = 2;
if (strcmp(rType, "<>")==0 && strlen(rType)==2) condition = 3;
if (strcmp(rType, "!=")==0 && strlen(rType)==2) condition = 3;
delete []rType;
}
bool finded = false;
for (fields_map::iterator it = fields->begin(); it != fields->end(); ++it) {
if (it->second.name == fieldName) { // String == char comparasion (must work with std::string overloaded == operation)
type = it->second.type;
fid = it->second.fid;
finded = true;
break;
}
}
if (!finded) rb_raise(rb_eArgError,"xBase Field doesn't exists!");
if (TYPE(argv[1]) == T_ARRAY) {
rb_raise(rb_eScriptError,"Not Implemented yet!");
}
filter data;
data.name = string(fieldName);
data.condition = condition;
data.value = value;
data.type = type;
data.fid = fid;
/*
if (TYPE(type) == T_FIXNUM || TYPE(type) == T_SYMBOL) type = rb_funcall(type, rb_intern("to_s"), 0);
// Check variable for object
if (TYPE(type) == T_OBJECT) {
// Check object for a date class
if (rb_funcall(type, rb_intern("instance_of?"), 1, rb_path2class("Date")) == Qtrue) {
type = rb_funcall(type, rb_intern("strftime"), 1, rb_str_new2("%Y%m%d"));
//type = rb_funcall(type, rb_intern("to_s"), 0);
}
}
if (TYPE(type) == T_STRING) {
data.value = type;;
} else {
rb_raise(rb_eArgError,"Unknown value (second argument) type!");
}
*/
filters->push_back(data);
return self;
}
static VALUE lib_find(int argc, VALUE *argv, VALUE self) {
if (!getData(self)->isDbfLoaded) return Qnil;
if (argc > 2 || argc < 1) {
rb_raise(rb_eArgError, "This method accepts only 1 parameter");
}
bool useYield = true;
if (argc == 2) {
if (argv[1] == Qfalse) useYield = false;
if (argv[1] == Qtrue) useYield = true;
}
lib_storage *settings = getData(self);
filter_map *filters = settings->filters;
fields_map *fields = settings->fields;
selected_fields *selected = settings->selected;
xbDbf *dbf = settings->dbf;
xbXBase *x = settings->x;
xbShort status;
int matches = 0;
bool only_selected = (selected->size() > 0);
bool error = true;
bool first = false;
bool count = false;
if (TYPE(argv[0]) == T_FIXNUM) {
if (argc > 2) rb_raise(rb_eArgError, "Too much parameters to get one record (required 1 param)");
int id = NUM2INT(argv[0]);
status = dbf->GetRecord(id);
if (status != XB_NO_ERROR) rb_raise(rb_eIOError, x->GetErrorMessage(status));
VALUE data = read_record(self, dbf, fields, only_selected);
if (useYield) {
RETURN_ENUMERATOR(self, 0, NULL);
rb_yield( data );
} else {
return data;
}
return Qnil;
}
if (TYPE(argv[0]) != T_STRING && TYPE(argv[0]) != T_SYMBOL) rb_raise(rb_eArgError, "Unknown type to search (string or fixnum expected)");
if (TYPE(argv[0]) == T_SYMBOL) argv[0] = rb_funcall(argv[0], rb_intern("to_s"), 0);
char *data = RSTRING(argv[0])->ptr;
if (strcmp(data, "first") == 0) {
error = false;
first = true;
}
if (strcmp(data, "all") == 0) {
error = false;
}
if (strcmp(data, "count") == 0) {
error = false;
count = true;
}
if (error) rb_raise(rb_eArgError, "Unknown find parameter");
VALUE dataArray;
if (!useYield) dataArray = rb_ary_new();
//status = dbf->GetFirstRecord();
//if (status != XB_NO_ERROR) rb_raise(rb_eIOError, x->GetErrorMessage(status));
int total = dbf->NoOfRecords();
for (int id=1; id < total; id++) {
bool finded = true;
//std::cout << "Record " << id << " of " << total << std::endl;
status = dbf->GetRecord(id);
if (status != XB_NO_ERROR) rb_raise(rb_eIOError, x->GetErrorMessage(status));
for (filter_map::iterator it = filters->begin(); it != filters->end(); ++it) {
int fid = it->fid;
if (it->type == "Numeric" && TYPE(it->value) != T_FIXNUM) rb_raise(rb_eArgError, "Find argument error: expected fixnum got unknown");
if (it->type == "String" && TYPE(it->value) != T_STRING) rb_raise(rb_eArgError, "Find argument error: expected string got unknown");
if (it->type == "Numeric" && it->condition == 0 && NUM2INT(it->value) != dbf->GetLongField(fid)) finded = false;
if (it->type == "Numeric" && it->condition == 1 && NUM2INT(it->value) < dbf->GetLongField(fid)) finded = false;
if (it->type == "Numeric" && it->condition == 2 && NUM2INT(it->value) > dbf->GetLongField(fid)) finded = false;
if (it->type == "Numeric" && it->condition == 3 && NUM2INT(it->value) == dbf->GetLongField(fid)) finded = false;
if (it->type == "String") {
string fieldVal = cleaner(string(dbf->GetStringField(fid)));
if (settings->needIconv) getData(self)->conver->convert(fieldVal,fieldVal); //fieldVal = iconvert(settings->charsetSrc, settings->charsetTrg, fieldVal);
if (it->condition == 0 && string(RSTRING(it->value)->ptr) != fieldVal) finded = false;
if (it->condition == 3 && string(RSTRING(it->value)->ptr) == fieldVal) finded = false;
}
if (it->type == "Date") {
}
//std::cout << "Filter " << it->name << "(type " << it->type << " id " << it->fid << ") return " << (finded ? "true" : "false") << std::endl;
}
if (!finded) continue; // skip record
if (count) {
matches++;
continue;
}
VALUE data = read_record(self, dbf, fields, only_selected);
if (useYield) {
RETURN_ENUMERATOR(self, 0, NULL);
rb_yield( data );
} else {
rb_ary_push(dataArray, data);
}
if (first) break; // show only one record
}
if (!useYield) return dataArray;
if (count) return INT2NUM(matches);
return Qnil;
}
static VALUE lib_record_init(VALUE self, VALUE array) {
Check_Type(array, T_HASH);
rb_iv_set(self, "@data", array);
return Qnil;
}
static VALUE lib_record_attributes(VALUE self) {
return rb_iv_get(self, "@data");
}
static VALUE lib_record_to_json(int argc, VALUE *argv, VALUE self) {
VALUE data = rb_iv_get(self, "@data");
data = rb_funcall(data, rb_intern("to_json"), 0);
return data;
}
static VALUE lib_record_call(int argc, VALUE *argv, VALUE self) {
if (argc < 1) rb_raise(rb_eSyntaxError, "Oops. You can't call missing_method manually!");
if (argc > 2) rb_raise(rb_eArgError, "Too much parameters (2 max)!");
VALUE hash = rb_iv_get(self, "@data");
VALUE key = argv[0];
if (argc == 1) {
if (TYPE(key) == T_SYMBOL) key = rb_funcall(key, rb_intern("to_s"), 0);
return rb_hash_aref(hash, key);
}
if (argc == 2) {
if (strcmp("[]", valueToChar(argv[0])) != 0) rb_raise(rb_eSyntaxError, "Very strange call...");
key = argv[1];
if (TYPE(key) == T_SYMBOL) key = rb_funcall(key, rb_intern("to_s"), 0);
return rb_hash_aref(hash, key);
}
// Protection from Segmentation Fault
rb_raise(rb_eSyntaxError, "Something strange were happened!");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment