/** * @file * @author 2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) * @copyright Simplified BSD * * @cond * This program is free software: you can redistribute it and/or modify * it under the terms of the FreeBSD license as published by the FreeBSD * project. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the FreeBSD license along with this * program. If not, see . * @endcond */ #include "Predicates.h" #include "uscxml/util/String.h" namespace uscxml { using namespace xercesc; std::list getChildStates(const DOMElement* state, bool properOnly) { std::list children; DOMNodeList* childElems = state->getChildNodes(); for (size_t i = 0; i < childElems->getLength(); i++) { if (childElems->item(i)->getNodeType() != DOMNode::ELEMENT_NODE) continue; DOMElement* childElem = dynamic_cast(childElems->item(i)); if (isState(childElem, properOnly)) { children.push_back(childElem); } } return children; } std::list getChildStates(const std::list& states, bool properOnly) { std::list children; for (auto stateIter = states.begin(); stateIter != states.end(); stateIter++) { std::list tmp = getChildStates(*stateIter, properOnly); children.merge(tmp); } return children; } DOMElement* getParentState(const DOMElement* element) { DOMNode* parent = element->getParentNode(); while(parent && !isState(dynamic_cast(parent))) { parent = parent->getParentNode(); } return dynamic_cast(parent); } DOMElement* getSourceState(const DOMElement* transition) { if (iequals(TAGNAME_CAST(transition->getParentNode()), XML_PREFIX(transition).str() + "initial")) return dynamic_cast(transition->getParentNode()->getParentNode()); return dynamic_cast(transition->getParentNode()); } /** See: http://www.w3.org/TR/scxml/#LCCA The Least Common Compound Ancestor is the or element s such that s is a proper ancestor of all states on stateList and no descendant of s has this property. Note that there is guaranteed to be such an element since the wrapper element is a common ancestor of all states. Note also that since we are speaking of proper ancestor (parent or parent of a parent, etc.) the LCCA is never a member of stateList. */ #define VERBOSE_FIND_LCCA 0 DOMElement* findLCCA(const std::list& states) { std::list ancestors = getProperAncestors(states.front(), NULL); DOMElement* ancestor = NULL; for (auto ancIter = ancestors.begin(); ancIter != ancestors.end(); ancIter++) { if (!isCompound(dynamic_cast(*ancIter))) continue; for (auto stateIter = states.begin(); stateIter != states.end(); stateIter++) { #if VERBOSE_FIND_LCCA std::cerr << "Checking " << ATTR_CAST(states[j], "id") << " and " << ATTR_CAST(ancestors[i], "id") << std::endl; #endif if (!DOMUtils::isDescendant(*stateIter, *ancIter)) goto NEXT_ANCESTOR; } ancestor = *ancIter; break; NEXT_ANCESTOR: ; } // take uppermost root as ancestor if (!ancestor) ancestor = ancestors.back(); #if VERBOSE_FIND_LCCA std::cerr << " -> " << ATTR_CAST(ancestor, "id") << " " << ancestor.getLocalName() << std::endl; #endif return ancestor; } /* * If state2 is null, returns the set of all ancestors of state1 in ancestry order * (state1's parent followed by the parent's parent, etc. up to an including the * element). If state2 is non-null, returns in ancestry order the set of all ancestors * of state1, up to but not including state2. (A "proper ancestor" of a state is its * parent, or the parent's parent, or the parent's parent's parent, etc.))If state2 is * state1's parent, or equal to state1, or a descendant of state1, this returns the empty set. */ std::list getProperAncestors(const DOMElement* s1, const DOMElement* s2) { std::list ancestors; if (isState(s1)) { DOMNode* node = (DOMNode*)s1; while((node = node->getParentNode())) { if (node->getNodeType() != DOMNode::ELEMENT_NODE) break; const DOMElement* nodeElem = dynamic_cast(node); if (!isState(nodeElem)) break; if (!iequals(LOCALNAME(nodeElem), "parallel") && !iequals(LOCALNAME(nodeElem), "state") && !iequals(LOCALNAME(nodeElem), "scxml")) break; if (node == s2) break; ancestors.push_back(dynamic_cast(node)); } } return ancestors; } std::list getExitSet(const DOMElement* transition, const DOMElement* root) { std::list statesToExit; if (HAS_ATTR(transition, "target")) { DOMElement* domain = getTransitionDomain(transition, root); if (!domain) return statesToExit; // std::cout << DOMUtils::xPathForNode(domain) << std::endl; std::set elements; elements.insert(XML_PREFIX(transition).str() + "parallel"); elements.insert(XML_PREFIX(transition).str() + "state"); elements.insert(XML_PREFIX(transition).str() + "final"); statesToExit = DOMUtils::inDocumentOrder(elements, domain); if (statesToExit.front() == domain) { statesToExit.pop_front(); // do not include domain itself } } return statesToExit; } bool conflicts(const DOMElement* t1, const DOMElement* t2, const DOMElement* root) { return (DOMUtils::hasIntersection(getExitSet(t1, root), getExitSet(t2, root)) || (getSourceState(t1) == getSourceState(t2)) || (DOMUtils::isDescendant(getSourceState(t1), getSourceState(t2))) || (DOMUtils::isDescendant(getSourceState(t2), getSourceState(t1)))); } bool isState(const DOMElement* state, bool properOnly) { if (!state) return false; std::string localName = LOCALNAME(state); if (iequals("state", localName)) return true; if (iequals("scxml", localName)) return true; if (iequals("parallel", localName)) return true; if (iequals("final", localName)) return true; if (properOnly) return false; if (iequals("history", localName)) return true; if (iequals("initial", localName)) return true; return false; } bool isFinal(const DOMElement* state) { std::string localName = LOCALNAME(state); if (iequals("final", localName)) return true; if (HAS_ATTR(state, "final") && iequals("true", ATTR(state, "final"))) return true; return false; } bool isAtomic(const DOMElement* state) { if (!isState(state)) return false; if (iequals("final", LOCALNAME(state))) return true; if (iequals("parallel", LOCALNAME(state))) return false; if (getChildStates(state).size() > 0) return false; return true; } bool isHistory(const DOMElement* state) { if (iequals("history", LOCALNAME(state))) return true; return false; } bool isParallel(const DOMElement* state) { if (!isState(state)) return false; if (iequals("parallel", LOCALNAME(state))) return true; return false; } bool isCompound(const DOMElement* state) { if (!isState(state)) return false; if (iequals(LOCALNAME(state), "parallel")) // parallel is no compound state return false; if (getChildStates(state).size() > 0) return true; return false; } std::list getTargetStates(const DOMElement* transition, const DOMElement* root) { std::list targetStates; std::string targetId = ATTR(transition, "target"); std::list targetIds = tokenize(ATTR(transition, "target")); for (auto targetIter = targetIds.begin(); targetIter != targetIds.end(); targetIter++) { DOMElement* state = getState(*targetIter, root); if (state) { targetStates.push_back(state); } } return targetStates; } DOMElement* getTransitionDomain(const DOMElement* transition, const DOMElement* root) { std::list tStates = getTargetStates(transition, root); if (tStates.size() == 0) { return NULL; } std::string transitionType = (HAS_ATTR(transition, "type") ? ATTR(transition, "type") : "external"); DOMElement* source = getSourceState(transition); if (iequals(transitionType, "internal") && isCompound(source)) { for (auto tIter = tStates.begin(); tIter != tStates.end(); tIter++) { if (!DOMUtils::isDescendant(*tIter, source)) goto BREAK_LOOP; } return source; } BREAK_LOOP: tStates.push_front(source); return findLCCA(tStates); } std::list getStates(const std::list& stateIds, const DOMElement* root) { std::list states; std::list::const_iterator tokenIter = stateIds.begin(); while(tokenIter != stateIds.end()) { states.push_back(getState(*tokenIter, root)); tokenIter++; } return states; } DOMElement* getState(const std::string& stateId, const DOMElement* root) { std::list stateStack; stateStack.push_back(root); while(stateStack.size() > 0) { const DOMElement* curr = stateStack.front(); stateStack.pop_front(); if (!isState(curr, false)) assert(false); // std::cout << *curr; if (HAS_ATTR(curr, "id") && ATTR(curr, "id") == stateId) return (DOMElement*)curr; std::list children = getChildStates(curr, false); stateStack.insert(stateStack.end(), children.begin(), children.end()); } return NULL; } /** * In a conformant SCXML document, a compound state may specify either an "initial" * attribute or an element, but not both. See 3.6 for a * discussion of the difference between the two notations. If neither the "initial" * attribute nor an element is specified, the SCXML Processor must use * the first child state in document order as the default initial state. */ std::list getInitialStates(const DOMElement* state, const DOMElement* root) { if (!state) { state = root; } #if VERBOSE std::cerr << "Getting initial state of " << TAGNAME(state) << " " << ATTR(state, "id") << std::endl; #endif if (isAtomic(state)) { return std::list(); } if (isParallel(state)) { return getChildStates(state); } if (isCompound(state)) { // initial attribute at element if (HAS_ATTR(state, "initial")) { return getStates(tokenize(ATTR(state, "initial")), root); } // initial element as child std::list initElems = DOMUtils::filterChildElements(XML_PREFIX(state).str() + "initial", state); if(initElems.size() > 0 ) { std::list initTrans = DOMUtils::filterChildElements(XML_PREFIX(initElems.front()).str() + "transition", initElems.front()); if (initTrans.size() > 0 && HAS_ATTR(initTrans.front(),"target")) { return getTargetStates(initTrans.front(), root); } return std::list(); } // first child state std::list initStates; DOMNodeList* children = state->getChildNodes(); for (size_t i = 0; i < children->getLength(); i++) { if (children->item(i)->getNodeType() != DOMNode::ELEMENT_NODE) continue; DOMElement* childElem = dynamic_cast(children->item(i)); if (isState(childElem)) { initStates.push_back(childElem); return initStates; } } } // nothing found return std::list(); } std::list getReachableStates(const DOMElement* root) { /** Check which states are reachable */ std::list reachable; // total transitive hull std::list additions; // nodes added in last iteration std::list current; // new nodes caused by nodes added additions.push_back((DOMElement*)root); while (additions.size() > 0) { #if 0 for (auto stateIter = additions.begin(); stateIter != additions.end(); stateIter++) { DOMElement* state = *stateIter; std::cout << (HAS_ATTR(state, "id") ? ATTR(state, "id") : (std::string)X(state->getLocalName())) << ", " << std::endl; } #endif // reachable per initial attribute or document order - size will increase as we append new states for (auto stateIter = additions.begin(); stateIter != additions.end(); stateIter++) { // get the state's initial states DOMElement* state = *stateIter; std::list initials = getInitialStates(state, root); for (auto initIter = initials.begin(); initIter != initials.end(); initIter++) { DOMElement* initial = *initIter; if (!DOMUtils::isMember(initial, additions) && !DOMUtils::isMember(initial, reachable)) { current.push_back(initial); } } } // reachable per target attribute in transitions for (auto stateIter = additions.begin(); stateIter != additions.end(); stateIter++) { DOMElement* state = *stateIter; std::list transitions = DOMUtils::filterChildElements(XML_PREFIX(state).str() + "transition", state, false); for (auto transIter = transitions.begin(); transIter != transitions.end(); transIter++) { DOMElement* transition = *transIter; std::list targets = getTargetStates(transition, root); for (auto targetIter = targets.begin(); targetIter != targets.end(); targetIter++) { DOMElement* target = *targetIter; if (!DOMUtils::isMember(target, additions) && !DOMUtils::isMember(target, reachable)) { current.push_back(target); } } } } // reachable via a reachable child state for (auto stateIter = additions.begin(); stateIter != additions.end(); stateIter++) { DOMElement* state = *stateIter; if (isAtomic(state)) { // iterate the states parents DOMNode* parent = state->getParentNode(); while(parent && parent->getNodeType() == DOMNode::ELEMENT_NODE) { DOMElement* parentElem = static_cast(parent); if (!isState(parentElem)) { break; } if (!DOMUtils::isMember(parentElem, additions) && !DOMUtils::isMember(parentElem, reachable)) { current.push_back(parentElem); } parent = parent->getParentNode(); } } } // add all additions from last iterations to reachable set reachable.insert(reachable.end(), additions.begin(), additions.end()); // set current additions as new additions additions = current; // clear current set for next iteration current.clear(); } return reachable; } bool isInEmbeddedDocument(const DOMNode* node) { // a node is in an embedded document if there is a content element in its parents const DOMNode* parent = node; while(parent) { if(iequals(LOCALNAME(parent), "content")) { return true; } parent = parent->getParentNode(); } return false; } }