From 45ab2909e17f7e0348ccfe4179f23a897a2fd305 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Wed, 10 Apr 2013 22:57:11 +0200 Subject: New DataModels --- CMakeLists.txt | 35 +- README.md | 8 +- contrib/ctest/CTestCustom.ctest.in | 21 +- src/bindings/swig/php/uscxmlNativePHP.php | 62 +++ src/uscxml/Factory.cpp | 12 + src/uscxml/Factory.h | 61 ++- src/uscxml/Interpreter.cpp | 52 ++- src/uscxml/Interpreter.h | 2 +- src/uscxml/interpreter/InterpreterDraft6.cpp | 11 +- src/uscxml/interpreter/InterpreterDraft7.cpp | 18 +- src/uscxml/interpreter/InterpreterDraft7.h | 2 +- .../datamodel/ecmascript/v8/V8DataModel.cpp | 14 +- .../plugins/datamodel/ecmascript/v8/V8DataModel.h | 30 +- .../plugins/datamodel/null/NULLDataModel.cpp | 121 ++++++ src/uscxml/plugins/datamodel/null/NULLDataModel.h | 77 ++++ .../plugins/datamodel/xpath/XPathDataModel.cpp | 470 +++++++++++++++++++++ .../plugins/datamodel/xpath/XPathDataModel.h | 143 +++++++ test/CMakeLists.txt | 6 + test/samples/w3c/ecma/test463.scxml | 2 +- test/src/test-arabica-xpath.cpp | 198 +++++++++ 20 files changed, 1270 insertions(+), 75 deletions(-) create mode 100644 src/uscxml/plugins/datamodel/null/NULLDataModel.cpp create mode 100644 src/uscxml/plugins/datamodel/null/NULLDataModel.h create mode 100644 src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp create mode 100644 src/uscxml/plugins/datamodel/xpath/XPathDataModel.h create mode 100644 test/src/test-arabica-xpath.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 87f6ef8..fd72602 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -659,6 +659,39 @@ if (APPLE AND IOS AND OFF) endif() + +# NULL datamodel + +file(GLOB NULL_DATAMODEL + src/uscxml/plugins/datamodel/null/*.cpp + src/uscxml/plugins/datamodel/null/*.h +) +source_group("Datamodel\\null" FILES ${NULL_DATAMODEL}) +if (BUILD_AS_PLUGINS) + add_library(datamodel_null SHARED ${NULL_DATAMODEL}) + target_link_libraries(datamodel_null uscxml) + set_target_properties(datamodel_null PROPERTIES FOLDER "Plugin DataModel") +else() + list (APPEND USCXML_FILES ${NULL_DATAMODEL}) +endif() + + +# XPath datamodel + +file(GLOB XPATH_DATAMODEL + src/uscxml/plugins/datamodel/xpath/*.cpp + src/uscxml/plugins/datamodel/xpath/*.h +) +source_group("Datamodel\\xpath" FILES ${XPATH_DATAMODEL}) +if (BUILD_AS_PLUGINS) + add_library(datamodel_xpath SHARED ${XPATH_DATAMODEL}) + target_link_libraries(datamodel_xpath uscxml) + set_target_properties(datamodel_xpath PROPERTIES FOLDER "Plugin DataModel") +else() + list (APPEND USCXML_FILES ${XPATH_DATAMODEL}) +endif() + + # GOOGLE V8 ecmascript datamodel #if (NOT APPLE OR IOS) @@ -759,8 +792,8 @@ if (UMUNDO_FOUND) list (APPEND USCXML_FILES ${UMUNDO_INVOKER}) list (APPEND USCXML_OPT_LIBS ${UMUNDO_LIBRARIES}) endif() + add_definitions("-DUMUNDO_STATIC") endif() -add_definitions("-DUMUNDO_STATIC") # USCXML invoker diff --git a/README.md b/README.md index 476d14b..05d2017 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ the respective build-process and did not precompile required libraries. * Datamodels * ECMAScript using Google's v8 and JavaScriptCore (JSC is incomplete) * Prolog using SWI prolog - * No NULL datamodel yet - * No XPath datamodel yet + * NULL datamodel with required In predicate + * No XPath datamodel yet * Invokers * scxml: Invoke a nested scxml interpreter * dirmon: Watches a directory for changes to files @@ -23,7 +23,7 @@ the respective build-process and did not precompile required libraries. * Communication * Features the standard basichttp io-processor * Features the required SCXML io-processor - * No DOM io-processor + * No DOM io-processor yet * Can actually respond to HTTP requests with data via <response> * Language Bindings * PHP module for apache and cli interpreter @@ -33,7 +33,7 @@ the respective build-process and did not precompile required libraries. We continuously run the [W3C IRP tests](http://www.w3.org/Voice/2013/scxml-irp/) for SCXML. The results the for various platforms can be [found here](http://uscxml.tk.informatik.tu-darmstadt.de/cdash/index.php?project=uscxml). There are a few [excluded tests](https://github.com/tklab-tud/uscxml/blob/master/contrib/ctest/CTestCustom.ctest.in) -regarding the NULL and XPath datamodel, as well as the manual tests. +regarding the XPath datamodel and the manual tests. uSCXML still fails the following tests: diff --git a/contrib/ctest/CTestCustom.ctest.in b/contrib/ctest/CTestCustom.ctest.in index 480cb18..fd93ab6 100644 --- a/contrib/ctest/CTestCustom.ctest.in +++ b/contrib/ctest/CTestCustom.ctest.in @@ -8,25 +8,6 @@ # skip xpath datamodel tests set(CTEST_CUSTOM_TESTS_IGNORE - "test463.scxml" - "test464.scxml" - "test465.scxml" - "test466.scxml" - "test467.scxml" - "test468.scxml" - "test469.scxml" - "test470.scxml" - "test473.scxml" - "test474.scxml" - "test475.scxml" - "test476.scxml" - "test477.scxml" - "test478.scxml" - "test479.scxml" - "test480.scxml" - "test481.scxml" - "test482.scxml" - "test537.scxml" "test539.scxml" "test540.scxml" "test542.scxml" @@ -35,13 +16,13 @@ set(CTEST_CUSTOM_TESTS_IGNORE "test547.scxml" "test555.scxml" "test568.scxml" + "test178.scxml" "test230.scxml" "test250.scxml" "test307.scxml" "test313.scxml" "test314.scxml" - "test436.scxml" ) diff --git a/src/bindings/swig/php/uscxmlNativePHP.php b/src/bindings/swig/php/uscxmlNativePHP.php index 34984ab..89cad82 100644 --- a/src/bindings/swig/php/uscxmlNativePHP.php +++ b/src/bindings/swig/php/uscxmlNativePHP.php @@ -26,6 +26,64 @@ if (!extension_loaded('uscxmlNativePHP')) { +abstract class uscxmlNativePHP { + const CAN_NOTHING = 0; + + const CAN_BASIC_HTTP = 1; + + const CAN_GENERIC_HTTP = 2; + + const ANY = 0; + + const BOOL = BOOL; + + const NUMBER = NUMBER; + + const STRING = STRING; + + const NODE_SET = NODE_SET; + + static function NaN_get() { + return NaN_get(); + } + + static function Zero_get() { + return Zero_get(); + } + + static function Negative_Zero_get() { + return Negative_Zero_get(); + } + + static function Infinity_get() { + return Infinity_get(); + } + + static function Negative_Infinity_get() { + return Negative_Infinity_get(); + } + + static function isNaN($value) { + return isNaN($value); + } + + static function isInfinity($value) { + return isInfinity($value); + } + + static function isNegativeInfinity($value) { + return isNegativeInfinity($value); + } + + static function isInfinite($value) { + return isInfinite($value); + } + + static function roundNumber($value) { + return roundNumber($value); + } +} + /* PHP Proxy Classes */ class Data { public $_cPtr=null; @@ -67,6 +125,10 @@ class Data { } } + function isValid() { + return Data_isValid($this->_cPtr); + } + static function fromJSON($jsonString) { $r=Data_fromJSON($jsonString); if (is_resource($r)) { diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp index e927693..c74196a 100644 --- a/src/uscxml/Factory.cpp +++ b/src/uscxml/Factory.cpp @@ -47,6 +47,10 @@ # include "uscxml/plugins/datamodel/prolog/swi/SWIDataModel.h" # endif +#include "uscxml/plugins/datamodel/null/NULLDataModel.h" +#include "uscxml/plugins/datamodel/xpath/XPathDataModel.h" + + # include "uscxml/plugins/element/fetch/FetchElement.h" # include "uscxml/plugins/element/response/ResponseElement.h" # include "uscxml/plugins/element/postpone/PostponeElement.h" @@ -146,6 +150,14 @@ Factory::Factory() { // these are always available { + NULLDataModel* dataModel = new NULLDataModel(); + registerDataModel(dataModel); + } + { + XPathDataModel* dataModel = new XPathDataModel(); + registerDataModel(dataModel); + } + { USCXMLInvoker* invoker = new USCXMLInvoker(); registerInvoker(invoker); } diff --git a/src/uscxml/Factory.h b/src/uscxml/Factory.h index c396e63..540cf61 100644 --- a/src/uscxml/Factory.h +++ b/src/uscxml/Factory.h @@ -240,14 +240,33 @@ public: virtual void pushContext() = 0; virtual void popContext() = 0; + virtual bool supportsJSON() { return false; } + virtual void eval(const std::string& expr) = 0; virtual std::string evalAsString(const std::string& expr) = 0; virtual bool evalAsBool(const std::string& expr) = 0; - virtual void assign(const std::string& location, const Arabica::DOM::Document& doc) = 0; - virtual void assign(const std::string& location, const std::string& expr) = 0; - virtual void assign(const std::string& location, const Data& data) = 0; virtual bool isDeclared(const std::string& expr) = 0; + virtual void assign(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& assignElem) = 0; + virtual void assign(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& assignElem) = 0; + virtual void assign(const std::string& location, + const Data& data, + const Arabica::DOM::Element& assignElem) = 0; + + virtual void init(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& dataElem) = 0; + virtual void init(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& dataElem) = 0; + virtual void init(const std::string& location, + const Data& data, + const Arabica::DOM::Element& dataElem) = 0; + protected: InterpreterImpl* _interpreter; }; @@ -295,6 +314,9 @@ public: virtual void popContext() { return _impl->popContext(); } + virtual bool supportsJSON() { + return _impl->supportsJSON(); + } virtual void eval(const std::string& expr) { return _impl->eval(expr); @@ -306,15 +328,36 @@ public: return _impl->evalAsBool(expr); } - virtual void assign(const std::string& location, const Arabica::DOM::Document& doc) { - return _impl->assign(location, doc); + virtual void assign(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& assignElem) { + return _impl->assign(location, doc, assignElem); + } + virtual void assign(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& assignElem) { + return _impl->assign(location, expr, assignElem); + } + virtual void assign(const std::string& location, + const Data& data, + const Arabica::DOM::Element& assignElem) { + return _impl->assign(location, data, assignElem); } - virtual void assign(const std::string& location, const std::string& expr) { - return _impl->assign(location, expr); + virtual void init(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& dataElem) { + return _impl->init(location, doc, dataElem); + } + virtual void init(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& dataElem) { + return _impl->init(location, expr, dataElem); } - virtual void assign(const std::string& location, const Data& data) { - return _impl->assign(location, data); + virtual void init(const std::string& location, + const Data& data, + const Arabica::DOM::Element& dataElem) { + return _impl->init(location, data, dataElem); } virtual bool isDeclared(const std::string& expr) { diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 33d3b03..a78d4a0 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -313,7 +313,7 @@ void InterpreterImpl::init() { /** * Called with a single data element from the topmost datamodel element. */ -void InterpreterImpl::initializeData(const Node& data) { +void InterpreterImpl::initializeData(const Element& data) { if (!_dataModel) { LOG(ERROR) << "Cannot initialize data when no datamodel is given!"; return; @@ -327,7 +327,7 @@ void InterpreterImpl::initializeData(const Node& data) { /// test 240 - initialize from invoke request if (_invokeReq.params.find(ATTR(data, "id")) != _invokeReq.params.end()) { try { - _dataModel.assign(ATTR(data, "id"), _invokeReq.params.find(ATTR(data, "id"))->second); + _dataModel.init(ATTR(data, "id"), _invokeReq.params.find(ATTR(data, "id"))->second, data); } catch (Event e) { LOG(ERROR) << "Syntax error when initializing data from parameters:" << std::endl << e << std::endl; receiveInternal(e); @@ -336,7 +336,7 @@ void InterpreterImpl::initializeData(const Node& data) { } if (_invokeReq.namelist.find(ATTR(data, "id")) != _invokeReq.namelist.end()) { try { - _dataModel.assign(ATTR(data, "id"), _invokeReq.namelist.find(ATTR(data, "id"))->second); + _dataModel.init(ATTR(data, "id"), _invokeReq.namelist.find(ATTR(data, "id"))->second, data); } catch (Event e) { LOG(ERROR) << "Syntax error when initializing data from namelist:" << std::endl << e << std::endl; receiveInternal(e); @@ -350,13 +350,13 @@ void InterpreterImpl::initializeData(const Node& data) { // expression given directly std::string value = ATTR(data, "expr"); try { - _dataModel.assign(ATTR(data, "id"), value); + _dataModel.init(ATTR(data, "id"), value, data); } catch (Event e) { LOG(ERROR) << "Syntax error in data element:" << std::endl << e << std::endl; /// test 277 /// todo: if the identifier is invalid we'll raise to error events receiveInternal(e); - _dataModel.assign(ATTR(data, "id"), "undefined"); + _dataModel.init(ATTR(data, "id"), "undefined", data); } return; } @@ -385,12 +385,12 @@ void InterpreterImpl::initializeData(const Node& data) { Arabica::SAX2DOM::Parser parser; if(parser.parse(inputSource) && parser.getDocument()) { try { - _dataModel.assign(ATTR(data, "id"), parser.getDocument()); + _dataModel.init(ATTR(data, "id"), parser.getDocument(), data); return; } catch (Event e) { LOG(ERROR) << "Syntax error in data element:" << std::endl << e << std::endl; receiveInternal(e); - _dataModel.assign(ATTR(data, "id"), "undefined"); + _dataModel.init(ATTR(data, "id"), "undefined", data); } return; } @@ -416,7 +416,7 @@ void InterpreterImpl::initializeData(const Node& data) { Document dom = domFactory.createDocument(contentChild.getNamespaceURI(), "", 0); Node newNode = dom.importNode(contentChild, true); dom.appendChild(newNode); - _dataModel.assign(ATTR(data, "id"), dom); + _dataModel.init(ATTR(data, "id"), dom, data); return; } else if (contentChild) { // get first child and process below @@ -428,7 +428,7 @@ void InterpreterImpl::initializeData(const Node& data) { if (contentToProcess.length() > 0) { /// try to interpret as JSON try { - _dataModel.assign(ATTR(data, "id"), contentToProcess); + _dataModel.init(ATTR(data, "id"), contentToProcess, data); } catch(Event e) { /// create space normalized string if that failed /// test 558 @@ -443,10 +443,10 @@ void InterpreterImpl::initializeData(const Node& data) { seperator = " "; } } while (iss); - _dataModel.assign(ATTR(data, "id"), Data(spaceNormalized.str(), Data::VERBATIM)); + _dataModel.init(ATTR(data, "id"), Data(spaceNormalized.str(), Data::VERBATIM), data); } } else { - _dataModel.assign(ATTR(data, "id"), "undefined"); + _dataModel.init(ATTR(data, "id"), "undefined", data); } } catch (Event e) { @@ -726,7 +726,7 @@ void InterpreterImpl::send(const Arabica::DOM::Node& element) { */ sendReq.sendid = ATTR(getParentState(element), "id") + "." + getUUID(); if (HAS_ATTR(element, "idlocation") && _dataModel) { - _dataModel.assign(ATTR(element, "idlocation"), "'" + sendReq.sendid + "'"); + _dataModel.assign(ATTR(element, "idlocation"), "'" + sendReq.sendid + "'", Element()); } else { sendReq.hideSendId = true; } @@ -880,7 +880,7 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node& element) { } else { invokeReq.invokeid = ATTR(getParentState(element), "id") + "." + getUUID(); if (HAS_ATTR(element, "idlocation") && _dataModel) { - _dataModel.assign(ATTR(element, "idlocation"), "'" + invokeReq.invokeid + "'"); + _dataModel.assign(ATTR(element, "idlocation"), "'" + invokeReq.invokeid + "'", Element()); } } } catch (Event e) { @@ -941,7 +941,7 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node& element) { } if (_dataModel) { try { - _dataModel.assign("_invokers['" + invokeReq.invokeid + "']", invoker.getDataModelVariables()); + _dataModel.assign("_invokers['" + invokeReq.invokeid + "']", invoker.getDataModelVariables(), Element()); } catch(...) { LOG(ERROR) << "Exception caught while assigning datamodel variables from invoker " << invokeReq.invokeid; } @@ -971,7 +971,7 @@ void InterpreterImpl::cancelInvoke(const Arabica::DOM::Node& elemen LOG(INFO) << "Removed invoker at " << invokeId; if (_dataModel) { try { - _dataModel.assign("_invokers['" + invokeId + "']", "''"); + _dataModel.assign("_invokers['" + invokeId + "']", "''", Element()); } catch (Event e) { LOG(ERROR) << "Syntax when removing invoker:" << std::endl << e << std::endl; } @@ -1139,13 +1139,13 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node& cont // assign array element to item std::stringstream ss; ss << array << "[" << iteration << "]"; - _dataModel.assign(item, ss.str()); + _dataModel.assign(item, ss.str(), Element()); } if (index.length() > 0) { // assign iteration element to index std::stringstream ss; ss << iteration; - _dataModel.assign(index,ss.str()); + _dataModel.assign(index,ss.str(), Element()); } if (content.hasChildNodes()) // execute content and have exception rethrown to break foreach @@ -1176,14 +1176,28 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Node& cont } } else if (boost::iequals(TAGNAME(content), _xmlNSPrefix + "assign")) { // --- ASSIGN -------------------------- - if (_dataModel && HAS_ATTR(content, "location") && HAS_ATTR(content, "expr")) { + if (_dataModel && HAS_ATTR(content, "location")) { try { if (!_dataModel.isDeclared(ATTR(content, "location"))) { // test 286, 331 LOG(ERROR) << "Assigning to undeclared location '" << ATTR(content, "location") << "' not allowed." << std::endl; throw Event("error.execution", Event::PLATFORM); } else { - _dataModel.assign(ATTR(content, "location"), ATTR(content, "expr")); + Data data; + Document dom; + std::string text; + processContentElement(content, dom, text, data); + + if (dom) { + _dataModel.assign(ATTR(content, "location"), dom, Element(content)); + } else if(data && _dataModel.supportsJSON()) { + _dataModel.assign(ATTR(content, "location"), data, Element(content)); + } else if (text.length() > 0) { + _dataModel.assign(ATTR(content, "location"), text, Element(content)); + } else { + LOG(ERROR) << "Assign element does not specify any content" << std::endl; + throw Event("error.execution", Event::PLATFORM); + } } } CATCH_AND_DISTRIBUTE("Syntax error in attributes of assign element:") diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index 141d630..673cba1 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -222,7 +222,7 @@ protected: void init(); void normalize(Arabica::DOM::Element& scxml); - void initializeData(const Arabica::DOM::Node& data); + void initializeData(const Arabica::DOM::Element& data); void setupIOProcessors(); bool _stable; diff --git a/src/uscxml/interpreter/InterpreterDraft6.cpp b/src/uscxml/interpreter/InterpreterDraft6.cpp index 3c4a699..dfcc457 100644 --- a/src/uscxml/interpreter/InterpreterDraft6.cpp +++ b/src/uscxml/interpreter/InterpreterDraft6.cpp @@ -34,7 +34,7 @@ void InterpreterDraft6::interpret() { } if (_dataModel) { - _dataModel.assign("_x.args", _cmdLineOptions); + _dataModel.assign("_x.args", _cmdLineOptions, Element()); } setupIOProcessors(); @@ -50,13 +50,15 @@ void InterpreterDraft6::interpret() { for (unsigned int i = 0; i < dataElems.size(); i++) { // do not process data elements of nested documents from invokers if (!getAncestorElement(dataElems[i], _xmlNSPrefix + "invoke")) - initializeData(dataElems[i]); + if (dataElems[i].getNodeType() == Node_base::ELEMENT_NODE) + initializeData(Element(dataElems[i])); } } else if(_dataModel) { // initialize current data elements NodeSet topDataElems = filterChildElements(_xmlNSPrefix + "data", filterChildElements(_xmlNSPrefix + "datamodel", _scxml)); for (unsigned int i = 0; i < topDataElems.size(); i++) { - initializeData(topDataElems[i]); + if (topDataElems[i].getNodeType() == Node_base::ELEMENT_NODE) + initializeData(Element(topDataElems[i])); } } @@ -843,7 +845,8 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& if(dataModelElems.size() > 0 && _dataModel) { Arabica::XPath::NodeSet dataElems = filterChildElements(_xmlNSPrefix + "data", dataModelElems[0]); for (int j = 0; j < dataElems.size(); j++) { - initializeData(dataElems[j]); + if (dataElems[j].getNodeType() == Node_base::ELEMENT_NODE) + initializeData(Element(dataElems[j])); } } stateElem.setAttribute("isFirstEntry", ""); diff --git a/src/uscxml/interpreter/InterpreterDraft7.cpp b/src/uscxml/interpreter/InterpreterDraft7.cpp index 5f07cfc..e02a343 100644 --- a/src/uscxml/interpreter/InterpreterDraft7.cpp +++ b/src/uscxml/interpreter/InterpreterDraft7.cpp @@ -45,11 +45,11 @@ void InterpreterDraft7::interpret() { } if (_dataModel) { - _dataModel.assign("_x.args", _cmdLineOptions); + _dataModel.assign("_x.args", _cmdLineOptions, Element()); if (_httpServlet) { Data data; data.compound["location"] = Data(_httpServlet->getURL(), Data::VERBATIM); - _dataModel.assign("_ioprocessors['http']", data); + _dataModel.assign("_ioprocessors['http']", data, Element()); } } @@ -64,13 +64,15 @@ void InterpreterDraft7::interpret() { // initialize all data elements NodeSet dataElems = _xpath.evaluate("//" + _xpathPrefix + "data", _document).asNodeSet(); for (unsigned int i = 0; i < dataElems.size(); i++) { - initializeData(dataElems[i]); + if (dataElems[i].getNodeType() == Node_base::ELEMENT_NODE) + initializeData(Element(dataElems[i])); } } else if(_dataModel) { // initialize current data elements NodeSet topDataElems = filterChildElements(_xmlNSPrefix + "data", filterChildElements(_xmlNSPrefix + "datamodel", _scxml)); for (unsigned int i = 0; i < topDataElems.size(); i++) { - initializeData(topDataElems[i]); + if (topDataElems[i].getNodeType() == Node_base::ELEMENT_NODE) + initializeData(Element(topDataElems[i])); } } @@ -117,7 +119,7 @@ void InterpreterDraft7::interpret() { /** * Called with a single data element from the topmost datamodel element. */ -void InterpreterDraft7::initializeData(const Arabica::DOM::Node& data) { +void InterpreterDraft7::initializeData(const Arabica::DOM::Element& data) { if (!_dataModel) { LOG(ERROR) << "Cannot initialize data when no datamodel is given!"; return; @@ -130,7 +132,7 @@ void InterpreterDraft7::initializeData(const Arabica::DOM::Node& da if (HAS_ATTR(data, "expr")) { std::string value = ATTR(data, "expr"); - _dataModel.assign(ATTR(data, "id"), value); + _dataModel.assign(ATTR(data, "id"), value, data); } else if (HAS_ATTR(data, "src")) { URL srcURL(ATTR(data, "src")); if (!srcURL.isAbsolute()) @@ -143,7 +145,7 @@ void InterpreterDraft7::initializeData(const Arabica::DOM::Node& da ss << srcURL; _cachedURLs[srcURL.asString()] = srcURL; } - _dataModel.assign(ATTR(data, "id"), ss.str()); + _dataModel.assign(ATTR(data, "id"), ss.str(), data); } else if (data.hasChildNodes()) { // search for the text node with the actual script @@ -151,7 +153,7 @@ void InterpreterDraft7::initializeData(const Arabica::DOM::Node& da for (int i = 0; i < dataChilds.getLength(); i++) { if (dataChilds.item(i).getNodeType() == Node_base::TEXT_NODE) { Data value = Data(dataChilds.item(i).getNodeValue()); - _dataModel.assign(ATTR(data, "id"), value); + _dataModel.assign(ATTR(data, "id"), value, data); break; } } diff --git a/src/uscxml/interpreter/InterpreterDraft7.h b/src/uscxml/interpreter/InterpreterDraft7.h index 7600041..6763e95 100644 --- a/src/uscxml/interpreter/InterpreterDraft7.h +++ b/src/uscxml/interpreter/InterpreterDraft7.h @@ -37,7 +37,7 @@ class InterpreterDraft7 : public InterpreterImpl { const Arabica::XPath::NodeSet& statesToEnter, const Arabica::XPath::NodeSet& statesForDefaultEntry); - void initializeData(const Arabica::DOM::Node& data); + void initializeData(const Arabica::DOM::Element& data); }; diff --git a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp index 0146a31..2491fc4 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp @@ -392,7 +392,9 @@ double V8DataModel::evalAsNumber(const std::string& expr) { return 0; } -void V8DataModel::assign(const std::string& location, const Arabica::DOM::Document& doc) { +void V8DataModel::assign(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& dataElem) { v8::Locker locker; v8::HandleScope handleScope; v8::Context::Scope contextScope(_contexts.front()); @@ -402,17 +404,21 @@ void V8DataModel::assign(const std::string& location, const Arabica::DOM::Docume } -void V8DataModel::assign(const std::string& location, const Data& data) { +void V8DataModel::assign(const std::string& location, + const Data& data, + const Arabica::DOM::Element& dataElem) { v8::Locker locker; v8::HandleScope handleScope; v8::Context::Scope contextScope(_contexts.front()); std::stringstream ssJSON; ssJSON << data; - assign(location, ssJSON.str()); + assign(location, ssJSON.str(), dataElem); } -void V8DataModel::assign(const std::string& location, const std::string& expr) { +void V8DataModel::assign(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& dataElem) { v8::Locker locker; v8::HandleScope handleScope; v8::Context::Scope contextScope(_contexts.back()); diff --git a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h index 760b638..7bea50c 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h +++ b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h @@ -39,10 +39,34 @@ public: virtual void pushContext(); virtual void popContext(); + virtual bool supportsJSON() { return true; } + virtual void eval(const std::string& expr); - virtual void assign(const std::string& location, const Arabica::DOM::Document& doc); - virtual void assign(const std::string& location, const std::string& expr); - virtual void assign(const std::string& location, const Data& data); + virtual void assign(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& assignElem); + virtual void assign(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& assignElem); + virtual void assign(const std::string& location, + const Data& data, + const Arabica::DOM::Element& assignElem); + + virtual void init(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& dataElem) { + assign(location, doc, dataElem); + }; + virtual void init(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& dataElem) { + assign(location, expr, dataElem); + } + virtual void init(const std::string& location, + const Data& data, + const Arabica::DOM::Element& dataElem) { + assign(location, data, dataElem); + } virtual Data getStringAsData(const std::string& content); virtual Data getValueAsData(const v8::Handle& value); diff --git a/src/uscxml/plugins/datamodel/null/NULLDataModel.cpp b/src/uscxml/plugins/datamodel/null/NULLDataModel.cpp new file mode 100644 index 0000000..69970dd --- /dev/null +++ b/src/uscxml/plugins/datamodel/null/NULLDataModel.cpp @@ -0,0 +1,121 @@ +#include "uscxml/Common.h" +#include "NULLDataModel.h" + +#include "uscxml/Message.h" +#include + +#ifdef BUILD_AS_PLUGINS +#include +#endif + +namespace uscxml { + +#ifdef BUILD_AS_PLUGINS +PLUMA_CONNECTOR +bool connect(pluma::Host& host) { + host.add( new NULLDataModelProvider() ); + return true; +} +#endif + +NULLDataModel::NULLDataModel() { +} + +boost::shared_ptr NULLDataModel::create(InterpreterImpl* interpreter) { + boost::shared_ptr dm = boost::shared_ptr(new NULLDataModel()); + dm->_interpreter = interpreter; + return dm; +} + +NULLDataModel::~NULLDataModel() { +} + +void NULLDataModel::pushContext() { +} + +void NULLDataModel::popContext() { +} + +void NULLDataModel::initialize() { +} + +void NULLDataModel::setEvent(const Event& event) { +} + +Data NULLDataModel::getStringAsData(const std::string& content) { + Data data; + return data; +} + +bool NULLDataModel::validate(const std::string& location, const std::string& schema) { + return true; +} + +uint32_t NULLDataModel::getLength(const std::string& expr) { + return 0; +} + +void NULLDataModel::eval(const std::string& expr) { +} + +bool NULLDataModel::isDeclared(const std::string& expr) { + return true; +} + +/** + * The boolean expression language consists of the In predicate only. It has the + * form 'In(id)', where id is the id of a state in the enclosing state machine. + * The predicate must return 'true' if and only if that state is in the current + * state configuration. + */ +bool NULLDataModel::evalAsBool(const std::string& expr) { + std::string trimmedExpr = expr; + boost::trim(trimmedExpr); + if (!boost::istarts_with(trimmedExpr, "in")) + return false; + + // find string in between brackets + size_t start = trimmedExpr.find_first_of("("); + size_t end = trimmedExpr.find_last_of(")"); + if (start == std::string::npos || end == std::string::npos || start >= end) + return false; + start++; + + // split at comma + std::stringstream ss(trimmedExpr.substr(start, end - start)); + std::vector stateExprs; + std::string item; + while(std::getline(ss, item, ',')) { + stateExprs.push_back(item); + } + + for (unsigned int i = 0; i < stateExprs.size(); i++) { + // remove ticks + size_t start = stateExprs[i].find_first_of("'"); + size_t end = stateExprs[i].find_last_of("'"); + + std::string stateName; + if (start != std::string::npos && end != std::string::npos && start < end) { + start++; + stateName = stateExprs[i].substr(start, end - start); + } else { + stateName = stateExprs[i]; + } + + if (Interpreter::isMember(_interpreter->getState(stateName), _interpreter->getConfiguration())) { + continue; + } + return false; + } + return true; +} + +std::string NULLDataModel::evalAsString(const std::string& expr) { + return expr; +} + +double NULLDataModel::evalAsNumber(const std::string& expr) { + return 0; +} + +} \ No newline at end of file diff --git a/src/uscxml/plugins/datamodel/null/NULLDataModel.h b/src/uscxml/plugins/datamodel/null/NULLDataModel.h new file mode 100644 index 0000000..eaa9dbd --- /dev/null +++ b/src/uscxml/plugins/datamodel/null/NULLDataModel.h @@ -0,0 +1,77 @@ +#ifndef NULLDATAMODEL_H_KN8TWG0V +#define NULLDATAMODEL_H_KN8TWG0V + +#include "uscxml/Interpreter.h" +#include + +#ifdef BUILD_AS_PLUGINS +#include "uscxml/plugins/Plugins.h" +#endif + +namespace uscxml { +class Event; +class Data; +} + +namespace uscxml { + +class NULLDataModel : public DataModelImpl { +public: + NULLDataModel(); + virtual ~NULLDataModel(); + virtual boost::shared_ptr create(InterpreterImpl* interpreter); + + virtual std::set getNames() { + std::set names; + names.insert("null"); + return names; + } + + virtual void initialize(); + virtual void setEvent(const Event& event); + + virtual bool validate(const std::string& location, const std::string& schema); + + virtual uint32_t getLength(const std::string& expr); + virtual void pushContext(); + virtual void popContext(); + + virtual void eval(const std::string& expr); + virtual void assign(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& assignElem) {} + virtual void assign(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& assignElem) {} + virtual void assign(const std::string& location, + const Data& data, + const Arabica::DOM::Element& assignElem) {} + + virtual void init(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& dataElem) {}; + virtual void init(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& dataElem) {}; + virtual void init(const std::string& location, + const Data& data, + const Arabica::DOM::Element& dataElem) {}; + + virtual Data getStringAsData(const std::string& content); + virtual bool isDeclared(const std::string& expr); + + virtual std::string evalAsString(const std::string& expr); + virtual bool evalAsBool(const std::string& expr); + virtual double evalAsNumber(const std::string& expr); + +protected: + +}; + +#ifdef BUILD_AS_PLUGINS +PLUMA_INHERIT_PROVIDER(NULLDataModel, DataModelImpl); +#endif + +} + +#endif /* end of include guard: NULLDATAMODEL_H_KN8TWG0V */ diff --git a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp new file mode 100644 index 0000000..f874c86 --- /dev/null +++ b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp @@ -0,0 +1,470 @@ +#include "uscxml/Common.h" +#include "XPathDataModel.h" + +#include "uscxml/Message.h" +#include + +#ifdef BUILD_AS_PLUGINS +#include +#endif + +namespace uscxml { + +using namespace Arabica::XPath; +using namespace Arabica::DOM; + +#ifdef BUILD_AS_PLUGINS +PLUMA_CONNECTOR +bool connect(pluma::Host& host) { + host.add( new XPathDataModelProvider() ); + return true; +} +#endif + +XPathDataModel::XPathDataModel() { +} + +boost::shared_ptr XPathDataModel::create(InterpreterImpl* interpreter) { + boost::shared_ptr dm = boost::shared_ptr(new XPathDataModel()); + dm->_interpreter = interpreter; +// dm->_xpath.setVariableResolver(_varResolver); +// dm->_xpath->setVariableCompileTimeResolver(_varCTResolver); +// dm->_xpath->setNamespaceContext(interpreter->getNSContext()); + + dm->_funcResolver.setInterpreter(interpreter); + dm->_xpath.setFunctionResolver(dm->_funcResolver); + dm->_xpath.setVariableResolver(dm->_varResolver); + + dm->_domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); + dm->_doc = dm->_domFactory.createDocument("http://www.w3.org/2005/07/scxml", "", 0); + dm->_datamodel = dm->_doc.createElement("datamodel"); + dm->_doc.appendChild(dm->_datamodel); + + Element ioProcElem = dm->_doc.createElement("data"); + ioProcElem.setAttribute("id", "_ioprocessors"); + std::map::const_iterator ioProcIter = interpreter->getIOProcessors().begin(); + while(ioProcIter != interpreter->getIOProcessors().end()) { + Element ioProc = dm->_doc.createElement("processor"); + ioProc.setAttribute("name", ioProcIter->first); + + Data ioProcData = ioProcIter->second.getDataModelVariables(); + Element ioProcLoc = dm->_doc.createElement("location"); + Text ioProcLocText = dm->_doc.createTextNode(ioProcData.compound["location"].atom); + ioProcLoc.appendChild(ioProcLocText); + ioProc.appendChild(ioProcLoc); + ioProcElem.appendChild(ioProc); + + ioProcIter++; + } + dm->_datamodel.appendChild(ioProcElem); + + NodeSet ioProcNodeSet; + ioProcNodeSet.push_back(ioProcElem); + dm->_varResolver.setVariable("_ioprocessors", ioProcNodeSet); + + + Element sessIdElem = dm->_doc.createElement("data"); + sessIdElem.setAttribute("id", "_sessionid"); + Text sessIdText = dm->_doc.createTextNode(interpreter->getSessionId()); + sessIdElem.appendChild(sessIdText); + dm->_datamodel.appendChild(sessIdElem); + + NodeSet sessIdNodeSet; + sessIdNodeSet.push_back(sessIdElem); + dm->_varResolver.setVariable("_sessionid", sessIdNodeSet); + + + Element nameElem = dm->_doc.createElement("data"); + nameElem.setAttribute("id", "_name"); + Text nameText = dm->_doc.createTextNode(interpreter->getName()); + nameElem.appendChild(nameText); + dm->_datamodel.appendChild(nameElem); + + NodeSet nameNodeSet; + nameNodeSet.push_back(nameElem); + dm->_varResolver.setVariable("_name", nameNodeSet); + + return dm; +} + +XPathDataModel::~XPathDataModel() { +} + +void XPathDataModel::pushContext() { +} + +void XPathDataModel::popContext() { +} + +void XPathDataModel::initialize() { +} + +void XPathDataModel::setEvent(const Event& event) { + Element eventElem = _doc.createElement("data"); + eventElem.setAttribute("id", "_event"); + + Element eventDataElem = _doc.createElement("data"); + + NodeSet eventNodeSet; + if (event.params.size() > 0) { + std::multimap::const_iterator paramIter = event.params.begin(); + while(paramIter != event.params.end()) { + Element eventParamElem = _doc.createElement("data"); + Text eventParamText = _doc.createTextNode(paramIter->second); + + eventParamElem.setAttribute("id", paramIter->first); + eventParamElem.appendChild(eventParamText); + eventDataElem.appendChild(eventParamElem); + paramIter++; + } + } + if (event.namelist.size() > 0) { + std::map::const_iterator namelistIter = event.namelist.begin(); + while(namelistIter != event.namelist.end()) { + Element eventNamelistElem = _doc.createElement("data"); + Text eventNamelistText = _doc.createTextNode(namelistIter->second); + + eventNamelistElem.setAttribute("id", namelistIter->first); + eventNamelistElem.appendChild(eventNamelistText); + eventDataElem.appendChild(eventNamelistElem); + namelistIter++; + } + } + if (event.content.size() > 0) { + eventDataElem.setNodeValue(event.content); + } + + eventElem.appendChild(eventDataElem); + eventNodeSet.push_back(eventElem); + +// std::cout << eventElem << std::endl; + + // do we need to replace an existing event? + Node oldEventElem = _datamodel.getFirstChild(); + while(oldEventElem) { + if (oldEventElem.getNodeType() == Node_base::ELEMENT_NODE) { + if (HAS_ATTR(oldEventElem, "id") && boost::iequals(ATTR(oldEventElem, "id"), "_event")) + break; + } + oldEventElem = oldEventElem.getNextSibling(); + } + + if (oldEventElem) { + _datamodel.replaceChild(eventElem, oldEventElem); + } else { + _datamodel.appendChild(eventElem); + } + _varResolver.setVariable("_event", eventNodeSet); +} + +Data XPathDataModel::getStringAsData(const std::string& content) { + Data data; + return data; +} + +bool XPathDataModel::validate(const std::string& location, const std::string& schema) { + return true; +} + +uint32_t XPathDataModel::getLength(const std::string& expr) { + return 0; +} + +void XPathDataModel::eval(const std::string& expr) { + XPathValue result = _xpath.evaluate_expr(expr, _doc); +} + +bool XPathDataModel::isDeclared(const std::string& expr) { + return true; +} + +bool XPathDataModel::evalAsBool(const std::string& expr) { + XPathValue result = _xpath.evaluate_expr(expr, _doc); + return result.asBool(); +} + +std::string XPathDataModel::evalAsString(const std::string& expr) { + XPathValue result = _xpath.evaluate_expr(expr, _doc); + return result.asString(); +} + +double XPathDataModel::evalAsNumber(const std::string& expr) { + XPathValue result = _xpath.evaluate_expr(expr, _doc); + return result.asNumber(); +} + +void XPathDataModel::assign(const std::string& location, + const Document& doc, + const Arabica::DOM::Element& dataElem) { + XPathValue key = _xpath.evaluate_expr(location, _doc); + NodeSet nodeSet; + nodeSet.push_back(doc.getDocumentElement()); + assign(key, nodeSet, dataElem); +} + +void XPathDataModel::assign(const std::string& location, + const Data& data, + const Arabica::DOM::Element& dataElem) { +// assert(false); +// std::cout << location << " = " << data << std::endl; +} + +void XPathDataModel::assign(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& dataElem) { + std::string realExpr = (HAS_ATTR(dataElem, "expr") ? ATTR(dataElem, "expr") : expr); + XPathValue key = _xpath.evaluate_expr(location, _doc); + XPathValue value = _xpath.evaluate_expr(realExpr, _doc); + assign(key, value, dataElem); +} + +void XPathDataModel::init(const std::string& location, + const Document& doc, + const Arabica::DOM::Element& dataElem) { + Element container = _doc.createElement("data"); + container.setAttribute("id", location); + Element data = doc.getDocumentElement(); + if (data.hasChildNodes()) { + Node dataClone = _doc.importNode(data, true); + container.appendChild(dataClone); + } + _datamodel.appendChild(container); + + // put data element into nodeset and bind to xpath variable + NodeSet nodeSet; + nodeSet.push_back(container); + _varResolver.setVariable(location, nodeSet); +} + +void XPathDataModel::init(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& dataElem) { + Element data = _doc.createElement("data"); + data.setAttribute("id", location); + + if (expr.length() > 0) { + Text textNode = _doc.createTextNode(expr.c_str()); + data.appendChild(textNode); + _datamodel.appendChild(data); + } + + // put data element into nodeset and bind to xpath variable + NodeSet nodeSet; + nodeSet.push_back(data); + _varResolver.setVariable(location, nodeSet); +} + +void XPathDataModel::init(const std::string& location, + const Data& data, + const Arabica::DOM::Element& dataElem) { + assert(false); +} + +void XPathDataModel::assign(XPathValue& key, + const XPathValue& value, + const Arabica::DOM::Element& assignElem) { + switch (value.type()) { + case STRING: + assign(key, value.asString(), assignElem); + break; + case BOOL: + assign(key, value.asBool(), assignElem); + break; + case NUMBER: + assign(key, value.asNumber(), assignElem); + break; + case NODE_SET: + assign(key, value.asNodeSet(), assignElem); + break; + case ANY: + throw Event("error.execution", Event::PLATFORM); + } +} + +void XPathDataModel::assign(XPathValue& key, + const std::string& value, + const Arabica::DOM::Element& assignElem) { + switch (key.type()) { + case NODE_SET: { + if (key.asNodeSet().size() == 0) + return; + Node node = key.asNodeSet()[0]; + switch (node.getNodeType()) { + case Node_base::ATTRIBUTE_NODE: { + Attr attr(node); + attr.setValue(value); + break; + } + case Node_base::TEXT_NODE: { + Text text(node); + text.setNodeValue(value); + break; + } + case Node_base::ELEMENT_NODE: { + Element element(node); + if (HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "addattribute")) { + // addattribute: Add an attribute with the name specified by 'attr' + // and value specified by 'expr' to the node specified by 'location'. + if (!HAS_ATTR(assignElem, "attr")) + throw Event("error.execution", Event::PLATFORM); + element.setAttribute(ATTR(assignElem, "attr"), value); + } + break; + } + default: + throw Event("error.execution", Event::PLATFORM); + break; + } + break; + } + case STRING: + case BOOL: + case NUMBER: + case ANY: + throw Event("error.execution", Event::PLATFORM); + break; + default: + break; + } +} + +void XPathDataModel::assign(XPathValue& key, + const double value, + const Arabica::DOM::Element& assignElem) { +} + +void XPathDataModel::assign(XPathValue& key, + const bool value, + const Arabica::DOM::Element& assignElem) { +} + +void XPathDataModel::assign(XPathValue& key, + const NodeSet& value, + const Arabica::DOM::Element& assignElem) { + switch (key.type()) { + case NODE_SET: { + if (key.asNodeSet().size() == 0) + return; + Node node = key.asNodeSet()[0]; + switch (node.getNodeType()) { + case Node_base::ELEMENT_NODE: { + Element element(node); + if (false) { + } else if (HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "firstchild")) { + // firstchild: Insert the value specified by 'expr' before all of the children at 'location'. + for (int i = value.size(); i; i--) { + Node importedNode = _doc.importNode(value[i-1], true); + element.insertBefore(importedNode, element.getFirstChild()); + } + } else if (HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "lastchild")) { + // lastchild: Insert the value specified by 'expr' after all of the children at 'location'. + for (int i = 0; i < value.size(); i++) { + Node importedNode = _doc.importNode(value[i], true); + element.appendChild(importedNode); + } + } else if (HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "previoussibling")) { + // previoussibling: Insert the value specified by 'expr' before the + // node specified by 'location', keeping the same parent. + Node parent = element.getParentNode(); + if (!parent) + throw Event("error.execution", Event::PLATFORM); + for (int i = 0; i < value.size(); i++) { + Node importedNode = _doc.importNode(value[i], true); + parent.insertBefore(importedNode, element); + } + } else if (HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "nextsibling")) { + // nextsibling: Insert the value specified by 'expr' after the node + // specified by 'location', keeping the same parent. + Node parent = element.getParentNode(); + if (!parent) + throw Event("error.execution", Event::PLATFORM); + for (int i = value.size(); i; i--) { + Node importedNode = _doc.importNode(value[i-1], true); + Node nextSibling = element.getNextSibling(); + if (nextSibling) { + parent.insertBefore(importedNode, element.getNextSibling()); + } else { + parent.appendChild(importedNode); + } + } + } else if (HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "replace")) { + // replace: Replace the node specified by 'location' by the value specified by 'expr'. + Node parent = element.getParentNode(); + if (!parent) + throw Event("error.execution", Event::PLATFORM); + if (value.size() != 1) + throw Event("error.execution", Event::PLATFORM); + Node importedNode = _doc.importNode(value[0], true); + parent.replaceChild(importedNode, element); + } else if (HAS_ATTR(assignElem, "type") && boost::iequals(ATTR(assignElem, "type"), "delete")) { + // delete: Delete the node specified by 'location'. ('expr' is ignored.). + Node parent = element.getParentNode(); + if (!parent) + throw Event("error.execution", Event::PLATFORM); + parent.removeChild(element); + } else { + // replacechildren: Replace all the children at 'location' with the value specified by 'expr'. + while(element.hasChildNodes()) + element.removeChild(element.getChildNodes().item(0)); + for (int i = 0; i < value.size(); i++) { + Node importedNode = _doc.importNode(value[i], true); + element.appendChild(importedNode); + } + } + break; + } + default: + throw Event("error.execution", Event::PLATFORM); + break; + } + break; + } + case STRING: + case BOOL: + case NUMBER: + case ANY: + throw Event("error.execution", Event::PLATFORM); + break; + } +} + +XPathValue + NodeSetVariableResolver::resolveVariable(const std::string& namepaceUri, + const std::string& name) const { + std::map >::const_iterator n = _variables.find(name); + if(n == _variables.end()) { + throw Event("error.execution"); + } + return XPathValue(new NodeSetValue(n->second)); +} + +XPathFunction* + XPathFunctionResolver::resolveFunction(const std::string& namespace_uri, + const std::string& name, + const std::vector >& argExprs) const { + if (boost::iequals(name, "in")) { + return new XPathFunctionIn(1, -1, argExprs, _interpreter); + } + return _xpathFuncRes.resolveFunction(namespace_uri, name, argExprs); +} + +std::vector > XPathFunctionResolver::validNames() const { + std::vector > names = _xpathFuncRes.validNames(); + names.push_back(std::make_pair("", "In")); + return names; +} + +bool XPathFunctionIn::doEvaluate(const Node& context, + const ExecutionContext& executionContext) const { + for (int i = 0; i < argCount(); i++) { + XPathValue stateName = arg(i, context, executionContext); + if (stateName.type() == STRING) { + if (!Interpreter::isMember(_interpreter->getState(stateName.asString()), _interpreter->getConfiguration())) { + return false; + } + } + } + return true; +} + +} \ No newline at end of file diff --git a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.h b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.h new file mode 100644 index 0000000..d028129 --- /dev/null +++ b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.h @@ -0,0 +1,143 @@ +#ifndef XPATHDATAMODEL_H_KN8TWG0V +#define XPATHDATAMODEL_H_KN8TWG0V + +#include "uscxml/Interpreter.h" +#include + +#ifdef BUILD_AS_PLUGINS +#include "uscxml/plugins/Plugins.h" +#endif + +namespace uscxml { +class Event; +class Data; +} + +namespace uscxml { + +class XPathFunctionIn : public Arabica::XPath::BooleanXPathFunction { +public: + XPathFunctionIn(int minArgs, + int maxArgs, + const std::vector >& args, + InterpreterImpl* interpreter) : + BooleanXPathFunction(minArgs, maxArgs, args), + _interpreter(interpreter) {} + +protected: + bool doEvaluate(const Arabica::DOM::Node& context, + const Arabica::XPath::ExecutionContext& executionContext) const; + InterpreterImpl* _interpreter; +}; + +class XPathFunctionResolver : public Arabica::XPath::FunctionResolver { +public: + virtual ~XPathFunctionResolver() { } + + virtual Arabica::XPath::XPathFunction* + resolveFunction(const std::string& namespace_uri, + const std::string& name, + const std::vector >& argExprs) const; + + virtual std::vector > validNames() const; + void setInterpreter(InterpreterImpl* interpreter) { _interpreter = interpreter; } +protected: + Arabica::XPath::StandardXPathFunctionResolver _xpathFuncRes; + InterpreterImpl* _interpreter; +}; + +class NodeSetVariableResolver : public Arabica::XPath::VariableResolver { +public: + Arabica::XPath::XPathValue resolveVariable(const std::string& namepaceUri, + const std::string& name) const; + void setVariable(const std::string& name, const Arabica::XPath::NodeSet& value) { + _variables[name] = value; + } + +private: + std::map > _variables; +}; + +class XPathDataModel : public DataModelImpl { +public: + XPathDataModel(); + virtual ~XPathDataModel(); + virtual boost::shared_ptr create(InterpreterImpl* interpreter); + + virtual std::set getNames() { + std::set names; + names.insert("xpath"); + return names; + } + + virtual void initialize(); + virtual void setEvent(const Event& event); + + virtual bool validate(const std::string& location, const std::string& schema); + + virtual uint32_t getLength(const std::string& expr); + virtual void pushContext(); + virtual void popContext(); + + virtual void eval(const std::string& expr); + virtual void assign(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& assignElem); + virtual void assign(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& assignElem); + virtual void assign(const std::string& location, + const Data& data, + const Arabica::DOM::Element& assignElem); + + virtual void init(const std::string& location, + const Arabica::DOM::Document& doc, + const Arabica::DOM::Element& dataElem); + virtual void init(const std::string& location, + const std::string& expr, + const Arabica::DOM::Element& dataElem); + virtual void init(const std::string& location, + const Data& data, + const Arabica::DOM::Element& dataElem); + + virtual Data getStringAsData(const std::string& content); + virtual bool isDeclared(const std::string& expr); + + virtual std::string evalAsString(const std::string& expr); + virtual bool evalAsBool(const std::string& expr); + virtual double evalAsNumber(const std::string& expr); + +protected: + Arabica::XPath::XPath _xpath; + Arabica::DOM::DOMImplementation _domFactory; + Arabica::DOM::Element _datamodel; + Arabica::DOM::Document _doc; + + void assign(Arabica::XPath::XPathValue& key, + const Arabica::XPath::XPathValue& value, + const Arabica::DOM::Element& assignElem); + void assign(Arabica::XPath::XPathValue& key, + const std::string& value, + const Arabica::DOM::Element& assignElem); + void assign(Arabica::XPath::XPathValue& key, + const double value, + const Arabica::DOM::Element& assignElem); + void assign(Arabica::XPath::XPathValue& key, + const bool value, + const Arabica::DOM::Element& assignElem); + void assign(Arabica::XPath::XPathValue& key, + const Arabica::XPath::NodeSet& value, + const Arabica::DOM::Element& assignElem); + + NodeSetVariableResolver _varResolver; + XPathFunctionResolver _funcResolver; + +}; + +#ifdef BUILD_AS_PLUGINS +PLUMA_INHERIT_PROVIDER(XPathDataModel, DataModelImpl); +#endif + +} + +#endif /* end of include guard: XPATHDATAMODEL_H_KN8TWG0V */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d7c305e..a9ec9f1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,6 +45,12 @@ if (NOT WIN32) target_link_libraries(test-arabica-events uscxml) add_test(test-arabica-events ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test-arabica-events ${CMAKE_SOURCE_DIR}/test/samples/uscxml/arabica/test-arabica-events.xml) set_target_properties(test-arabica-events PROPERTIES FOLDER "Tests") + + add_executable(test-arabica-xpath src/test-arabica-xpath.cpp) + target_link_libraries(test-arabica-xpath uscxml) + add_test(test-arabica-xpath ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test-arabica-xpath) + set_target_properties(test-arabica-xpath PROPERTIES FOLDER "Tests") + endif() add_executable(test-url src/test-url.cpp) diff --git a/test/samples/w3c/ecma/test463.scxml b/test/samples/w3c/ecma/test463.scxml index 4f32b84..8ff0555 100644 --- a/test/samples/w3c/ecma/test463.scxml +++ b/test/samples/w3c/ecma/test463.scxml @@ -1,7 +1,7 @@ - + diff --git a/test/src/test-arabica-xpath.cpp b/test/src/test-arabica-xpath.cpp new file mode 100644 index 0000000..2bcf605 --- /dev/null +++ b/test/src/test-arabica-xpath.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#include +#include + +#define string_type std::string +#define string_adaptor Arabica::default_string_adaptor + +typedef string_adaptor SA; + +class NodeSetVariableResolver : public Arabica::XPath::VariableResolver +{ + //typedef string_adaptorstring_adaptor; +public: + virtual Arabica::XPath::XPathValue resolveVariable(const string_type& /* namepace_uri */, + const string_type& name) const + { + using namespace Arabica::XPath; + typename VarMap::const_iterator n = map_.find(name); + if(n == map_.end()) + throw UnboundVariableException(string_adaptor::asStdString(name)); + return XPathValue(new NodeSetValue((*n).second)); + } // resolveVariable + + void setVariable(const string_type& name, const Arabica::XPath::NodeSet& value) + { + map_[name] = value; + } // setVariable + +private: + typedef std::map > VarMap; + VarMap map_; +}; // class NodeSetVariableResolver + +Arabica::XPath::XPath parser; +Arabica::DOM::DOMImplementation factory_; +Arabica::DOM::Document document_; + +Arabica::DOM::Element root_; +Arabica::DOM::Element element1_; +Arabica::DOM::Element element2_; +Arabica::DOM::Element element3_; +Arabica::DOM::Element spinkle_; + +Arabica::DOM::Attr attr_; +Arabica::DOM::Text text_; +Arabica::DOM::Comment comment_; +Arabica::DOM::ProcessingInstruction processingInstruction_; +Arabica::DOM::Document chapters_; +Arabica::DOM::Document numbers_; + +class StringVariableResolver : public Arabica::XPath::VariableResolver +{ +public: + virtual Arabica::XPath::XPathValue resolveVariable(const string_type& /* namespace_uri */, + const string_type& name) const + { + using namespace Arabica::XPath; + typename VarMap::const_iterator n = map_.find(name); + if(n == map_.end()) + throw UnboundVariableException(string_adaptor::asStdString(name)); + return XPathValue(new StringValue((*n).second)); + } // resolveVariable + + void setVariable(const string_type& name, const string_type& value) + { + map_[name] = value; + } // setVariable + +private: + typedef std::map VarMap; + VarMap map_; +}; // StringVariableResolver + + +int main(int argc, char** argv) { + + factory_ = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); + document_ = factory_.createDocument(SA::construct_from_utf8(""), SA::construct_from_utf8(""), 0); + root_ = document_.createElement("root"); + document_.appendChild(root_); + assert(root_); + + element1_ = document_.createElement(SA::construct_from_utf8("child1")); + element2_ = document_.createElement(SA::construct_from_utf8("child2")); + element3_ = document_.createElement(SA::construct_from_utf8("child3")); + + element1_.setAttribute(SA::construct_from_utf8("one"), SA::construct_from_utf8("1")); + + element2_.setAttribute(SA::construct_from_utf8("one"), SA::construct_from_utf8("1")); + element2_.setAttribute(SA::construct_from_utf8("two"), SA::construct_from_utf8("1")); + element2_.setAttribute(SA::construct_from_utf8("three"), SA::construct_from_utf8("1")); + element2_.setAttribute(SA::construct_from_utf8("four"), SA::construct_from_utf8("1")); + + text_ = document_.createTextNode(SA::construct_from_utf8("data")); + comment_ = document_.createComment(SA::construct_from_utf8("comment")); + processingInstruction_ = document_.createProcessingInstruction(SA::construct_from_utf8("target"), SA::construct_from_utf8("data")); + element2_.appendChild(text_); + spinkle_ = document_.createElement(SA::construct_from_utf8("spinkle")); + element2_.appendChild(spinkle_); + element2_.appendChild(comment_); + element2_.appendChild(processingInstruction_); + + attr_ = element1_.getAttributeNode(SA::construct_from_utf8("one")); + + root_.appendChild(element1_); + root_.appendChild(element2_); + root_.appendChild(element3_); + + chapters_ = factory_.createDocument(SA::construct_from_utf8(""), SA::construct_from_utf8(""), 0); + chapters_.appendChild(chapters_.createElement(SA::construct_from_utf8("document"))); + chapters_.getFirstChild().appendChild(chapters_.createElement(SA::construct_from_utf8("chapter"))).appendChild(chapters_.createTextNode(SA::construct_from_utf8("one"))); + chapters_.getFirstChild().appendChild(chapters_.createElement(SA::construct_from_utf8("chapter"))).appendChild(chapters_.createTextNode(SA::construct_from_utf8("two"))); + chapters_.getFirstChild().appendChild(chapters_.createElement(SA::construct_from_utf8("chapter"))).appendChild(chapters_.createTextNode(SA::construct_from_utf8("three"))); + chapters_.getFirstChild().appendChild(chapters_.createElement(SA::construct_from_utf8("chapter"))).appendChild(chapters_.createTextNode(SA::construct_from_utf8("four"))); + chapters_.getFirstChild().appendChild(chapters_.createElement(SA::construct_from_utf8("chapter"))).appendChild(chapters_.createTextNode(SA::construct_from_utf8("five"))); + + numbers_ = factory_.createDocument(SA::construct_from_utf8(""), SA::construct_from_utf8(""), 0); + numbers_.appendChild(numbers_.createElement(SA::construct_from_utf8("doc"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("1"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("2"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("3"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("4"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("5"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("6"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("7"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("8"))); + numbers_.getFirstChild().appendChild(numbers_.createElement(SA::construct_from_utf8("number"))).appendChild(numbers_.createTextNode(SA::construct_from_utf8("9"))); + std::cout << document_ << std::endl; + std::cout << numbers_ << std::endl; + std::cout << chapters_ << std::endl; + + + if (false) { + using namespace Arabica::XPath; + StringVariableResolver svr; + svr.setVariable(SA::construct_from_utf8("index"), SA::construct_from_utf8("1")); + + parser.setVariableResolver(svr); + XPathValue result = parser.evaluate(SA::construct_from_utf8("/root/*[@two = $index]"), document_); + assert(NODE_SET == result.type()); + assert(element2_ == result.asNodeSet()[0]); + + parser.resetVariableResolver(); + } // test18 + + if (false) { + using namespace Arabica::XPath; + XPathExpression xpath = parser.compile(SA::construct_from_utf8("root/*[position() = 2]")); + XPathValue result = xpath.evaluate(document_); + + assert(NODE_SET == result.type()); + assert(1 == result.asNodeSet().size()); + Arabica::DOM::Node n = result.asNodeSet()[0]; + assert(element2_ == n); + } // test19 + + if (false) { + using namespace Arabica::XPath; + Arabica::DOM::DocumentFragment frag = document_.createDocumentFragment(); + frag.appendChild(document_.createElement(SA::construct_from_utf8("foo"))); + + NodeSetVariableResolver svr; + NodeSet ns; + ns.push_back(frag); + svr.setVariable(SA::construct_from_utf8("fruit"), ns); + parser.setVariableResolver(svr); + + XPathValue result = parser.evaluate_expr(SA::construct_from_utf8("$fruit/foo|/root/child3"), document_); + assert(NODE_SET == result.type()); + assert(2 == result.asNodeSet().size()); + assert(element3_ == result.asNodeSet()[0]); + } // testUnion11 + + if (false) { + using namespace Arabica::XPath; + XPathValue result = parser.evaluate_expr(SA::construct_from_utf8("local-name(/root)"), document_); + assert(STRING == result.type()); + assert(SA::construct_from_utf8("root") == result.asString()); + } // testLocalNameFn1 + + { + using namespace Arabica::XPath; + Arabica::DOM::DocumentFragment frag = document_.createDocumentFragment(); + frag.appendChild(document_.createElement("foo")); + + NodeSetVariableResolver svr; + NodeSet ns; + ns.push_back(frag); + svr.setVariable("fruit", ns); + parser.setVariableResolver(svr); + + XPathValue result = parser.evaluate(SA::construct_from_utf8("local-name($fruit/foo) = 'foo'"), document_); + std::cout << result.asBool() << std::endl; + } + +} \ No newline at end of file -- cgit v0.12