/** * @file * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) * @copyright Simplified BSD * * @cond * This program is free software: you can redistribute it and/or modify * it under the terms of the FreeBSD license as published by the FreeBSD * project. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the FreeBSD license along with this * program. If not, see . * @endcond */ #include #include "uscxml/Common.h" #include "uscxml/config.h" #include "SWIDataModel.h" #include "uscxml/DOMUtils.h" #include "uscxml/Message.h" #include #ifdef BUILD_AS_PLUGINS #include #endif // these are defined but not exported by swi-prolog 7 extern "C" { PL_EXPORT(int) PL_is_dict(term_t t); PL_EXPORT(int) PL_for_dict(term_t dict, int (*func)(term_t key, term_t value, int last, void *closure), void *closure, int flags); } #define RETHROW_PLEX_AS_EVENT \ catch (PlException plex) { \ Event e; \ e.name = "error.execution"; \ e.data.compound["cause"] = (char*)plex; \ throw e; \ } \ #define PL_MODULE \ _interpreter.getSessionId().c_str() \ #define SET_PL_ENGINE(dm) \ assert(_swiEngines.find(dm) != _swiEngines.end()); \ int rc = PL_set_engine(_swiEngines[dm], NULL); \ assert(rc == PL_ENGINE_SET); \ _dmPtr = dm; namespace uscxml { using namespace Arabica::XPath; using namespace Arabica::DOM; #ifdef BUILD_AS_PLUGINS PLUMA_CONNECTOR bool pluginConnect(pluma::Host& host) { host.add( new SWIDataModelProvider() ); return true; } #endif // SWI prolog does not support passing user data static SWIDataModel* _dmPtr; static std::map _swiEngines; PL_blob_t SWIDataModel::blobType = { PL_BLOB_MAGIC, PL_BLOB_NOCOPY, (char*)"blob", releaseBlob, compareBlob, writeBlob, acquireBlob }; SWIDataModel::SWIDataModel() { } SWIDataModel::~SWIDataModel() { try { if (_swiEngines.find(this) != _swiEngines.end()) { PL_destroy_engine(_swiEngines[this]); _swiEngines.erase(this); } } RETHROW_PLEX_AS_EVENT; } boost::shared_ptr SWIDataModel::create(InterpreterImpl* interpreter) { try { boost::shared_ptr dm = boost::shared_ptr(new SWIDataModel()); dm->_interpreter = interpreter; const char* swibin = getenv("SWI_BINARY"); if (swibin == NULL) swibin = SWI_BINARY; const char* quiet = "--quiet"; int argc = 2; static char * av[] = { (char*)swibin, (char*)quiet, NULL }; if (!PL_is_initialised(NULL, NULL)) { if(!PL_initialise(argc,av)) { LOG(ERROR) << "Error intializing prolog engine"; PL_halt(1); return boost::shared_ptr(); } PL_set_engine(PL_ENGINE_CURRENT, &_engine); // load SWI XML parser try { PlCall("use_module", PlCompound("library", PlTerm("sgml"))); } catch (PlException plex) { LOG(ERROR) << "Cannot load prolog sgml module - make sure you have it installed in your prolog runtime: " << (char*)plex; throw plex; } // load json parser try { PlCall("use_module", PlCompound("library", PlTerm("http/json"))); PlCall("use_module", PlCompound("library", PlTerm("http/json_convert"))); } catch (PlException plex) { LOG(ERROR) << "Cannot load prolog json module or json_convert - make sure you have it installed in your prolog runtime: " << (char*)plex; throw plex; } } else { LOG(WARNING) << "Instantiating more than one SWI prolog datamodel will lead to weird effects as I cannot seperate the environments"; // engine = PL_create_engine(NULL); } // assert(engine); // _swiEngines[dm.get()] = engine; // _dmPtr = dm.get(); // int rc = PL_set_engine(engine, NULL); // assert(rc == PL_ENGINE_SET); // (void)rc; _plModule = boost::replace_all_copy(interpreter->getSessionId(), "-", ""); boost::replace_all(_plModule, "0", "g"); boost::replace_all(_plModule, "1", "h"); boost::replace_all(_plModule, "2", "i"); boost::replace_all(_plModule, "3", "j"); boost::replace_all(_plModule, "4", "k"); boost::replace_all(_plModule, "5", "l"); boost::replace_all(_plModule, "6", "m"); boost::replace_all(_plModule, "7", "n"); boost::replace_all(_plModule, "8", "o"); boost::replace_all(_plModule, "9", "p"); // use atoms for double quoted PlCall("set_prolog_flag(double_quotes,atom)."); // set system variables PlCall("assert", PlCompound("sessionid", PlTerm(PlString(dm->_interpreter->getSessionId().c_str())))); PlCall("assert", PlCompound("name", PlTerm(PlString(dm->_interpreter->getName().c_str())))); std::map::const_iterator ioProcIter = dm->_interpreter->getIOProcessors().begin(); while(ioProcIter != dm->_interpreter->getIOProcessors().end()) { Data ioProcData = ioProcIter->second.getDataModelVariables(); if (ioProcIter->first.find_first_of(":/'") == std::string::npos) { std::stringstream ioProcShortCall; ioProcShortCall << "assert(ioprocessors(" << ioProcIter->first << "(location('" << ioProcData.compound["location"].atom << "'))))"; PlCall(ioProcShortCall.str().c_str()); } std::stringstream ioProcCall; ioProcCall << "assert(ioprocessors(name('" << ioProcIter->first << "'), location('" << ioProcData.compound["location"].atom << "')))"; PlCall(ioProcCall.str().c_str()); ioProcIter++; } // the in predicate PlRegister("user", "in", 1, SWIDataModel::inPredicate); PL_set_engine(NULL, NULL); return dm; } RETHROW_PLEX_AS_EVENT; } foreign_t SWIDataModel::inPredicate(term_t a0, int arity, void* context) { try { char *s; if ( PL_get_atom_chars(a0, &s) ) { if (_dmPtr->_interpreter->isInState(s)) { return true; } } return FALSE; } RETHROW_PLEX_AS_EVENT; } void SWIDataModel::registerIOProcessor(const std::string& name, const IOProcessor& ioprocessor) { // std::cout << "SWIDataModel::registerIOProcessor" << std::endl; } void SWIDataModel::setSessionId(const std::string& sessionId) { // std::cout << "SWIDataModel::setSessionId" << std::endl; _sessionId = sessionId; } void SWIDataModel::setName(const std::string& name) { // std::cout << "SWIDataModel::setName" << std::endl; _name = name; } void SWIDataModel::pushContext() { // std::cout << "SWIDataModel::pushContext" << std::endl; } void SWIDataModel::popContext() { // std::cout << "SWIDataModel::popContext" << std::endl; } void SWIDataModel::initialize() { // std::cout << "SWIDataModel::initialize" << std::endl; } void SWIDataModel::setEvent(const Event& event) { SWIEngineLock engineLock; // remove old event try { PlCall("retractall(event(_))"); // simple values PlCall("assert", PlCompound("event", PlCompound("name", PlTerm(event.name.c_str())))); PlCall("assert", PlCompound("event", PlCompound("origin", PlString(event.origin.c_str())))); PlCall("assert", PlCompound("event", PlCompound("origintype", PlString(event.invokeid.c_str())))); PlCall("assert", PlCompound("event", PlCompound("invokeid", PlTerm(event.origintype.c_str())))); PlCall("assert", PlCompound("event", PlCompound("raw", PlString(event.raw.c_str())))); // event.type std::string type; switch (event.eventType) { case Event::PLATFORM: type = "platform"; break; case Event::INTERNAL: type = "internal"; break; case Event::EXTERNAL: type = "external"; break; } PlCall("assert", PlCompound("event", PlCompound("type", PlTerm(type.c_str())))); // event.sendid if (!event.hideSendId) PlCall("assert", PlCompound("event", PlCompound("sendid", PlTerm(event.sendid.c_str())))); // event.data URL domUrl; if (event.dom) { std::stringstream dataInitStr; std::stringstream xmlDoc; // xmlDoc << event.getFirstDOMElement(); xmlDoc << event.dom; domUrl = URL::toLocalFile(xmlDoc.str(), ".pl"); dataInitStr << "load_xml_file('" << domUrl.asLocalFile(".pl") << "', XML), copy_term(XML,DATA), assert(event(data(DATA)))"; PlCall(dataInitStr.str().c_str()); } else if (event.content.size() > 0) { PlCall("assert", PlCompound("event", PlCompound("data", PlString(InterpreterImpl::spaceNormalize(event.content).c_str())))); } else if (event.data) { assertFromData(event.data, "event(data(", 2); } Event::params_t::const_iterator paramIter = event.params.begin(); while(paramIter != event.params.end()) { assertFromData(paramIter->second, "event(param(" + paramIter->first + "(", 3); paramIter++; } Event::namelist_t::const_iterator namelistIter = event.namelist.begin(); while(namelistIter != event.namelist.end()) { assertFromData(paramIter->second, "event(param(" + namelistIter->first + "(", 3); namelistIter++; } #if 0 // event.params size_t uniqueKeys = 0; Event::params_t::const_iterator paramIter = event.params.begin(); while(paramIter != event.params.end()) { uniqueKeys++; paramIter = event.params.upper_bound(paramIter->first); } if (uniqueKeys > 0) { paramIter = event.params.begin(); for(int i = 0; paramIter != event.params.end(); i++) { std::stringstream paramArray; Event::params_t::const_iterator lastValueIter = event.params.upper_bound(paramIter->first); paramArray << paramIter->first << "(["; std::string termSep = ""; for (int j = 0; paramIter != lastValueIter; j++) { paramArray << termSep << "'" << paramIter->second << "'"; termSep = ", "; paramIter++; } paramArray << "])"; std::stringstream paramExpr; paramExpr << "assert(event(param(" << paramArray.str() << ")))"; //std::cout << paramExpr.str() << std::endl; PlCall(paramExpr.str().c_str()); paramIter = lastValueIter; } } #endif } RETHROW_PLEX_AS_EVENT; } void SWIDataModel::assertFromData(const Data& data, const std::string& expr, size_t nesting) { if (data.atom.size() > 0) { // terminal branch, this is where we assert std::stringstream ss; ss << expr; // nesting++; if (data.type == Data::VERBATIM) { ss << "\"" << data.atom << "\""; } else { ss << data.atom; } for (size_t i = 0; i < nesting; i++) { ss << ")"; } // std::cout << ss.str() << std::endl; PlCall("assert", PlCompound(ss.str().c_str())); return; } if (data.compound.size() > 0) { std::map::const_iterator compIter = data.compound.begin(); while(compIter != data.compound.end()) { // std::cout << compIter->first << std::endl; std::stringstream prefix; size_t prefixNesting = 0; size_t oldPos = 0; size_t pos = 0; while((pos = compIter->first.find_first_of(",.(-", oldPos)) != std::string::npos) { prefix << compIter->first.substr(oldPos, pos - oldPos) << "("; prefixNesting++; oldPos = pos + 1; } if (oldPos != compIter->first.size()) { prefix << compIter->first.substr(oldPos, compIter->first.size() - oldPos) << "("; prefixNesting++; } assertFromData(compIter->second, expr + prefix.str(), nesting + prefixNesting); compIter++; } } if (data.array.size() > 0) { std::list::const_iterator arrIter = data.array.begin(); while(arrIter != data.array.end()) { assertFromData(*arrIter, expr, nesting); arrIter++; } } if (data.node) { std::stringstream dataInitStr; std::stringstream xmlDoc; xmlDoc << data.node; URL domUrl = URL::toLocalFile(xmlDoc.str(), ".pl"); dataInitStr << "load_xml_file('" << domUrl.asLocalFile(".pl") << "', XML), "; dataInitStr << "copy_term(XML,DATA), "; dataInitStr << "assert("; dataInitStr << expr << "(DATA)"; for (size_t i = 0; i < nesting; i++) { dataInitStr << ")"; } PlCall(dataInitStr.str().c_str()); return; } } #if 0 std::list SWIDataModel::getSolutions(PlCompound compound) { std::list solutions; PlTermv termv(compound.arity()); for (int i = 0; i < compound.arity(); i++) { termv[i] = compound[i + 1]; } PlQuery query(compound.name(), termv); while(query.next_solution()) { // std::cout << (char*)compound << std::endl; solutions.push_back(compound); } return solutions; } #endif Data SWIDataModel::getStringAsData(const std::string& content) { SWIEngineLock engineLock; try { PlCompound compound(content.c_str()); PlCompound orig(content.c_str()); Data data; PlTermv termv(compound.arity()); for (int i = 0; i < compound.arity(); i++) { termv[i] = compound[i + 1]; } PlQuery query(compound.name(), termv); while(query.next_solution()) { std::map vars = resolveAtoms(compound, orig); std::map::const_iterator varIter = vars.begin(); while(varIter != vars.end()) { data.merge(termAsData(varIter->second)); varIter++; } } // std::cout << Data::toJSON(data) << std::endl; return data; } catch (PlException plex) { try { // could not parse as compound, try term and type in termAsData PlTerm term(content.c_str()); return(termAsData(term)); } RETHROW_PLEX_AS_EVENT } return Data(); } Data SWIDataModel::termAsData(PlTerm term) { Data data; // std::cout << term.name() << (char*)term << std::endl; switch (term.type()) { case PL_TERM: for (int i = 1; i <= term.arity(); i++) { // arguments start at 1 data.compound[term.name()] = termAsData(term[i]); } break; case PL_VARIABLE: case PL_INTEGER: case PL_FLOAT: case PL_SHORT: case PL_INT: case PL_LONG: case PL_DOUBLE: data.atom = std::string(term); data.type = Data::INTERPRETED; break; case PL_STRING: case PL_ATOM: data.atom = std::string(term); data.type = Data::VERBATIM; break; #ifdef SWI_HAS_PL_NIL case PL_NIL: data.array.push_back(Data("", Data::VERBATIM)); break; #endif #ifdef SWI_HAS_PL_LIST_PAIR case PL_LIST_PAIR: { PlTail tail(term); PlTerm item; while(tail.next(item)) { data.array.push_back(termAsData(item)); } break; } #endif #ifdef SWI_HAS_DICT case PL_DICT: { std::string key(term); size_t curlyPos = key.find_first_of("{"); if (curlyPos == std::string::npos || curlyPos == 0) { // no key given PL_for_dict(term, SWIDataModel::dictCallBack, &data, 0); } else { // with key given Data& tmp = data.compound[boost::trim_copy(key.substr(0, curlyPos))]; PL_for_dict(term, SWIDataModel::dictCallBack, &tmp, 0); } break; } #endif default: LOG(ERROR) << "Prolog type " << term.type() << " at '" << (char*)term << "' not supported"; break; } return data; } int SWIDataModel::dictCallBack(term_t key, term_t value, int last, void *closure) { Data* data = (Data*)closure; PlTerm keyTerm(key); data->compound[(char*)keyTerm] = termAsData(value); return 0; } PlTerm SWIDataModel::dataAsTerm(Data data) { if (data.atom.length() > 0) { return PlTerm(data.atom.c_str()); } if (data.array.size() > 0) { PlTerm head; PlTail list(head); std::list::const_iterator arrIter = data.array.begin(); while(arrIter != data.array.end()) { list.append(dataAsTerm(*arrIter)); arrIter++; } list.close(); return PlTail(head); } if (data.compound.size() > 0) { if (data.compound.size() == 1 && data.compound.begin()->second.array.size() > 0) { // this used to be a prolog compound const Data& arr = data.compound.begin()->second; PlTermv termv(arr.array.size()); int index = 0; std::list::const_iterator arrIter = arr.array.begin(); while(arrIter != arr.array.end()) { termv[index] = dataAsTerm(*arrIter); index++; arrIter++; } return PlCompound(data.compound.begin()->first.c_str(), termv); } else if (data.compound.size() == 1 && data.compound.begin()->second.compound.size() > 0) { // this used to be a named dict - until we have dict support in C/C++ use PL_chars_to_term std::stringstream dictSS; std::string seperator; dictSS << data.compound.begin()->first << "{"; std::map::const_iterator keyIter = data.compound.begin()->second.compound.begin(); while(keyIter != data.compound.begin()->second.compound.end()) { dictSS << seperator << keyIter->first << ":" << (char*)dataAsTerm(keyIter->second); seperator = ","; keyIter++; } dictSS << "}"; return PlCompound(dictSS.str().c_str()); } else { // an array of dicts PlTermv termv(data.compound.size()); int index = 0; std::map::const_iterator compIter = data.compound.begin(); while(compIter != data.compound.end()) { termv[index] = PlCompound(compIter->first.c_str(), dataAsTerm(compIter->second)); index++; compIter++; } return PlCompound(data.compound.begin()->first.c_str(), termv); } } if (data.binary) { LOG(ERROR) << "Binary data with prolog datamodel still very experimental"; // term_t binTerm = PL_new_term_ref(); // PL_put_blob(binTerm, data.binary->data, data.binary->size, &blobType); // return binTerm; } if (data.node) { LOG(ERROR) << "DOM in event with prolog datamodel still very experimental"; std::stringstream dataInitStr; std::stringstream xmlDoc; // xmlDoc << event.getFirstDOMElement(); xmlDoc << data.node; URL domUrl = URL::toLocalFile(xmlDoc.str(), ".pl"); dataInitStr << "load_xml_file('" << domUrl.asLocalFile(".pl") << "', XML), copy_term(XML,DATA), assert(event(data(DATA)))"; PlCall(dataInitStr.str().c_str()); } return PlTerm(); } bool SWIDataModel::validate(const std::string& location, const std::string& schema) { SWIEngineLock engineLock; // std::cout << "SWIDataModel::validate" << std::endl; return true; } uint32_t SWIDataModel::getLength(const std::string& expr) { SWIEngineLock engineLock; try { PlCompound compound(expr.c_str()); PlTermv termv(compound.arity()); for (int i = 0; i < compound.arity(); i++) { termv[i] = compound[i + 1]; } PlQuery query(compound.name(), termv); uint32_t length = 0; while(query.next_solution() > 0) length++; return length; } RETHROW_PLEX_AS_EVENT; } void SWIDataModel::setForeach(const std::string& item, const std::string& array, const std::string& index, uint32_t iteration) { SWIEngineLock engineLock; try { PlCompound compound(array.c_str()); PlCompound orig(array.c_str()); PlTermv termv(compound.arity()); for (int i = 0; i < compound.arity(); i++) { termv[i] = compound[i + 1]; } { int tmp = iteration + 1; PlQuery query(compound.name(), termv); while (tmp) { query.next_solution(); tmp--; } } PlCall("retractall", PlCompound(index.c_str(), 1)); PlCall("retractall", PlCompound(item.c_str(), 1)); PlCall("assert", PlCompound(index.c_str(), PlTerm((long)iteration))); std::map vars = resolveAtoms(compound, orig); std::map::iterator varIter = vars.begin(); while(varIter != vars.end()) { PlCall("assert", PlCompound(item.c_str(), varIter->second)); varIter++; } } RETHROW_PLEX_AS_EVENT; } void SWIDataModel::eval(const Element& scriptElem, const std::string& expr) { SWIEngineLock engineLock; try { if (scriptElem && HAS_ATTR(scriptElem, "type") && iequals(ATTR(scriptElem, "type"), "query")) { evalAsBool(expr); } else { URL localPLFile = URL::toLocalFile(expr, ".pl"); PlCall("user", "load_files", PlTermv(localPLFile.asLocalFile(".pl").c_str())) || LOG(ERROR) << "Could not execute prolog from file"; } } RETHROW_PLEX_AS_EVENT; } bool SWIDataModel::evalAsBool(const std::string& expr) { return evalAsBool(Arabica::DOM::Node(), expr); } bool SWIDataModel::evalAsBool(const Arabica::DOM::Node& node, const std::string& expr) { SWIEngineLock engineLock; try { PlCompound compound(expr.c_str()); PlTermv termv(compound.arity()); for (int i = 0; i < compound.arity(); i++) { termv[i] = compound[i + 1]; } PlQuery query(compound.name(), termv); return query.next_solution() > 0; } catch(...) { return false; } } std::string SWIDataModel::evalAsString(const std::string& expr) { SWIEngineLock engineLock; try { PlCompound compound(expr.c_str()); if (strlen(compound.name())) { PlCompound orig(expr.c_str()); PlTermv termv(compound.arity()); for (int i = 0; i < compound.arity(); i++) { termv[i] = compound[i + 1]; } PlQuery query(compound.name(), termv); std::stringstream ss; std::string solSeparator = ""; while (query.next_solution()) { ss << solSeparator; std::map vars = resolveAtoms(compound, orig); std::map::const_iterator varIter = vars.begin(); std::string varSeparator = ""; while(varIter != vars.end()) { ss << varSeparator << (char *)varIter->second; varSeparator = ", "; varIter++; } solSeparator = "\n"; } return ss.str(); } return std::string(compound); } catch(PlException plex) { // we got an exception while trying to evaluate as compound PlTerm term(expr.c_str()); if (term.type() == PL_ATOM || term.type() == PL_CHARS || term.type() == PL_STRING) { return std::string(term); } else { Event e; e.name = "error.execution"; e.data.compound["cause"] = (char*)plex; throw e; } } } // this is similar to http://etalis.googlecode.com/svn/eEtalis/src/term.c std::map SWIDataModel::resolveAtoms(PlTerm& term, PlTerm& orig) { try { std::map atoms; switch (orig.type()) { case PL_VARIABLE: { atoms[(char *)orig] = term; break; } case PL_ATOM: break; case PL_STRING: break; case PL_INTEGER: break; case PL_TERM: for (int i = 1; i <= orig.arity(); i++) { PlTerm newTerm = term[i]; PlTerm newOrig = orig[i]; std::map result = resolveAtoms(newTerm, newOrig); atoms.insert(result.begin(), result.end()); } break; default: LOG(ERROR) << "Resolving variable of unknown type in query solution"; } return atoms; } RETHROW_PLEX_AS_EVENT } void SWIDataModel::assign(const Element& assignElem, const Node& node, const std::string& content) { SWIEngineLock engineLock; try { std::string expr = content; std::string predicate; if (HAS_ATTR(assignElem, "expr")) { expr = ATTR(assignElem, "expr"); } if (HAS_ATTR(assignElem, "id")) predicate = ATTR(assignElem, "id"); if (HAS_ATTR(assignElem, "location")) predicate = ATTR(assignElem, "location"); if (predicate.size() > 0) { std::string callAssert = "assert"; std::string type; if (HAS_ATTR(assignElem, "type")) { type = ATTR(assignElem, "type"); if(iequals(type, "append")) { callAssert = "assertz"; } else if(iequals(type, "prepend")) { callAssert = "asserta"; } } URL domUrl; Data json; if (!node) json = Data::fromJSON(expr); if (node) { std::stringstream dataInitStr; std::stringstream xmlDoc; Node child = node; while(child) { xmlDoc << child; child = child.getNextSibling(); } domUrl = URL::toLocalFile(xmlDoc.str(), ".pl"); if (iequals(type, "retract")) PlCall("retractall", PlCompound(predicate.c_str(), 1)); dataInitStr << "load_xml_file('" << domUrl.asLocalFile(".pl") << "', XML), copy_term(XML,DATA), " << callAssert << "(" << predicate << "(DATA))"; PlCall(dataInitStr.str().c_str()); } else if (json) { std::stringstream dataInitStr; if (iequals(type, "retract")) PlCall("retractall", PlCompound(predicate.c_str(), 1)); dataInitStr << "json_to_prolog(" << expr << ", JSON), assert(" << predicate << "(JSON))"; PlCall(dataInitStr.str().c_str()); } else { // treat content as . seperated facts std::stringstream factStream(content); std::string item; while(std::getline(factStream, item, '.')) { std::string fact = boost::trim_copy(item); if (fact.length() == 0) continue; PlCall((callAssert + "(" + predicate + "(" + fact + "))").c_str()); } } } else if (expr.length() > 0) { if (boost::equals(TAGNAME(assignElem), "data")) { eval(assignElem, expr); } else { std::stringstream exprStream(expr); std::string item; while(std::getline(exprStream, item, '.')) { std::string plExpr = boost::trim_copy(item); if (plExpr.length() == 0) continue; PlCall(plExpr.c_str()); } } } } RETHROW_PLEX_AS_EVENT } void SWIDataModel::assign(const std::string& location, const Data& data) { eval(Element(), data.atom); } void SWIDataModel::init(const Element& dataElem, const Node& node, const std::string& content) { assign(dataElem, node, content); } void SWIDataModel::init(const std::string& location, const Data& data) { assign(location, data); } bool SWIDataModel::isDeclared(const std::string& expr) { return true; } void SWIDataModel::acquireBlob(atom_t symbol) { } int SWIDataModel::releaseBlob(atom_t symbol) { return TRUE; } int SWIDataModel::compareBlob(atom_t a, atom_t b) { return 0; } int SWIDataModel::writeBlob(void *s, atom_t symbol, int flags) { return TRUE; } }