summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2014-12-07 14:31:09 (GMT)
committerStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2014-12-07 14:31:09 (GMT)
commit9608216597fd17021d38e80689644beb3c85abb9 (patch)
tree1e3375c8beecd160be1f66750c201167c1b8c6a0
parent93f8cb376e6e496f0194d1e0657081354434da96 (diff)
downloaduscxml-9608216597fd17021d38e80689644beb3c85abb9.zip
uscxml-9608216597fd17021d38e80689644beb3c85abb9.tar.gz
uscxml-9608216597fd17021d38e80689644beb3c85abb9.tar.bz2
More complete xinclude implementation and tests
-rw-r--r--src/uscxml/Interpreter.cpp149
-rw-r--r--src/uscxml/Interpreter.h1
-rw-r--r--src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp10
-rw-r--r--src/uscxml/plugins/datamodel/promela/PromelaParser.cpp11
-rw-r--r--test/ctest/CTestCustom.ctest.in36
-rw-r--r--test/uscxml/xinclude/test-fail-namespace1.include3
-rw-r--r--test/uscxml/xinclude/test-fail-namespace1.scxml16
-rw-r--r--test/uscxml/xinclude/test-fail-namespace2.include3
-rw-r--r--test/uscxml/xinclude/test-fail-namespace2.scxml16
-rw-r--r--test/uscxml/xinclude/test-fail-namespace3.include (renamed from test/uscxml/xinclude/included.scxml)2
-rw-r--r--test/uscxml/xinclude/test-fail-namespace3.scxml16
-rw-r--r--test/uscxml/xinclude/test-fail-namespace4.include3
-rw-r--r--test/uscxml/xinclude/test-fail-namespace4.scxml15
-rw-r--r--test/uscxml/xinclude/test-recursive-include.include5
-rw-r--r--test/uscxml/xinclude/test-recursive-include.scxml15
-rw-r--r--test/uscxml/xinclude/test-xinclude-simple.scxml25
-rw-r--r--test/uscxml/xinclude/test-xinclude.scxml9
-rw-r--r--test/uscxml/xinclude/text-xinclude-simple.included3
18 files changed, 288 insertions, 50 deletions
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<std::string> 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<std::string, std::string> mergedNs = _nsInfo.nsInfo;
+
+ std::list<std::string> includeChain;
+ includeChain.push_back(_sourceURI);
+
+ Arabica::XPath::NodeSet<std::string> xincludes = _xpath.evaluate("//" + xIncludeNS + "include", _document.getDocumentElement()).asNodeSet();
+ for (int i = 0; i < xincludes.size(); i++) {
+ // recursively resolve includes
+ resolveXIncludes(includeChain, mergedNs, xIncludeNS, Element<std::string>(xincludes[i]));
+ }
+
+ // update NameSpaceInfo and reinit xpath resolver
+ _nsInfo = NameSpaceInfo(mergedNs);
+ _xpath.setNamespaceContext(*_nsInfo.getNSContext());
+
+}
+
+void InterpreterImpl::resolveXIncludes(std::list<std::string> includeChain, std::map<std::string, std::string>& mergedNS, const std::string& xIncludeNS, Arabica::DOM::Element<std::string> xinclude) {
+ NodeSet<std::string> 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<std::string>::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<std::string> 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<std::string> 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<std::string, std::string>::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<std::string, std::string>::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<std::string> 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<std::string> 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<std::string> 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<std::string> xincludes = filterChildElements(xIncludeNS + "include", newNodes[i], true);
+ for (int j = 0; j < xincludes.size(); j++) {
+ resolveXIncludes(includeChain, mergedNS, xIncludeNS, Element<std::string>(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<std::string> includeChain, std::map<std::string, std::string>& mergedNS, const std::string& xIncludeNS, Arabica::DOM::Element<std::string> xinclude);
virtual void setupIOProcessors();
std::list<InterpreterIssue> 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<PromelaParserNode*>::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 <param> produces error.execution
"spin/promela/test488.scxml" # illegal expr in <param> 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/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 @@
+<state id="start" xmlns:bar="http://www.w3.org/2001/Foo">
+ <transition target="pass" />
+</state>
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 @@
+<!-- Namespace http://www.w3.org/2001/Foo is mapped from prefix foo and bar -> error -->
+<scxml
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:foo="http://www.w3.org/2001/Foo">
+ <!-- include from file -->
+ <xi:include href="test-fail-namespace1.include" />
+ <state id="s0">
+ <transition target="fail" />
+ </state>
+ <final id="fail">
+ <onentry><log expr="FAIL!" /></onentry>
+ </final>
+ <final id="pass">
+ <onentry><log expr="PASS!" /></onentry>
+ </final>
+</scxml>
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 @@
+<state id="start" xmlns:foo="http://www.w3.org/2001/Bar">
+ <transition target="pass" />
+</state>
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 @@
+<!-- Prefix foo is pointing to http://www.w3.org/2001/Foo and http://www.w3.org/2001/Bar -> error -->
+<scxml
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:foo="http://www.w3.org/2001/Foo">
+ <!-- include from file -->
+ <xi:include href="test-fail-namespace2.include" />
+ <state id="s0">
+ <transition target="fail" />
+ </state>
+ <final id="fail">
+ <onentry><log expr="FAIL!" /></onentry>
+ </final>
+ <final id="pass">
+ <onentry><log expr="PASS!" /></onentry>
+ </final>
+</scxml>
diff --git a/test/uscxml/xinclude/included.scxml b/test/uscxml/xinclude/test-fail-namespace3.include
index 4d8c8f5..d6b2fc7 100644
--- a/test/uscxml/xinclude/included.scxml
+++ b/test/uscxml/xinclude/test-fail-namespace3.include
@@ -1,3 +1,3 @@
<state id="start">
<transition target="pass" />
-</state> \ No newline at end of file
+</state>
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 @@
+<!-- Qualified SCXML Namespace includes unqualified -> warning -->
+<scxml:scxml
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:scxml="http://www.w3.org/2005/07/scxml">
+ <!-- include from file -->
+ <xi:include href="test-fail-namespace3.include" />
+ <scxml:state id="s0">
+ <scxml:transition target="fail" />
+ </scxml:state>
+ <scxml:final id="fail">
+ <scxml:onentry><scxml:log expr="FAIL!" /></scxml:onentry>
+ </scxml:final>
+ <scxml:final id="pass">
+ <scxml:onentry><scxml:log expr="PASS!" /></scxml:onentry>
+ </scxml:final>
+</scxml:scxml>
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 @@
+<scxml:state id="start" xmlns:scxml="http://www.w3.org/2005/07/scxml">
+ <scxml:transition target="pass" />
+</scxml:state>
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 @@
+<!-- Unqualified SCXML Namespace includes qualified -> error -->
+<scxml
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+ <!-- include from file -->
+ <xi:include href="test-fail-namespace4.include" />
+ <state id="s0">
+ <transition target="fail" />
+ </state>
+ <final id="fail">
+ <onentry><log expr="FAIL!" /></onentry>
+ </final>
+ <final id="pass">
+ <onentry><log expr="PASS!" /></onentry>
+ </final>
+</scxml>
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 @@
+<state id="foo"
+ xmlns:xi="http://www.w3.org/2001/XInclude" >
+ <!-- include from file -->
+ <xi:include href="test-recursive-include.include" />
+</state>
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 @@
+<!-- include is recursive -> error -->
+<scxml
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+ <!-- include from file -->
+ <xi:include href="test-recursive-include.include" />
+ <state id="s0">
+ <transition target="fail" />
+ </state>
+ <final id="fail">
+ <onentry><log expr="FAIL!" /></onentry>
+ </final>
+ <final id="pass">
+ <onentry><log expr="PASS!" /></onentry>
+ </final>
+</scxml>
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 @@
+<scxml xmlns:xi="http://www.w3.org/2001/XInclude">
+ <!-- include from file -->
+ <xi:include href="text-xinclude-simple.included" />
+ <state id="s0">
+ <transition target="fail" />
+ </state>
+ <state id="s1">
+ <!-- force fallback via invalid url -->
+ <xi:include href="included.invalid">
+ <xi:fallback>
+ <transition target="s2" />
+ </xi:fallback>
+ </xi:include>
+ <transition target="fail" />
+ </state>
+ <state id="s2">
+ <transition target="pass" />
+ </state>
+ <final id="fail">
+ <onentry><log expr="FAIL!" /></onentry>
+ </final>
+ <final id="pass">
+ <onentry><log expr="PASS!" /></onentry>
+ </final>
+</scxml> \ 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 @@
-<scxml xmlns:xi="http://www.w3.org/2001/XInclude">
- <xi:include href="included.scxml" />
- <final id="fail">
- <onentry><log expr="FAIL!" /></onentry>
- </final>
- <final id="pass">
- <onentry><log expr="PASS!" /></onentry>
- </final>
-</scxml> \ 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 @@
+<state id="start">
+ <transition target="s1" />
+</state> \ No newline at end of file