From 9608216597fd17021d38e80689644beb3c85abb9 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Sun, 7 Dec 2014 15:31:09 +0100 Subject: More complete xinclude implementation and tests --- src/uscxml/Interpreter.cpp | 149 ++++++++++++++++++--- src/uscxml/Interpreter.h | 1 + .../plugins/datamodel/promela/PromelaDataModel.cpp | 10 +- .../plugins/datamodel/promela/PromelaParser.cpp | 11 +- test/ctest/CTestCustom.ctest.in | 36 ++--- test/uscxml/xinclude/included.scxml | 3 - test/uscxml/xinclude/test-fail-namespace1.include | 3 + test/uscxml/xinclude/test-fail-namespace1.scxml | 16 +++ test/uscxml/xinclude/test-fail-namespace2.include | 3 + test/uscxml/xinclude/test-fail-namespace2.scxml | 16 +++ test/uscxml/xinclude/test-fail-namespace3.include | 3 + test/uscxml/xinclude/test-fail-namespace3.scxml | 16 +++ test/uscxml/xinclude/test-fail-namespace4.include | 3 + test/uscxml/xinclude/test-fail-namespace4.scxml | 15 +++ .../uscxml/xinclude/test-recursive-include.include | 5 + test/uscxml/xinclude/test-recursive-include.scxml | 15 +++ test/uscxml/xinclude/test-xinclude-simple.scxml | 25 ++++ test/uscxml/xinclude/test-xinclude.scxml | 9 -- test/uscxml/xinclude/text-xinclude-simple.included | 3 + 19 files changed, 290 insertions(+), 52 deletions(-) delete mode 100644 test/uscxml/xinclude/included.scxml create mode 100644 test/uscxml/xinclude/test-fail-namespace1.include create mode 100644 test/uscxml/xinclude/test-fail-namespace1.scxml create mode 100644 test/uscxml/xinclude/test-fail-namespace2.include create mode 100644 test/uscxml/xinclude/test-fail-namespace2.scxml create mode 100644 test/uscxml/xinclude/test-fail-namespace3.include create mode 100644 test/uscxml/xinclude/test-fail-namespace3.scxml create mode 100644 test/uscxml/xinclude/test-fail-namespace4.include create mode 100644 test/uscxml/xinclude/test-fail-namespace4.scxml create mode 100644 test/uscxml/xinclude/test-recursive-include.include create mode 100644 test/uscxml/xinclude/test-recursive-include.scxml create mode 100644 test/uscxml/xinclude/test-xinclude-simple.scxml delete mode 100644 test/uscxml/xinclude/test-xinclude.scxml create mode 100644 test/uscxml/xinclude/text-xinclude-simple.included diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index cb780df..3d068f6 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -1148,6 +1148,7 @@ bool InterpreterImpl::runOnMainThread(int fps, bool blocking) { tthread::this_thread::sleep_for(tthread::chrono::milliseconds(nextRun - tthread::timeStamp())); } } else { + _lastRunOnMainThread = tthread::timeStamp(); return true; } } @@ -1246,7 +1247,6 @@ void InterpreterImpl::setupDOM() { _xpath.setNamespaceContext(*_nsInfo.getNSContext()); // normalize document - // TODO: Resolve XML includes #if 0 // make sure every state has an id @@ -1288,30 +1288,139 @@ void InterpreterImpl::setupDOM() { void InterpreterImpl::resolveXIncludes() { std::string xIncludeNS = _nsInfo.getXMLPrefixForNS("http://www.w3.org/2001/XInclude"); - if (xIncludeNS.size() > 0) { - Arabica::XPath::NodeSet xincludes = _xpath.evaluate("//" + xIncludeNS + "include", _document.getDocumentElement()).asNodeSet(); - for (int i = 0; i < xincludes.size(); i++) { - if (HAS_ATTR_CAST(xincludes[i], "href")) { - URL src(ATTR_CAST(xincludes[i], "href")); - if (!src.isAbsolute()) { - if (!src.toAbsolute(_baseURI)) { - LOG(ERROR) << "Cannot resolve relative URL '" << ATTR_CAST(xincludes[i], "href") << "' to absolute URL via base URL '" << _baseURI << "'"; - continue; - } + + // no element in namespace for xinclude, don't bother searching + if (xIncludeNS.size() == 0) + return; + + std::map mergedNs = _nsInfo.nsInfo; + + std::list includeChain; + includeChain.push_back(_sourceURI); + + Arabica::XPath::NodeSet xincludes = _xpath.evaluate("//" + xIncludeNS + "include", _document.getDocumentElement()).asNodeSet(); + for (int i = 0; i < xincludes.size(); i++) { + // recursively resolve includes + resolveXIncludes(includeChain, mergedNs, xIncludeNS, Element(xincludes[i])); + } + + // update NameSpaceInfo and reinit xpath resolver + _nsInfo = NameSpaceInfo(mergedNs); + _xpath.setNamespaceContext(*_nsInfo.getNSContext()); + +} + +void InterpreterImpl::resolveXIncludes(std::list includeChain, std::map& mergedNS, const std::string& xIncludeNS, Arabica::DOM::Element xinclude) { + NodeSet newNodes; + if (HAS_ATTR(xinclude, "href")) { + URL src(ATTR(xinclude, "href")); + if (!src.isAbsolute()) { + if (!src.toAbsolute(_baseURI)) { + LOG(ERROR) << "Cannot resolve relative URL '" << ATTR(xinclude, "href") << "' to absolute URL via base URL '" << _baseURI << "', trying xi:fallback"; + goto TRY_WITH_FALLBACK; + } + } + + if (std::find(includeChain.begin(), includeChain.end(), src.asString()) != includeChain.end()) { + std::stringstream incErr; + incErr << ("Ignoring recursive inclusion of '" + src.asString() + " via:") << std::endl; + for (std::list::iterator incIter = includeChain.begin(); incIter != includeChain.end(); incIter++) { + incErr << " " << *incIter << std::endl; + } + LOG(ERROR) << incErr.str(); + return; + } + includeChain.push_back(src.asString()); + + if (HAS_ATTR(xinclude, "accept")) { + src.addOutHeader("Accept", ATTR_CAST(xinclude, "accept")); + } + + if (HAS_ATTR(xinclude, "accept-language")) { + src.addOutHeader("Accept-Language", ATTR_CAST(xinclude, "accept-language")); + } + + std::string includedContent; + try { + includedContent = src.getInContent(); + } catch (Event e) { + goto TRY_WITH_FALLBACK; + } + + if (HAS_ATTR(xinclude, "parse") && iequals(ATTR(xinclude, "parse"), "text")) { + // parse as text + Text textNode = _document.createTextNode(includedContent); + xinclude.getParentNode().insertBefore(textNode, xinclude); + goto REMOVE_AND_RECURSE; + } else { + // parse as XML + NameSpacingParser xiParser = NameSpacingParser::fromXML(includedContent); + if (xiParser.errorsReported()) { + ERROR_PLATFORM_THROW(xiParser.errors()); + } else { + // scxml namespace prefixed and non-profixed + if (mergedNS.find("http://www.w3.org/2005/07/scxml") != mergedNS.end() && xiParser.nameSpace.find("http://www.w3.org/2005/07/scxml") == xiParser.nameSpace.end()) { + LOG(WARNING) << ("Warning for '" + DOMUtils::xPathForNode(xinclude) + "': root document maps SCXML namespace to prefix '" + mergedNS["http://www.w3.org/2005/07/scxml"] + "', included document does not specify SCXML namespace at all"); } - NameSpacingParser xiParser = NameSpacingParser::fromXML(src.getInContent()); - if (xiParser.errorsReported()) { - ERROR_PLATFORM_THROW(xiParser.errors()); - } else { - // import DOM - Node imported = _document.importNode(xiParser.getDocument().getDocumentElement(), true); - xincludes[i].getParentNode().insertBefore(imported, xincludes[i]); - xincludes[i].getParentNode().removeChild(xincludes[i]); + if (mergedNS.find("http://www.w3.org/2005/07/scxml") == mergedNS.end() && xiParser.nameSpace.find("http://www.w3.org/2005/07/scxml") != xiParser.nameSpace.end()) { + LOG(ERROR) << ("Error for '" + DOMUtils::xPathForNode(xinclude) + "': root document uses implicit SCXML namespace without prefix, included document does map it to prefix '" + xiParser.nameSpace["http://www.w3.org/2005/07/scxml"] + "', trying xi:fallback"); + goto TRY_WITH_FALLBACK; + + } + + // merge namespaces to prefix mappings + for (std::map::iterator nsIter = xiParser.nameSpace.begin(); nsIter != xiParser.nameSpace.end(); nsIter++) { + + // same nsURL but different prefix + if (mergedNS.find(nsIter->first) != mergedNS.end() && mergedNS[nsIter->first] != nsIter->second) { + LOG(ERROR) << ("Error for '" + DOMUtils::xPathForNode(xinclude) + "': Cannot map namespace '" + nsIter->first + "' to prefix '" + nsIter->second + "', it is already mapped to prefix '" + mergedNS[nsIter->first] + "', trying xi:fallback"); + goto TRY_WITH_FALLBACK; + } + + // same prefix but different nsURL + for (std::map::iterator currIter = mergedNS.begin(); currIter != mergedNS.end(); currIter++) { + if (currIter->second == nsIter->second && currIter->first != nsIter->first) { + LOG(ERROR) << ("Error for '" + DOMUtils::xPathForNode(xinclude) + "': Cannot assign prefix '" + nsIter->second + "' to namespace '" + nsIter->first + "' it is already a prefix for '" + currIter->first + "', trying xi:fallback"); + goto TRY_WITH_FALLBACK; + } + } + mergedNS[nsIter->first] = nsIter->second; } + + // import DOM + Node imported = _document.importNode(xiParser.getDocument().getDocumentElement(), true); + newNodes.push_back(imported); + xinclude.getParentNode().insertBefore(imported, xinclude); + goto REMOVE_AND_RECURSE; } } + } else { + LOG(ERROR) << "No href attribute for xi:xinclude at '" << DOMUtils::xPathForNode(xinclude) << "', trying xi:fallback"; + goto TRY_WITH_FALLBACK; + } +TRY_WITH_FALLBACK: + { + NodeSet fallbacks = filterChildElements(xIncludeNS + "fallback", xinclude); + if (fallbacks.size() > 0) { + LOG(WARNING) << "Using xi:fallback for '" << DOMUtils::xPathForNode(xinclude) << "'"; + // move the fallbacks children in place + NodeList fallbackChildren = fallbacks[0].getChildNodes(); + while (fallbacks[0].getChildNodes().getLength() > 0) { + newNodes.push_back(fallbackChildren.item(0)); + xinclude.getParentNode().insertBefore(fallbackChildren.item(0), xinclude); + } + } else { + LOG(WARNING) << "No xi:fallback found for '" << DOMUtils::xPathForNode(xinclude) << "', document most likely incomplete"; + } + } +REMOVE_AND_RECURSE: + xinclude.getParentNode().removeChild(xinclude); + for (int i = 0; i < newNodes.size(); i++) { + Arabica::XPath::NodeSet xincludes = filterChildElements(xIncludeNS + "include", newNodes[i], true); + for (int j = 0; j < xincludes.size(); j++) { + resolveXIncludes(includeChain, mergedNS, xIncludeNS, Element(xincludes[j])); + } } - } void InterpreterImpl::init() { diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index d962083..12582cf 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -459,6 +459,7 @@ protected: void init(); void setupDOM(); void resolveXIncludes(); + void resolveXIncludes(std::list includeChain, std::map& mergedNS, const std::string& xIncludeNS, Arabica::DOM::Element xinclude); virtual void setupIOProcessors(); std::list validate(); diff --git a/src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp b/src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp index d63c354..5b3c79c 100644 --- a/src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp +++ b/src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp @@ -375,8 +375,14 @@ void PromelaDataModel::evaluateDecl(void* ast) { std::list::iterator opIterAsgn = (*nameIter)->operands.begin(); PromelaParserNode* name = *opIterAsgn++; PromelaParserNode* expr = *opIterAsgn++; - - variable.compound["value"] = evaluateExpr(expr); + + try { + variable.compound["value"] = evaluateExpr(expr); + } catch(uscxml::Event e) { + // test277, declare and throw + _variables.compound[name->value] = variable; + throw e; + } assert(opIterAsgn == (*nameIter)->operands.end()); _variables.compound[name->value] = variable; diff --git a/src/uscxml/plugins/datamodel/promela/PromelaParser.cpp b/src/uscxml/plugins/datamodel/promela/PromelaParser.cpp index ada2c48..74cb791 100644 --- a/src/uscxml/plugins/datamodel/promela/PromelaParser.cpp +++ b/src/uscxml/plugins/datamodel/promela/PromelaParser.cpp @@ -38,6 +38,15 @@ void promela_error (void* yylloc_param, uscxml::PromelaParser* ctx, void* yyscan PROMELA_LTYPE* yylloc = (PROMELA_LTYPE*)yylloc_param; // mark as pending exception as we cannot throw from constructor and have the destructor called ERROR_EXECUTION(excEvent, err); + excEvent.data.compound["line"] = uscxml::Data(yylloc->first_line, uscxml::Data::VERBATIM); + excEvent.data.compound["col"] = uscxml::Data(yylloc->first_column, uscxml::Data::VERBATIM); + + std::stringstream ssUnderline; + for (int i = 0; i < yylloc->first_column; i++) + ssUnderline << " "; + ssUnderline << "^"; + excEvent.data.compound["sourcemark"] = uscxml::Data(ssUnderline.str(), uscxml::Data::VERBATIM); + ctx->pendingException = excEvent; } @@ -85,7 +94,7 @@ void PromelaParser::init(const std::string& expr) { if (pendingException.name.size() > 0) { // parsing failed in promela_error destroy(); - pendingException.data.compound["code"] = Data(expr, Data::VERBATIM); + pendingException.data.compound["sourceline"] = Data(expr, Data::VERBATIM); throw pendingException; } } diff --git a/test/ctest/CTestCustom.ctest.in b/test/ctest/CTestCustom.ctest.in index feba4c1..07ade54 100644 --- a/test/ctest/CTestCustom.ctest.in +++ b/test/ctest/CTestCustom.ctest.in @@ -34,21 +34,18 @@ set(CTEST_CUSTOM_TESTS_IGNORE ### Ignore for PROMELA datamodel - "promela/test178.scxml" # manual test + "promela/test178.scxml" # two identical params in _event.raw - failed + "promela/test230.scxml" # autoforwarded events are identical - passed + "promela/test250.scxml" # no onexit in cancelled invoker - passed + "promela/test301.scxml" # reject invalid script - passed + "promela/test307.scxml" # declare variable via script - failed + "promela/test415.scxml" # terminate on toplevel final - passed + # "promela/test513.txt" # manual test + "promela/test190.scxml" # string concatenation "promela/test224.scxml" # string operation startWith - "promela/test230.scxml" # manual test - "promela/test250.scxml" # manual test - "promela/test277.scxml" # no runtime checks for undeclared variables "promela/test280.scxml" # no runtime checks for undeclared variables - "promela/test301.scxml" # manual test - "promela/test307.scxml" # manual test -# "promela/test330.scxml" # no "defined" operator in promela -# "promela/test333.scxml" # no typeof or "defined" operator in promela -# "promela/test335.scxml" # no typeof or "defined" operator in promela -# "promela/test337.scxml" # no typeof or "defined" operator in promela "promela/test350.scxml" # string concatenation - "promela/test415.scxml" # manual test "promela/test509.scxml" # string operation contains "promela/test518.scxml" # string operation contains "promela/test519.scxml" # string operation contains @@ -57,7 +54,18 @@ set(CTEST_CUSTOM_TESTS_IGNORE "promela/test530.scxml" # assigns DOM node to variable "promela/test534.scxml" # string operation contains + ### Ignore for PROMELA model checking + + # manual tests + "spin/promela/test178.scxml" # two identical params in _event.raw - failed + "spin/promela/test230.scxml" # autoforwarded events are identical - passed + "spin/promela/test250.scxml" # no onexit in cancelled invoker - passed + "spin/promela/test301.scxml" # reject invalid script - passed + "spin/promela/test307.scxml" # declare variable via script - failed + "spin/promela/test415.scxml" # terminate on toplevel final - passed + # "spin/promela/test513.txt" # manual test + # fail for syntax "spin/promela/test150.scxml" # test that foreach causes a new variable to be declared "spin/promela/test151.scxml" # test that foreach causes a new variable to be declared @@ -68,10 +76,8 @@ set(CTEST_CUSTOM_TESTS_IGNORE "spin/promela/test280.scxml" # late data binding / undeclared variable "spin/promela/test286.scxml" # assignment to a non-declared var causes an error "spin/promela/test294.scxml" # mixed types for event.data via donedata - "spin/promela/test301.scxml" # manual test "spin/promela/test302.scxml" # variable not declared "spin/promela/test304.scxml" # variable not declared - "spin/promela/test307.scxml" # manual test "spin/promela/test309.scxml" # 'return' as an invalid boolean expression ought to eval to false "spin/promela/test311.scxml" # assignment to a non-declared var "spin/promela/test312.scxml" # assignment of 'return' @@ -88,7 +94,6 @@ set(CTEST_CUSTOM_TESTS_IGNORE "spin/promela/test354.scxml" # mixed types for event.data "spin/promela/test401.scxml" # variable not declared "spin/promela/test402.scxml" # variable not declared - "spin/promela/test415.scxml" # manual test "spin/promela/test436.scxml" # In(s) -> _x.states[s] prevents completion as NULL dm is hardcoded "spin/promela/test487.scxml" # assignment of 'return' "spin/promela/test509.scxml" # string operation contains @@ -101,18 +106,15 @@ set(CTEST_CUSTOM_TESTS_IGNORE # fail for semantics "spin/promela/test159.scxml" # error raised causes all subsequent elements to be skipped - "spin/promela/test178.scxml" # manual test with two identical params - failed "spin/promela/test194.scxml" # illegal target for send "spin/promela/test199.scxml" # invalid send type "spin/promela/test216.scxml" # nested SCXML document with srcexpr at invoke - "spin/promela/test230.scxml" # nested SCXML document with manual test "spin/promela/test298.scxml" # non-existent data model location "spin/promela/test331.scxml" # tests _error.type via 'error.execution' "spin/promela/test332.scxml" # tests _error.sendid via 'error.execution' "spin/promela/test343.scxml" # test that illegal produces error.execution "spin/promela/test488.scxml" # illegal expr in produces error.execution "spin/promela/test496.scxml" # tests error.communication with illegal target - "spin/promela/test513.txt" # pure manual wget "spin/promela/test521.scxml" # tests error.communication with illegal target "spin/promela/test528.scxml" # illegal 'expr' produces error.execution "spin/promela/test531.scxml" # uses _ioprocessors.basichttp.location diff --git a/test/uscxml/xinclude/included.scxml b/test/uscxml/xinclude/included.scxml deleted file mode 100644 index 4d8c8f5..0000000 --- a/test/uscxml/xinclude/included.scxml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/test/uscxml/xinclude/test-fail-namespace1.include b/test/uscxml/xinclude/test-fail-namespace1.include new file mode 100644 index 0000000..28f0cc6 --- /dev/null +++ b/test/uscxml/xinclude/test-fail-namespace1.include @@ -0,0 +1,3 @@ + + + diff --git a/test/uscxml/xinclude/test-fail-namespace1.scxml b/test/uscxml/xinclude/test-fail-namespace1.scxml new file mode 100644 index 0000000..dea45c6 --- /dev/null +++ b/test/uscxml/xinclude/test-fail-namespace1.scxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/test/uscxml/xinclude/test-fail-namespace2.include b/test/uscxml/xinclude/test-fail-namespace2.include new file mode 100644 index 0000000..5a69573 --- /dev/null +++ b/test/uscxml/xinclude/test-fail-namespace2.include @@ -0,0 +1,3 @@ + + + diff --git a/test/uscxml/xinclude/test-fail-namespace2.scxml b/test/uscxml/xinclude/test-fail-namespace2.scxml new file mode 100644 index 0000000..f256e54 --- /dev/null +++ b/test/uscxml/xinclude/test-fail-namespace2.scxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/test/uscxml/xinclude/test-fail-namespace3.include b/test/uscxml/xinclude/test-fail-namespace3.include new file mode 100644 index 0000000..d6b2fc7 --- /dev/null +++ b/test/uscxml/xinclude/test-fail-namespace3.include @@ -0,0 +1,3 @@ + + + diff --git a/test/uscxml/xinclude/test-fail-namespace3.scxml b/test/uscxml/xinclude/test-fail-namespace3.scxml new file mode 100644 index 0000000..f7c6dd1 --- /dev/null +++ b/test/uscxml/xinclude/test-fail-namespace3.scxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/test/uscxml/xinclude/test-fail-namespace4.include b/test/uscxml/xinclude/test-fail-namespace4.include new file mode 100644 index 0000000..279b53e --- /dev/null +++ b/test/uscxml/xinclude/test-fail-namespace4.include @@ -0,0 +1,3 @@ + + + diff --git a/test/uscxml/xinclude/test-fail-namespace4.scxml b/test/uscxml/xinclude/test-fail-namespace4.scxml new file mode 100644 index 0000000..f5d4af7 --- /dev/null +++ b/test/uscxml/xinclude/test-fail-namespace4.scxml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/test/uscxml/xinclude/test-recursive-include.include b/test/uscxml/xinclude/test-recursive-include.include new file mode 100644 index 0000000..a46b928 --- /dev/null +++ b/test/uscxml/xinclude/test-recursive-include.include @@ -0,0 +1,5 @@ + + + + diff --git a/test/uscxml/xinclude/test-recursive-include.scxml b/test/uscxml/xinclude/test-recursive-include.scxml new file mode 100644 index 0000000..62618a1 --- /dev/null +++ b/test/uscxml/xinclude/test-recursive-include.scxml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/test/uscxml/xinclude/test-xinclude-simple.scxml b/test/uscxml/xinclude/test-xinclude-simple.scxml new file mode 100644 index 0000000..72127cd --- /dev/null +++ b/test/uscxml/xinclude/test-xinclude-simple.scxml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/uscxml/xinclude/test-xinclude.scxml b/test/uscxml/xinclude/test-xinclude.scxml deleted file mode 100644 index a07c087..0000000 --- a/test/uscxml/xinclude/test-xinclude.scxml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/test/uscxml/xinclude/text-xinclude-simple.included b/test/uscxml/xinclude/text-xinclude-simple.included new file mode 100644 index 0000000..4ff416f --- /dev/null +++ b/test/uscxml/xinclude/text-xinclude-simple.included @@ -0,0 +1,3 @@ + + + \ No newline at end of file -- cgit v0.12