#include "uscxml/Common.h" #include "SCXMLDotWriter.h" #include "uscxml/Interpreter.h" #include // replace_all namespace uscxml { using namespace Arabica::DOM; int SCXMLDotWriter::_indentation = 0; SCXMLDotWriter::SCXMLDotWriter(Interpreter* interpreter) { _interpreter = interpreter; } SCXMLDotWriter::~SCXMLDotWriter() { } std::string SCXMLDotWriter::getPrefix() { std::string prefix = ""; for (int i = 0; i < _indentation; i++) prefix += " "; return prefix; } void SCXMLDotWriter::toDot(const std::string& filename, Interpreter* interpreter) { std::ofstream outfile(filename.c_str()); NodeList scxmlElems = interpreter->getDocument().getElementsByTagName("scxml"); SCXMLDotWriter writer(interpreter); if (scxmlElems.getLength() > 0) { _indentation++; outfile << "digraph {" << std::endl; outfile << "rankdir=LR;" << std::endl; writer.writeSCXMLElement(outfile, (Arabica::DOM::Element)scxmlElems.item(0)); _indentation--; outfile << "}" << std::endl; } } void SCXMLDotWriter::writeSCXMLElement(std::ostream& os, const Arabica::DOM::Element& elem) { writeStateElement(os, elem); // std::string elemId = idForNode(elem); // os << getPrefix() << "subgraph \"cluster" << elemId.substr(1, elemId.length() - 1) << " {" << std::endl; // _indentation++; // os << getPrefix() << "label=\"" << nameForNode(elem) << "\"" << std::endl; // writeStateElement(os, (Arabica::DOM::Element)_interpreter->getInitialState()); // os << getPrefix() << "} " << std::endl; } void SCXMLDotWriter::writeStateElement(std::ostream& os, const Arabica::DOM::Element& elem) { std::string elemId = idForNode(elem); NodeList childElems = elem.getChildNodes(); if (_knownIds.find(elemId) != _knownIds.end()) return; _knownIds.insert(elemId); bool subgraph = Interpreter::isCompound(elem) || Interpreter::isParallel(elem); if (subgraph) { _indentation++; os << getPrefix() << "subgraph \"cluster_" << elemId << "\" {" << std::endl; os << getPrefix() << "label=\"" << nameForNode(elem) << "\\l\"" << std::endl; } os << getPrefix() << "\"" << elemId << "\"["; os << "label=<State
" << nameForNode(elem) << ">,"; if (_interpreter->isInitial(elem)) os << "style=filled,"; if (_interpreter->isFinal(elem)) os << "shape=doublecircle,"; os << "];" << std::endl; std::string details = getDetailedLabel(elem); // std::cout << details << std::endl; if (details.size() > 0) { os << getPrefix() << "\"" << elemId << "Exec\"["; // os << "fontsize=10,"; os << "shape=box,"; os << "color=grey,"; os << "label=<" << details << ">"; os << "]" << std::endl; os << getPrefix() << "\"" << elemId << "\" -> \"" << elemId << "Exec\" [arrowhead=none, color=grey]" << std::endl; } // NodeList childElems = elem.getChildNodes(); // for (int i = 0; i < childElems.getLength(); i++) { // if (Interpreter::isState(childElems.item(i))) { // writeStateElement(os, (Arabica::DOM::Element)childElems.item(i)); // } // } for (int i = 0; i < childElems.getLength(); i++) { if (childElems.item(i).getNodeType() == Node_base::ELEMENT_NODE && boost::iequals(TAGNAME(childElems.item(i)), "transition")) { writeTransitionElement(os, (Arabica::DOM::Element)childElems.item(i)); os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(childElems.item(i)) << "\"" << std::endl; } if (Interpreter::isState(childElems.item(i))) { writeStateElement(os, (Arabica::DOM::Element)childElems.item(i)); } if (childElems.item(i).getNodeType() == Node_base::ELEMENT_NODE && boost::iequals(TAGNAME(childElems.item(i)), "initial")) { NodeList grandChildElems = childElems.item(i).getChildNodes(); for (int j = 0; j < grandChildElems.getLength(); j++) { if (grandChildElems.item(j).getNodeType() == Node_base::ELEMENT_NODE && boost::iequals(TAGNAME(grandChildElems.item(j)), "transition")) { writeTransitionElement(os, (Arabica::DOM::Element)grandChildElems.item(j)); os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(grandChildElems.item(j)) << "\"" << std::endl; } } } } if (subgraph) { _indentation--; os << getPrefix() << "} " << std::endl; } } void SCXMLDotWriter::writeTransitionElement(std::ostream& os, const Arabica::DOM::Element& elem) { std::string elemId = idForNode(elem); Arabica::XPath::NodeSet targetStates = _interpreter->getTargetStates(elem); std::string label; os << getPrefix() << "\"" << elemId << "\"["; // os << "fontsize=10,"; os << "shape=box,"; os << "label=<Transition
"; if (HAS_ATTR(elem, "event")) os << "event: " << ATTR(elem, "event"); if (HAS_ATTR(elem, "cond")) os << "cond: " << ATTR(elem, "cond"); if (!HAS_ATTR(elem, "cond") && !HAS_ATTR(elem, "event")) os << "unconditional"; os << ">"; os << "]" << std::endl; for (int i = 0; i < targetStates.size(); i++) { os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(targetStates[i]) << "\"" << std::endl; writeStateElement(os, (Arabica::DOM::Element)targetStates[i]); } } std::string SCXMLDotWriter::getDetailedLabel(const Arabica::DOM::Element& elem, int indentation) { /*
onEntry
Details Nested Content
*/ std::list content; NodeList childElems = elem.getChildNodes(); for (int i = 0; i < childElems.getLength(); i++) { if (childElems.item(i).getNodeType() != Node_base::ELEMENT_NODE) continue; if (Interpreter::isState(childElems.item(i)) || boost::iequals(TAGNAME(childElems.item(i)), "transition") || boost::iequals(TAGNAME(childElems.item(i)), "initial") || false) continue; struct ElemDetails details; details.name = "" + TAGNAME(childElems.item(i)) + ""; // provide details for special elements here // param --------- if (boost::iequals(TAGNAME(childElems.item(i)), "param")) { if (HAS_ATTR(childElems.item(i), "name")) details.name += " " + ATTR(childElems.item(i), "name") + " = "; if (HAS_ATTR(childElems.item(i), "expr")) details.name += ATTR(childElems.item(i), "expr"); if (HAS_ATTR(childElems.item(i), "location")) details.name += ATTR(childElems.item(i), "location"); } // data --------- if (boost::iequals(TAGNAME(childElems.item(i)), "data")) { if (HAS_ATTR(childElems.item(i), "id")) details.name += " " + ATTR(childElems.item(i), "id") + " = "; if (HAS_ATTR(childElems.item(i), "src")) details.name += ATTR(childElems.item(i), "src"); if (HAS_ATTR(childElems.item(i), "expr")) details.name += ATTR(childElems.item(i), "expr"); NodeList grandChildElems = childElems.item(i).getChildNodes(); for (int j = 0; j < grandChildElems.getLength(); j++) { if (grandChildElems.item(j).getNodeType() == Node_base::TEXT_NODE) { details.name += dotEscape(grandChildElems.item(j).getNodeValue()); } } } // invoke --------- if (boost::iequals(TAGNAME(childElems.item(i)), "invoke")) { if (HAS_ATTR(childElems.item(i), "type")) details.name += "
type = " + ATTR(childElems.item(i), "type"); if (HAS_ATTR(childElems.item(i), "typeexpr")) details.name += "
type = " + ATTR(childElems.item(i), "typeexpr"); if (HAS_ATTR(childElems.item(i), "src")) details.name += "
src = " + ATTR(childElems.item(i), "src"); if (HAS_ATTR(childElems.item(i), "srcexpr")) details.name += "
src = " + ATTR(childElems.item(i), "srcexpr"); if (HAS_ATTR(childElems.item(i), "id")) details.name += "
id = " + ATTR(childElems.item(i), "id"); if (HAS_ATTR(childElems.item(i), "idlocation")) details.name += "
id = " + ATTR(childElems.item(i), "idlocation"); } // send --------- if (boost::iequals(TAGNAME(childElems.item(i)), "send")) { if (HAS_ATTR(childElems.item(i), "type")) details.name += "
type = " + ATTR(childElems.item(i), "type"); if (HAS_ATTR(childElems.item(i), "typeexpr")) details.name += "
type = " + ATTR(childElems.item(i), "typeexpr"); if (HAS_ATTR(childElems.item(i), "event")) details.name += "
event = " + ATTR(childElems.item(i), "event"); if (HAS_ATTR(childElems.item(i), "eventexpr")) details.name += "
event = " + ATTR(childElems.item(i), "eventexpr"); if (HAS_ATTR(childElems.item(i), "target")) details.name += "
target = " + ATTR(childElems.item(i), "target"); if (HAS_ATTR(childElems.item(i), "targetexpr")) details.name += "
target = " + ATTR(childElems.item(i), "targetexpr"); if (HAS_ATTR(childElems.item(i), "delay")) details.name += "
delay = " + ATTR(childElems.item(i), "delay"); if (HAS_ATTR(childElems.item(i), "delayexpr")) details.name += "
delay = " + ATTR(childElems.item(i), "delayexpr"); } // script --------- if (boost::iequals(TAGNAME(childElems.item(i)), "script")) { details.name += " "; if (HAS_ATTR(childElems.item(i), "src")) details.name += ATTR(childElems.item(i), "src"); NodeList grandChildElems = childElems.item(i).getChildNodes(); for (int j = 0; j < grandChildElems.getLength(); j++) { if (grandChildElems.item(j).getNodeType() == Node_base::TEXT_NODE) { details.name += dotEscape(grandChildElems.item(j).getNodeValue()); } } } // recurse details.content = getDetailedLabel((Arabica::DOM::Element)childElems.item(i), indentation + 1); content.push_back(details); } std::stringstream ssContent; if (content.size() > 0) { ssContent << ""; std::list::iterator contentIter = content.begin(); while(contentIter != content.end()) { ssContent << ""; // ssContent << ""; ssContent << ""; ssContent << ""; if (contentIter->content.size() > 0) { ssContent << ""; // ssContent << ""; ssContent << ""; ssContent << ""; } contentIter++; } ssContent << "
" << contentIter->name << "" << contentIter->name << "
" << contentIter->details << "" << contentIter->content << "
"; } return ssContent.str(); } std::string SCXMLDotWriter::dotEscape(const std::string& text) { std::string escaped(text); boost::replace_all(escaped, "", ""); return escaped; } std::string SCXMLDotWriter::colorForIndent(int indent) { int color = 255 - (16 * indent); std::stringstream ss; ss << std::hex << color; ss << std::hex << color; ss << std::hex << color; return ss.str(); } std::string SCXMLDotWriter::nameForNode(const Arabica::DOM::Node& node) { std::string elemName; if (node.getNodeType() == Node_base::ELEMENT_NODE) { Arabica::DOM::Element elem = (Arabica::DOM::Element)node; if (elem.hasAttribute("name")) { elemName = elem.getAttribute("name"); } else if (elem.hasAttribute("id")) { elemName = elem.getAttribute("id"); } } if (elemName.size() == 0) elemName = boost::lexical_cast(node.getLocalName()); return elemName; } std::string SCXMLDotWriter::idForNode(const Arabica::DOM::Node& node) { std::string elemId; if (node.getNodeType() == Node_base::ELEMENT_NODE) { Arabica::DOM::Element elem = (Arabica::DOM::Element)node; if (elem.hasAttribute("name")) { elemId = elem.getAttribute("name"); } else if (elem.hasAttribute("id")) { elemId = elem.getAttribute("id"); } } if (elemId.size() == 0) { Arabica::DOM::Node tmpParent = node; Arabica::DOM::Node tmpIndex; do { if (tmpParent.getNodeType() != Node_base::ELEMENT_NODE) continue; tmpIndex = tmpParent; int index = 0; while((tmpIndex = tmpIndex.getPreviousSibling())) index++; std::stringstream ssElemId; ssElemId << TAGNAME(tmpParent) << index << "."; elemId = ssElemId.str() + elemId; } while ((tmpParent = tmpParent.getParentNode())); // elemId = ssElemId.str(); } std::replace(elemId.begin(), elemId.end(), '-', '_'); // std::replace(elemId.begin(), elemId.end(), '.', '_'); return elemId; } }