summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2014-08-21 15:14:55 (GMT)
committerStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2014-08-21 15:14:55 (GMT)
commitf7ad82f972bf46571bb5229205f877f8ab31069d (patch)
treee83110054a551053a68fb93d7061709808a19a0d
parenta3fb1daf5b4e58471cc714853636025b6cac9aed (diff)
downloaduscxml-f7ad82f972bf46571bb5229205f877f8ab31069d.zip
uscxml-f7ad82f972bf46571bb5229205f877f8ab31069d.tar.gz
uscxml-f7ad82f972bf46571bb5229205f877f8ab31069d.tar.bz2
New Interpreter::validate() to identify issues with a document before running it
-rw-r--r--embedding/java/src/org/uscxml/tests/TestValidation.java35
-rw-r--r--src/bindings/swig/csharp/uscxml.i2
-rw-r--r--src/bindings/swig/java/uscxml.i2
-rw-r--r--src/uscxml/Factory.cpp39
-rw-r--r--src/uscxml/Factory.h5
-rw-r--r--src/uscxml/Interpreter.cpp396
-rw-r--r--src/uscxml/Interpreter.h24
-rw-r--r--src/uscxml/plugins/DataModel.h6
-rw-r--r--src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp11
-rw-r--r--src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.h2
-rw-r--r--src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp7
-rw-r--r--src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h1
-rw-r--r--src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp4
-rw-r--r--src/uscxml/plugins/datamodel/lua/LuaDataModel.h1
-rw-r--r--test/src/test-lifecycle.cpp24
15 files changed, 542 insertions, 17 deletions
diff --git a/embedding/java/src/org/uscxml/tests/TestValidation.java b/embedding/java/src/org/uscxml/tests/TestValidation.java
new file mode 100644
index 0000000..f09f678
--- /dev/null
+++ b/embedding/java/src/org/uscxml/tests/TestValidation.java
@@ -0,0 +1,35 @@
+package org.uscxml.tests;
+
+import org.uscxml.Interpreter;
+import org.uscxml.InterpreterException;
+import org.uscxml.IssueList;
+
+public class TestValidation {
+
+ public static void main(String[] args) {
+ System.load("/Users/sradomski/Documents/TK/Code/uscxml/build/cli/lib/libuscxmlNativeJava64.jnilib");
+
+ // invalid expression in transition
+ try {
+ String xml =
+ "<scxml datamodel=\"ecmascript\">" +
+ " <state id=\"start\">" +
+ " <transition target=\"done\" cond=\"%sf\" />" +
+ " </state>" +
+ " <final id=\"done\" />" +
+ "</scxml>";
+ Interpreter interpreter = Interpreter.fromXML(xml);
+ IssueList issues = interpreter.validate();
+ for (int i = 0; i < issues.size(); i++) {
+ System.out.println(issues.get(i));
+ }
+
+ throw new RuntimeException("");
+
+ } catch (InterpreterException e) {
+ System.err.println(e);
+ }
+
+ }
+
+}
diff --git a/src/bindings/swig/csharp/uscxml.i b/src/bindings/swig/csharp/uscxml.i
index eb7926e..5e98544 100644
--- a/src/bindings/swig/csharp/uscxml.i
+++ b/src/bindings/swig/csharp/uscxml.i
@@ -153,6 +153,7 @@ WRAP_TO_STRING(uscxml::Event);
WRAP_TO_STRING(uscxml::Data);
WRAP_TO_STRING(uscxml::SendRequest);
WRAP_TO_STRING(uscxml::InvokeRequest);
+WRAP_TO_STRING(uscxml::InterpreterIssue);
%include "../uscxml_ignores.i"
@@ -403,6 +404,7 @@ using System.Runtime.InteropServices;
%include "../wrapped/WrappedInterpreterMonitor.h"
+%template(IssueList) std::list<uscxml::InterpreterIssue>;
%template(DataList) std::list<uscxml::Data>;
%template(DataMap) std::map<std::string, uscxml::Data>;
%template(StringSet) std::set<std::string>;
diff --git a/src/bindings/swig/java/uscxml.i b/src/bindings/swig/java/uscxml.i
index 7ec8218..7f25fc3 100644
--- a/src/bindings/swig/java/uscxml.i
+++ b/src/bindings/swig/java/uscxml.i
@@ -121,6 +121,7 @@ WRAP_TO_STRING(uscxml::Event);
WRAP_TO_STRING(uscxml::Data);
WRAP_TO_STRING(uscxml::SendRequest);
WRAP_TO_STRING(uscxml::InvokeRequest);
+WRAP_TO_STRING(uscxml::InterpreterIssue);
WRAP_HASHCODE(uscxml::Interpreter);
@@ -431,6 +432,7 @@ import java.util.LinkedList;
%include "../wrapped/WrappedInterpreterMonitor.h"
+%template(IssueList) std::list<uscxml::InterpreterIssue>;
%template(DataList) std::list<uscxml::Data>;
%template(DataMap) std::map<std::string, uscxml::Data>;
%template(StringSet) std::set<std::string>;
diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp
index 9ebc0d8..7117de2 100644
--- a/src/uscxml/Factory.cpp
+++ b/src/uscxml/Factory.cpp
@@ -528,6 +528,15 @@ std::map<std::string, IOProcessorImpl*> Factory::getIOProcessors() {
return ioProcs;
}
+bool Factory::hasInvoker(const std::string& type) {
+ if (_invokerAliases.find(type) != _invokerAliases.end()) {
+ return true;
+ } else if(_parentFactory) {
+ return _parentFactory->hasInvoker(type);
+ }
+ return false;
+}
+
boost::shared_ptr<InvokerImpl> Factory::createInvoker(const std::string& type, InterpreterImpl* interpreter) {
// do we have this type ourself?
@@ -550,6 +559,16 @@ boost::shared_ptr<InvokerImpl> Factory::createInvoker(const std::string& type, I
return boost::shared_ptr<InvokerImpl>();
}
+
+bool Factory::hasDataModel(const std::string& type) {
+ if (_dataModelAliases.find(type) != _dataModelAliases.end()) {
+ return true;
+ } else if(_parentFactory) {
+ return _parentFactory->hasDataModel(type);
+ }
+ return false;
+}
+
boost::shared_ptr<DataModelImpl> Factory::createDataModel(const std::string& type, InterpreterImpl* interpreter) {
// do we have this type ourself?
@@ -572,6 +591,16 @@ boost::shared_ptr<DataModelImpl> Factory::createDataModel(const std::string& typ
return boost::shared_ptr<DataModelImpl>();
}
+
+bool Factory::hasIOProcessor(const std::string& type) {
+ if (_ioProcessorAliases.find(type) != _ioProcessorAliases.end()) {
+ return true;
+ } else if(_parentFactory) {
+ return _parentFactory->hasIOProcessor(type);
+ }
+ return false;
+}
+
boost::shared_ptr<IOProcessorImpl> Factory::createIOProcessor(const std::string& type, InterpreterImpl* interpreter) {
// do we have this type ourself?
if (_ioProcessorAliases.find(type) != _ioProcessorAliases.end()) {
@@ -593,6 +622,16 @@ boost::shared_ptr<IOProcessorImpl> Factory::createIOProcessor(const std::string&
return boost::shared_ptr<IOProcessorImpl>();
}
+bool Factory::hasExecutableContent(const std::string& localName, const std::string& nameSpace) {
+ std::string actualNameSpace = (nameSpace.length() == 0 ? "http://www.w3.org/2005/07/scxml" : nameSpace);
+ if (_executableContent.find(std::make_pair(localName, actualNameSpace)) != _executableContent.end()) {
+ return true;
+ } else if(_parentFactory) {
+ return _parentFactory->hasExecutableContent(localName, nameSpace);
+ }
+ return false;
+}
+
boost::shared_ptr<ExecutableContentImpl> Factory::createExecutableContent(const std::string& localName, const std::string& nameSpace, InterpreterImpl* interpreter) {
// do we have this type in this factory?
std::string actualNameSpace = (nameSpace.length() == 0 ? "http://www.w3.org/2005/07/scxml" : nameSpace);
diff --git a/src/uscxml/Factory.h b/src/uscxml/Factory.h
index a0f5178..d81747f 100644
--- a/src/uscxml/Factory.h
+++ b/src/uscxml/Factory.h
@@ -59,6 +59,11 @@ public:
boost::shared_ptr<InvokerImpl> createInvoker(const std::string& type, InterpreterImpl* interpreter);
boost::shared_ptr<ExecutableContentImpl> createExecutableContent(const std::string& localName, const std::string& nameSpace, InterpreterImpl* interpreter);
+ bool hasDataModel(const std::string& type);
+ bool hasIOProcessor(const std::string& type);
+ bool hasInvoker(const std::string& type);
+ bool hasExecutableContent(const std::string& localName, const std::string& nameSpace);
+
std::map<std::string, IOProcessorImpl*> getIOProcessors();
void listComponents();
diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp
index ae620b8..8fa3d6c 100644
--- a/src/uscxml/Interpreter.cpp
+++ b/src/uscxml/Interpreter.cpp
@@ -709,6 +709,378 @@ void InterpreterImpl::reset() {
setInterpreterState(USCXML_INSTANTIATED);
}
+InterpreterIssue::InterpreterIssue(const std::string& msg, Arabica::DOM::Node<std::string> node, IssueSeverity severity) : message(msg), node(node), severity(severity) {
+ xPath = DOMUtils::xPathForNode(node);
+}
+
+std::list<InterpreterIssue> InterpreterImpl::validate() {
+ // some things we need to prepare first
+ if (_factory == NULL)
+ _factory = Factory::getInstance();
+ setupDOM();
+
+ std::list<InterpreterIssue> issues;
+
+ if (!_scxml) {
+ InterpreterIssue issue("No SCXML element to be found", _document.getDocumentElement(), InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ return issues;
+ }
+
+ _cachedStates.clear();
+
+ NodeSet<std::string> allStates;
+ allStates.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "state", _scxml, true));
+ allStates.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "parallel", _scxml, true));
+ allStates.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "history", _scxml, true));
+ allStates.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "final", _scxml, true));
+
+ for (int i = 0; i < allStates.size(); i++) {
+ Element<std::string> state = Element<std::string>(allStates[i]);
+
+ // check for existance of id attribute
+ if (!HAS_ATTR(state, "id")) {
+ InterpreterIssue issue("State has no 'id' attribute", state, InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ continue;
+ }
+ std::string stateId = ATTR(state, "id");
+
+ // check for uniqueness of id attribute
+ if (_cachedStates.find(stateId) != _cachedStates.end()) {
+ InterpreterIssue issue("Duplicate state with id '" + stateId + "'", state, InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ continue;
+ }
+ _cachedStates[ATTR(state, "id")] = state;
+ }
+
+ NodeSet<std::string> transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true);
+ for (int i = 0; i < transitions.size(); i++) {
+ Element<std::string> transition = Element<std::string>(transitions[i]);
+
+ // check for valid target
+ std::list<std::string> targetIds = InterpreterImpl::tokenizeIdRefs(ATTR(transition, "target"));
+ for (std::list<std::string>::iterator targetIter = targetIds.begin(); targetIter != targetIds.end(); targetIter++) {
+ if (_cachedStates.find(*targetIter) == _cachedStates.end()) {
+ InterpreterIssue issue("Transition has non-existant target state with id '" + *targetIter + "'", transition, InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+
+ // check for redundancy of transition
+ // TODO
+ }
+
+ // check for valid initial attribute
+ {
+ allStates.push_back(_scxml);
+ for (int i = 0; i < allStates.size(); i++) {
+ Element<std::string> state = Element<std::string>(allStates[i]);
+ if (HAS_ATTR(state, "initial")) {
+ std::list<std::string> intials = InterpreterImpl::tokenizeIdRefs(ATTR(state, "initial"));
+ for (std::list<std::string>::iterator initIter = intials.begin(); initIter != intials.end(); initIter++) {
+ if (_cachedStates.find(*initIter) == _cachedStates.end()) {
+ InterpreterIssue issue("Initial attribute has invalid target state with id '" + *initIter + "'", state, InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ // check that all invokers exists
+ {
+ NodeSet<std::string> invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true);
+ for (int i = 0; i < invokes.size(); i++) {
+ Element<std::string> invoke = Element<std::string>(invokes[i]);
+ if (HAS_ATTR(invoke, "type") && !_factory->hasInvoker(ATTR(invoke, "type"))) {
+ InterpreterIssue issue("Invoke with unknown type '" + ATTR(invoke, "type") + "'", invoke, InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+
+ // check that all io processors exists
+ {
+ NodeSet<std::string> sends = filterChildElements(_nsInfo.xmlNSPrefix + "send", _scxml, true);
+ for (int i = 0; i < sends.size(); i++) {
+ Element<std::string> send = Element<std::string>(sends[i]);
+ if (HAS_ATTR(send, "type") && !_factory->hasIOProcessor(ATTR(send, "type"))) {
+ InterpreterIssue issue("Send to unknown IO Processor '" + ATTR(send, "type") + "'", send, InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+
+ // check that the datamodel is known
+ if (HAS_ATTR(_scxml, "datamodel")) {
+ if (!_factory->hasDataModel(ATTR(_scxml, "datamodel"))) {
+ InterpreterIssue issue("SCXML document requires unknown datamodel '" + ATTR(_scxml, "datamodel") + "'", _scxml, InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ }
+ }
+
+
+ // instantiate datamodel if not explicitly set
+ if (!_dataModel) {
+ if (HAS_ATTR(_scxml, "datamodel")) {
+ // might throw
+ _dataModel = _factory->createDataModel(ATTR(_scxml, "datamodel"), this);
+ } else {
+ _dataModel = _factory->createDataModel("null", this);
+ }
+ }
+
+
+ // check that all custom executable content is known
+ {
+ NodeSet<std::string> allExecContents;
+ allExecContents.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onentry", _scxml, true));
+ allExecContents.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onexit", _scxml, true));
+ allExecContents.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true));
+ allExecContents.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "finalize", _scxml, true));
+
+ for (int i = 0; i < allExecContents.size(); i++) {
+ Element<std::string> block = Element<std::string>(allExecContents[i]);
+ NodeSet<std::string> execContents = filterChildType(Node_base::ELEMENT_NODE, block);
+ for (int j = 0; j < execContents.size(); j++) {
+ Element<std::string> execContent = Element<std::string>(execContents[j]);
+ // SCXML specific executable content, always available
+ if (iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "onentry") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "onexit") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "transition") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "finalize") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "raise") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "if") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "elseif") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "else") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "foreach") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "log") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "assign") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "validate") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "script") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "send") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "cancel") ||
+ iequals(TAGNAME(execContent), _nsInfo.xmlNSPrefix + "invoke") ||
+ false)
+ {
+ continue;
+ }
+ if (!_factory->hasExecutableContent(execContent.getLocalName(), execContent.getNamespaceURI())) {
+ InterpreterIssue issue("Executable content element '" + execContent.getLocalName() + "' in namespace '" + execContent.getNamespaceURI() + "' unknown", _scxml, InterpreterIssue::USCXML_ISSUE_FATAL);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+ }
+
+ // test all scripts for valid syntax
+ {
+ NodeSet<std::string> scripts = filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml, true);
+ for (int i = 0; i < scripts.size(); i++) {
+ Element<std::string> script = Element<std::string>(scripts[i]);
+
+ if (script.hasChildNodes()) {
+ // search for the text node with the actual script
+ std::string scriptContent;
+ for (Node<std::string> child = script.getFirstChild(); child; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node_base::TEXT_NODE || child.getNodeType() == Node_base::CDATA_SECTION_NODE)
+ scriptContent += child.getNodeValue();
+ }
+
+ if (!_dataModel.isValidSyntax(scriptContent)) {
+ InterpreterIssue issue("Syntax error in script", script, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ }
+ }
+ }
+ }
+
+ // test the various attributes with datamodel expressions for valid syntax
+ {
+ NodeSet<std::string> withCondAttrs;
+ withCondAttrs.push_back(transitions);
+ withCondAttrs.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "if", _scxml, true));
+ withCondAttrs.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "elseif", _scxml, true));
+
+ for (int i = 0; i < withCondAttrs.size(); i++) {
+ Element<std::string> condAttr = Element<std::string>(withCondAttrs[i]);
+ if (HAS_ATTR(condAttr, "cond")) {
+ if (!_dataModel.isValidSyntax(ATTR(condAttr, "cond"))) {
+ InterpreterIssue issue("Syntax error in cond attribute", condAttr, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+ }
+
+ {
+ NodeSet<std::string> withExprAttrs;
+ withExprAttrs.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "log", _scxml, true));
+ withExprAttrs.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "data", _scxml, true));
+ withExprAttrs.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "assign", _scxml, true));
+ withExprAttrs.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "content", _scxml, true));
+ withExprAttrs.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "param", _scxml, true));
+
+ for (int i = 0; i < withExprAttrs.size(); i++) {
+ Element<std::string> withExprAttr = Element<std::string>(withExprAttrs[i]);
+ if (HAS_ATTR(withExprAttr, "expr")) {
+ if (!_dataModel.isValidSyntax(ATTR(withExprAttr, "expr"))) {
+ InterpreterIssue issue("Syntax error in expr attribute", withExprAttr, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+ }
+
+ {
+ NodeSet<std::string> foreachs = filterChildElements(_nsInfo.xmlNSPrefix + "foreach", _scxml, true);
+ for (int i = 0; i < foreachs.size(); i++) {
+ Element<std::string> foreach = Element<std::string>(foreachs[i]);
+ if (HAS_ATTR(foreach, "array")) {
+ if (!_dataModel.isValidSyntax(ATTR(foreach, "array"))) {
+ InterpreterIssue issue("Syntax error in array attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ if (HAS_ATTR(foreach, "item")) {
+ if (!_dataModel.isValidSyntax(ATTR(foreach, "item"))) {
+ InterpreterIssue issue("Syntax error in item attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ if (HAS_ATTR(foreach, "index")) {
+ if (!_dataModel.isValidSyntax(ATTR(foreach, "index"))) {
+ InterpreterIssue issue("Syntax error in index attribute", foreach, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+ }
+
+ {
+ NodeSet<std::string> sends = filterChildElements(_nsInfo.xmlNSPrefix + "send", _scxml, true);
+ for (int i = 0; i < sends.size(); i++) {
+ Element<std::string> send = Element<std::string>(sends[i]);
+ if (HAS_ATTR(send, "eventexpr")) {
+ if (!_dataModel.isValidSyntax(ATTR(send, "eventexpr"))) {
+ InterpreterIssue issue("Syntax error in eventexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ if (HAS_ATTR(send, "targetexpr")) {
+ if (!_dataModel.isValidSyntax(ATTR(send, "targetexpr"))) {
+ InterpreterIssue issue("Syntax error in targetexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ if (HAS_ATTR(send, "typeexpr")) {
+ if (!_dataModel.isValidSyntax(ATTR(send, "typeexpr"))) {
+ InterpreterIssue issue("Syntax error in typeexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ if (HAS_ATTR(send, "idlocation")) {
+ if (!_dataModel.isValidSyntax(ATTR(send, "idlocation"))) {
+ InterpreterIssue issue("Syntax error in idlocation attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ if (HAS_ATTR(send, "delayexpr")) {
+ if (!_dataModel.isValidSyntax(ATTR(send, "delayexpr"))) {
+ InterpreterIssue issue("Syntax error in delayexpr attribute", send, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+
+ }
+
+ {
+ NodeSet<std::string> invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true);
+ for (int i = 0; i < invokes.size(); i++) {
+ Element<std::string> invoke = Element<std::string>(invokes[i]);
+ if (HAS_ATTR(invoke, "typeexpr")) {
+ if (!_dataModel.isValidSyntax(ATTR(invoke, "typeexpr"))) {
+ InterpreterIssue issue("Syntax error in typeexpr attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ if (HAS_ATTR(invoke, "srcexpr")) {
+ if (!_dataModel.isValidSyntax(ATTR(invoke, "srcexpr"))) {
+ InterpreterIssue issue("Syntax error in srcexpr attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ if (HAS_ATTR(invoke, "idlocation")) {
+ if (!_dataModel.isValidSyntax(ATTR(invoke, "idlocation"))) {
+ InterpreterIssue issue("Syntax error in idlocation attribute", invoke, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+ }
+
+ {
+ NodeSet<std::string> cancels = filterChildElements(_nsInfo.xmlNSPrefix + "cancel", _scxml, true);
+ for (int i = 0; i < cancels.size(); i++) {
+ Element<std::string> cancel = Element<std::string>(cancels[i]);
+ if (HAS_ATTR(cancel, "sendidexpr")) {
+ if (!_dataModel.isValidSyntax(ATTR(cancel, "sendidexpr"))) {
+ InterpreterIssue issue("Syntax error in sendidexpr attribute", cancel, InterpreterIssue::USCXML_ISSUE_WARNING);
+ issues.push_back(issue);
+ continue;
+ }
+ }
+ }
+ }
+
+ return issues;
+}
+
+std::ostream& operator<< (std::ostream& os, const InterpreterIssue& issue) {
+ switch (issue.severity) {
+ case InterpreterIssue::USCXML_ISSUE_FATAL:
+ os << "Issue (FATAL) ";
+ break;
+ case InterpreterIssue::USCXML_ISSUE_WARNING:
+ os << "Issue (WARNING) ";
+ break;
+ case InterpreterIssue::USCXML_ISSUE_INFO:
+ os << "Issue (INFO) ";
+ break;
+ default:
+ break;
+ }
+
+ if (issue.xPath.size() > 0) {
+ os << " at " << issue.xPath << ": ";
+ } else {
+ os << ": ";
+ }
+ os << issue.message;
+ return os;
+}
+
void InterpreterImpl::setupDOM() {
if (_domIsSetup)
return;
@@ -718,19 +1090,21 @@ void InterpreterImpl::setupDOM() {
}
// find scxml element
- NodeList<std::string> scxmls;
- if (_nsInfo.nsURL.size() == 0) {
- scxmls = _document.getElementsByTagName("scxml");
- } else {
- scxmls = _document.getElementsByTagNameNS(_nsInfo.nsURL, "scxml");
- }
-
- if (scxmls.getLength() == 0) {
- ERROR_PLATFORM_THROW("Cannot find SCXML element in DOM");
- }
+ if (!_scxml) {
+ NodeList<std::string> scxmls;
+ if (_nsInfo.nsURL.size() == 0) {
+ scxmls = _document.getElementsByTagName("scxml");
+ } else {
+ scxmls = _document.getElementsByTagNameNS(_nsInfo.nsURL, "scxml");
+ }
- _scxml = (Arabica::DOM::Element<std::string>)scxmls.item(0);
+ if (scxmls.getLength() == 0) {
+ ERROR_PLATFORM_THROW("Cannot find SCXML element in DOM");
+ }
+ _scxml = (Arabica::DOM::Element<std::string>)scxmls.item(0);
+ }
+
if (_nsInfo.getNSContext() != NULL)
_xpath.setNamespaceContext(*_nsInfo.getNSContext());
diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h
index 70fb888..f82d095 100644
--- a/src/uscxml/Interpreter.h
+++ b/src/uscxml/Interpreter.h
@@ -211,9 +211,25 @@ enum InterpreterState {
USCXML_MICROSTEPPED = 2, ///< processed one transition set
USCXML_MACROSTEPPED = 4, ///< processed all transition sets and reached a stable configuration
};
+USCXML_API std::ostream& operator<< (std::ostream& os, const InterpreterState& interpreterState);
+class USCXML_API InterpreterIssue {
+public:
+ enum IssueSeverity {
+ USCXML_ISSUE_FATAL,
+ USCXML_ISSUE_WARNING,
+ USCXML_ISSUE_INFO
+ };
+
+ InterpreterIssue(const std::string& msg, Arabica::DOM::Node<std::string> node, IssueSeverity severity);
+
+ std::string xPath;
+ std::string message;
+ Arabica::DOM::Node<std::string> node;
+ IssueSeverity severity;
+};
+USCXML_API std::ostream& operator<< (std::ostream& os, const InterpreterIssue& issue);
-USCXML_API std::ostream& operator<< (std::ostream& os, const InterpreterState& interpreterState);
class USCXML_API InterpreterImpl : public boost::enable_shared_from_this<InterpreterImpl> {
public:
@@ -453,6 +469,8 @@ protected:
void setupDOM();
virtual void setupIOProcessors();
+ std::list<InterpreterIssue> validate();
+
void initializeData(const Arabica::DOM::Element<std::string>& data);
void finalizeAndAutoForwardCurrentEvent();
@@ -615,6 +633,10 @@ public:
return _impl->step(0);
};
+ std::list<InterpreterIssue> validate() {
+ return _impl->validate();
+ }
+
InterpreterState getState() {
return _impl->getInterpreterState();
}
diff --git a/src/uscxml/plugins/DataModel.h b/src/uscxml/plugins/DataModel.h
index b1eafb9..be68ea2 100644
--- a/src/uscxml/plugins/DataModel.h
+++ b/src/uscxml/plugins/DataModel.h
@@ -42,6 +42,9 @@ public:
virtual bool validate(const std::string& location, const std::string& schema) = 0;
virtual bool isLocation(const std::string& expr) = 0;
+ virtual bool isValidSyntax(const std::string& expr) {
+ return true; // overwrite when datamodel supports it
+ }
virtual void setEvent(const Event& event) = 0;
virtual Data getStringAsData(const std::string& content) = 0;
@@ -125,6 +128,9 @@ public:
virtual bool isLocation(const std::string& expr) {
return _impl->isLocation(expr);
}
+ virtual bool isValidSyntax(const std::string& expr) {
+ return _impl->isValidSyntax(expr);
+ }
virtual void setEvent(const Event& event) {
return _impl->setEvent(event);
diff --git a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp
index 3a9cb27..b8ec2cc 100644
--- a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp
+++ b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp
@@ -485,18 +485,21 @@ void JSCDataModel::setForeach(const std::string& item,
bool JSCDataModel::isLocation(const std::string& expr) {
// location needs to be RHS and ++ is only valid for RHS
- JSStringRef scriptJS = JSStringCreateWithUTF8CString((expr + "++").c_str());
+ return isValidSyntax(expr + "++");
+}
+
+bool JSCDataModel::isValidSyntax(const std::string& expr) {
+ JSStringRef scriptJS = JSStringCreateWithUTF8CString(expr.c_str());
JSValueRef exception = NULL;
bool valid = JSCheckScriptSyntax(_ctx, scriptJS, NULL, 0, &exception);
JSStringRelease(scriptJS);
-
+
if (exception || !valid) {
return false;
}
return true;
}
-
-
+
bool JSCDataModel::isDeclared(const std::string& expr) {
JSStringRef scriptJS = JSStringCreateWithUTF8CString(expr.c_str());
JSValueRef exception = NULL;
diff --git a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.h b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.h
index 6792130..10d5999 100644
--- a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.h
+++ b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.h
@@ -50,6 +50,8 @@ public:
virtual bool validate(const std::string& location, const std::string& schema);
virtual bool isLocation(const std::string& expr);
+ virtual bool isValidSyntax(const std::string& expr);
+
virtual void setEvent(const Event& event);
virtual Data getStringAsData(const std::string& content);
diff --git a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp
index 2a27d47..7b0b8e9 100644
--- a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp
+++ b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp
@@ -458,12 +458,17 @@ bool V8DataModel::validate(const std::string& location, const std::string& schem
}
bool V8DataModel::isLocation(const std::string& expr) {
+ // location needs to be RHS and ++ is only valid for RHS
+ return isValidSyntax(expr + "++");
+}
+
+bool V8DataModel::isValidSyntax(const std::string& expr) {
v8::Locker locker;
v8::HandleScope handleScope;
v8::TryCatch tryCatch;
v8::Context::Scope contextScope(_contexts.back());
- v8::Handle<v8::String> source = v8::String::New((expr + "++").c_str());
+ v8::Handle<v8::String> source = v8::String::New(expr.c_str());
v8::Handle<v8::Script> script = v8::Script::Compile(source);
if (script.IsEmpty() || tryCatch.HasCaught()) {
diff --git a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h
index a9717a2..e67a5ab 100644
--- a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h
+++ b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.h
@@ -54,6 +54,7 @@ public:
virtual bool validate(const std::string& location, const std::string& schema);
virtual bool isLocation(const std::string& expr);
+ virtual bool isValidSyntax(const std::string& expr);
virtual uint32_t getLength(const std::string& expr);
virtual void setForeach(const std::string& item,
diff --git a/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp b/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp
index dd3a0b4..02b97c3 100644
--- a/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp
+++ b/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp
@@ -336,6 +336,10 @@ bool LuaDataModel::isLocation(const std::string& expr) {
return true;
}
+bool LuaDataModel::isValidSyntax(const std::string& expr) {
+ return true;
+}
+
uint32_t LuaDataModel::getLength(const std::string& expr) {
// we need the result of the expression on the lua stack -> has to "return"!
std::string trimmedExpr = boost::trim_copy(expr);
diff --git a/src/uscxml/plugins/datamodel/lua/LuaDataModel.h b/src/uscxml/plugins/datamodel/lua/LuaDataModel.h
index 69b8d57..39990c6 100644
--- a/src/uscxml/plugins/datamodel/lua/LuaDataModel.h
+++ b/src/uscxml/plugins/datamodel/lua/LuaDataModel.h
@@ -57,6 +57,7 @@ public:
virtual bool validate(const std::string& location, const std::string& schema);
virtual bool isLocation(const std::string& expr);
+ virtual bool isValidSyntax(const std::string& expr);
virtual uint32_t getLength(const std::string& expr);
virtual void setForeach(const std::string& item,
diff --git a/test/src/test-lifecycle.cpp b/test/src/test-lifecycle.cpp
index 22c724a..9b1ac5a 100644
--- a/test/src/test-lifecycle.cpp
+++ b/test/src/test-lifecycle.cpp
@@ -212,6 +212,30 @@ int main(int argc, char** argv) {
int iterations = 1;
while(iterations--) {
+
+ if (1) {
+ // syntactic xml parse error
+ try {
+ const char* xml =
+ "<scxml datamodel=\"ecmascript\">"
+ " <state id=\"start\">"
+ " <transition target=\"done\" cond=\"%sf\" />"
+ " </state>"
+ " <final id=\"done\" />"
+ "</scxml>";
+ Interpreter interpreter = Interpreter::fromXML(xml);
+ std::list<InterpreterIssue> issues = interpreter.validate();
+
+ for (std::list<InterpreterIssue>::iterator issueIter = issues.begin(); issueIter != issues.end(); issueIter++) {
+ std::cout << *issueIter << std::endl;
+ }
+
+ } catch (Event& e) {
+ std::cout << e;
+ }
+ }
+
+
if (1) {
// syntactic xml parse error
try {