/** * @file * @author 2012-2013 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 "uscxml/Common.h" #include "SCXMLDotWriter.h" #include "uscxml/DOMUtils.h" #include // replace_all #include namespace uscxml { using namespace Arabica::DOM; SCXMLDotWriter::SCXMLDotWriter() { _iteration = 0; _indentation = 0; } SCXMLDotWriter::SCXMLDotWriter(Interpreter interpreter, const Arabica::XPath::NodeSet& transitions) { _interpreter = interpreter; _transitions = transitions; _iteration = 0; _indentation = 0; } SCXMLDotWriter::~SCXMLDotWriter() { } void SCXMLDotWriter::onStableConfiguration(Interpreter interpreter) { std::ostringstream fileSS; fileSS << interpreter.getName() << "." << std::setw(6) << std::setfill('0') << _iteration++ << ".dot"; toDot(fileSS.str(), interpreter); } void SCXMLDotWriter::afterCompletion(Interpreter interpreter) { std::ostringstream fileSS; fileSS << interpreter.getName() << "." << std::setw(6) << std::setfill('0') << _iteration++ << ".dot"; toDot(fileSS.str(), interpreter); } void SCXMLDotWriter::beforeMicroStep(Interpreter interpreter) { // std::ostringstream fileSS; // fileSS << interpreter.getName() << "." << std::setw(6) << std::setfill('0') << _iteration++ << ".dot"; // toDot(fileSS.str(), interpreter); } void SCXMLDotWriter::beforeTakingTransitions(Interpreter interpreter, const Arabica::XPath::NodeSet& transitions) { std::ostringstream fileSS; fileSS << interpreter.getName() << "." << std::setw(6) << std::setfill('0') << _iteration++ << ".dot"; toDot(fileSS.str(), interpreter, transitions); } 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, const Arabica::XPath::NodeSet& transitions) { std::ofstream outfile(filename.c_str()); NodeList scxmlElems = interpreter.getDocument().getElementsByTagName("scxml"); SCXMLDotWriter writer(interpreter, transitions); if (scxmlElems.getLength() > 0) { writer._indentation++; outfile << "digraph {" << std::endl; outfile << "rankdir=TB; fontsize=10;" << std::endl; writer.writeSCXMLElement(outfile, (Arabica::DOM::Element)scxmlElems.item(0)); writer._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 << "fontsize=10,"; os << "label=<State
" << nameForNode(elem) << ">,"; // is the state initial? if (_interpreter.isInitial(elem)) os << "style=filled, fillcolor=lightgrey, "; // is this state final? if (_interpreter.isFinal(elem)) os << "shape=doublecircle,"; // is the current state in the basic configuration? if (_interpreter.isMember(elem, _interpreter.getBasicConfiguration())) os << "color=red, penwidth=3,"; // is the current state a target state? #if 0 for (int i = 0; i < _transitions.size(); i++) { if (_interpreter.isMember(elem, _interpreter.getTargetStates(_transitions[i]))) { os << "color=red, penwidth=3,"; break; } } #endif os << "];" << std::endl; std::string details = getDetailedLabel(elem); // std::cout << details << std::endl; if (details.size() > 0) { os << getPrefix() << "\"" << elemId << "Exec\"["; os << "fontsize=8,"; 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 && iequals(TAGNAME(childElems.item(i)), "transition")) { writeTransitionElement(os, (Arabica::DOM::Element)childElems.item(i)); bool active = Interpreter::isMember(childElems.item(i), _transitions); os << getPrefix() << "\"" << elemId << "\" -> \"" << idForNode(childElems.item(i)) << "\" [arrowhead=none" << std::endl; if (active) { os << ", penwidth=3, color=red]" << std::endl; } else { os << "]" << 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 && 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 && 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); bool active = Interpreter::isMember(elem, _transitions); std::string label; os << getPrefix() << "\"" << elemId << "\"["; if (active) { os << "color=red, penwidth=3, "; } 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: " << dotEscape(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]) << "\""; if (active) { os << " [penwidth=3, color=red]" << std::endl; } else { os << 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)) || iequals(TAGNAME(childElems.item(i)), "transition") || 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 (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 (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 (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 (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 (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()); } } } // if --------- if (iequals(TAGNAME(childElems.item(i)), "if")) { if (HAS_ATTR(childElems.item(i), "cond")) details.name += " cond = " + dotEscape(ATTR(childElems.item(i), "cond")); } // elseif --------- if (iequals(TAGNAME(childElems.item(i)), "elseif")) { if (HAS_ATTR(childElems.item(i), "cond")) details.name += " cond = " + dotEscape(ATTR(childElems.item(i), "cond")); } // log --------- if (iequals(TAGNAME(childElems.item(i)), "log")) { details.name += " "; if (HAS_ATTR(childElems.item(i), "label")) details.name += ATTR(childElems.item(i), "label") + " = "; if (HAS_ATTR(childElems.item(i), "expr")) details.name += ATTR(childElems.item(i), "expr"); } // foreach --------- if (iequals(TAGNAME(childElems.item(i)), "foreach")) { if (HAS_ATTR(childElems.item(i), "item")) details.name += "
  item = " + ATTR(childElems.item(i), "item"); if (HAS_ATTR(childElems.item(i), "array")) details.name += "
  array = " + ATTR(childElems.item(i), "array"); if (HAS_ATTR(childElems.item(i), "index")) details.name += "
  index = " + ATTR(childElems.item(i), "index"); } // 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, " ", " "); boost::replace_all(escaped, "\t", "   "); boost::replace_all(escaped, "<", "<"); boost::replace_all(escaped, ">", ">"); boost::replace_all(escaped, "\"", """); boost::replace_all(escaped, "\n", "
"); 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; // try to get the id as the name or id attribute 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"); } } // no luck, create id from position in tree 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(), '-', '_'); return elemId; } }