From f7ad82f972bf46571bb5229205f877f8ab31069d Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Thu, 21 Aug 2014 17:14:55 +0200 Subject: New Interpreter::validate() to identify issues with a document before running it --- .../java/src/org/uscxml/tests/TestValidation.java | 35 ++ src/bindings/swig/csharp/uscxml.i | 2 + src/bindings/swig/java/uscxml.i | 2 + src/uscxml/Factory.cpp | 39 ++ src/uscxml/Factory.h | 5 + src/uscxml/Interpreter.cpp | 396 ++++++++++++++++++++- src/uscxml/Interpreter.h | 24 +- src/uscxml/plugins/DataModel.h | 6 + .../ecmascript/JavaScriptCore/JSCDataModel.cpp | 11 +- .../ecmascript/JavaScriptCore/JSCDataModel.h | 2 + .../datamodel/ecmascript/v8/V8DataModel.cpp | 7 +- .../plugins/datamodel/ecmascript/v8/V8DataModel.h | 1 + src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp | 4 + src/uscxml/plugins/datamodel/lua/LuaDataModel.h | 1 + test/src/test-lifecycle.cpp | 24 ++ 15 files changed, 542 insertions(+), 17 deletions(-) create mode 100644 embedding/java/src/org/uscxml/tests/TestValidation.java 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 = + "" + + " " + + " " + + " " + + " " + + ""; + 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; %template(DataList) std::list; %template(DataMap) std::map; %template(StringSet) std::set; 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; %template(DataList) std::list; %template(DataMap) std::map; %template(StringSet) std::set; 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 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 Factory::createInvoker(const std::string& type, InterpreterImpl* interpreter) { // do we have this type ourself? @@ -550,6 +559,16 @@ boost::shared_ptr Factory::createInvoker(const std::string& type, I return boost::shared_ptr(); } + +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 Factory::createDataModel(const std::string& type, InterpreterImpl* interpreter) { // do we have this type ourself? @@ -572,6 +591,16 @@ boost::shared_ptr Factory::createDataModel(const std::string& typ return boost::shared_ptr(); } + +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 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 Factory::createIOProcessor(const std::string& return boost::shared_ptr(); } +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 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 createInvoker(const std::string& type, InterpreterImpl* interpreter); boost::shared_ptr 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 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 node, IssueSeverity severity) : message(msg), node(node), severity(severity) { + xPath = DOMUtils::xPathForNode(node); +} + +std::list InterpreterImpl::validate() { + // some things we need to prepare first + if (_factory == NULL) + _factory = Factory::getInstance(); + setupDOM(); + + std::list 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 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 state = Element(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 transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + for (int i = 0; i < transitions.size(); i++) { + Element transition = Element(transitions[i]); + + // check for valid target + std::list targetIds = InterpreterImpl::tokenizeIdRefs(ATTR(transition, "target")); + for (std::list::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 state = Element(allStates[i]); + if (HAS_ATTR(state, "initial")) { + std::list intials = InterpreterImpl::tokenizeIdRefs(ATTR(state, "initial")); + for (std::list::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 invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true); + for (int i = 0; i < invokes.size(); i++) { + Element invoke = Element(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 sends = filterChildElements(_nsInfo.xmlNSPrefix + "send", _scxml, true); + for (int i = 0; i < sends.size(); i++) { + Element send = Element(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 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 block = Element(allExecContents[i]); + NodeSet execContents = filterChildType(Node_base::ELEMENT_NODE, block); + for (int j = 0; j < execContents.size(); j++) { + Element execContent = Element(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 scripts = filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml, true); + for (int i = 0; i < scripts.size(); i++) { + Element script = Element(scripts[i]); + + if (script.hasChildNodes()) { + // search for the text node with the actual script + std::string scriptContent; + for (Node 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 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 condAttr = Element(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 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 withExprAttr = Element(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 foreachs = filterChildElements(_nsInfo.xmlNSPrefix + "foreach", _scxml, true); + for (int i = 0; i < foreachs.size(); i++) { + Element foreach = Element(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 sends = filterChildElements(_nsInfo.xmlNSPrefix + "send", _scxml, true); + for (int i = 0; i < sends.size(); i++) { + Element send = Element(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 invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true); + for (int i = 0; i < invokes.size(); i++) { + Element invoke = Element(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 cancels = filterChildElements(_nsInfo.xmlNSPrefix + "cancel", _scxml, true); + for (int i = 0; i < cancels.size(); i++) { + Element cancel = Element(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 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 scxmls; + if (_nsInfo.nsURL.size() == 0) { + scxmls = _document.getElementsByTagName("scxml"); + } else { + scxmls = _document.getElementsByTagNameNS(_nsInfo.nsURL, "scxml"); + } - _scxml = (Arabica::DOM::Element)scxmls.item(0); + if (scxmls.getLength() == 0) { + ERROR_PLATFORM_THROW("Cannot find SCXML element in DOM"); + } + _scxml = (Arabica::DOM::Element)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 node, IssueSeverity severity); + + std::string xPath; + std::string message; + Arabica::DOM::Node 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 { public: @@ -453,6 +469,8 @@ protected: void setupDOM(); virtual void setupIOProcessors(); + std::list validate(); + void initializeData(const Arabica::DOM::Element& data); void finalizeAndAutoForwardCurrentEvent(); @@ -615,6 +633,10 @@ public: return _impl->step(0); }; + std::list 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 source = v8::String::New((expr + "++").c_str()); + v8::Handle source = v8::String::New(expr.c_str()); v8::Handle 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 = + "" + " " + " " + " " + " " + ""; + Interpreter interpreter = Interpreter::fromXML(xml); + std::list issues = interpreter.validate(); + + for (std::list::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 { -- cgit v0.12