diff options
Diffstat (limited to 'src/qscxml/scxmlparser.cpp')
-rw-r--r-- | src/qscxml/scxmlparser.cpp | 1599 |
1 files changed, 1599 insertions, 0 deletions
diff --git a/src/qscxml/scxmlparser.cpp b/src/qscxml/scxmlparser.cpp new file mode 100644 index 0000000..50ac13f --- /dev/null +++ b/src/qscxml/scxmlparser.cpp @@ -0,0 +1,1599 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "scxmlparser.h" +#include "executablecontent_p.h" +#include "nulldatamodel.h" +#include "ecmascriptdatamodel.h" +#include <QXmlStreamReader> +#include <QLoggingCategory> +#include <QState> +#include <QHistoryState> +#include <QEventTransition> +#include <QSignalTransition> +#include <QJsonDocument> +#include <QJsonObject> +#include <QFile> +#include <QFileInfo> +#include <QDir> +#include <QVector> +#include <private/qabstracttransition_p.h> + +#include <typeinfo> + +namespace Scxml { + +static QString scxmlNamespace = QStringLiteral("http://www.w3.org/2005/07/scxml"); +static QString qtScxmlNamespace = QStringLiteral("http://theqtcompany.com/scxml/2015/06/"); + +class ScxmlVerifier: public DocumentModel::NodeVisitor +{ +public: + ScxmlVerifier(std::function<void (const DocumentModel::XmlLocation &, const QString &)> errorHandler) + : m_errorHandler(errorHandler) + {} + + bool verify(DocumentModel::ScxmlDocument *doc) + { + if (doc->isVerified) + return true; + + doc->isVerified = true; + m_doc = doc; + foreach (DocumentModel::AbstractState *state, doc->allStates) { + if (state->id.isEmpty()) { + continue; +#ifndef QT_NO_DEBUG + } else if (m_stateById.contains(state->id)) { + Q_ASSERT(!"Should be unreachable: the parser should check for this case!"); +#endif // QT_NO_DEBUG + } else { + m_stateById[state->id] = state; + } + } + + doc->root->accept(this); + return !m_hasErrors; + } + +private: + bool visit(DocumentModel::Scxml *scxml) Q_DECL_OVERRIDE + { + Q_ASSERT(scxml->initialStates.isEmpty()); + + if (scxml->initial.isEmpty()) { + if (auto firstChild = firstAbstractState(scxml)) { + scxml->initialStates.append(firstChild); + } + } else { + foreach (const QString &initial, scxml->initial) { + if (DocumentModel::AbstractState *s = m_stateById.value(initial)) + scxml->initialStates.append(s); + else + error(scxml->xmlLocation, QStringLiteral("initial state '%1' not found for <scxml> element").arg(initial)); + } + } + + m_parentNodes.append(scxml); + + return true; + } + + void endVisit(DocumentModel::Scxml *) Q_DECL_OVERRIDE + { + m_parentNodes.removeLast(); + } + + bool visit(DocumentModel::State *state) Q_DECL_OVERRIDE + { + Q_ASSERT(state->initialState == nullptr); + + if (state->initial.isEmpty()) { + state->initialState = firstAbstractState(state); + } else { + Q_ASSERT(state->type == DocumentModel::State::Normal); + if (DocumentModel::AbstractState *s = m_stateById.value(state->initial)) { + state->initialState = s; + } else { + error(state->xmlLocation, QStringLiteral("undefined initial state '%1' for state '%2'").arg(state->initial, state->id)); + } + } + + switch (state->type) { + case DocumentModel::State::Normal: + break; + case DocumentModel::State::Parallel: + if (!state->initial.isEmpty()) { + error(state->xmlLocation, QStringLiteral("parallel states cannot have an initial state")); + } + break; + case DocumentModel::State::Initial: + if (transitionCount(state) != 1) + error(state->xmlLocation, QStringLiteral("an initial state can only have one transition, but has '%1'").arg(transitionCount(state))); + if (DocumentModel::Transition *t = firstTransition(state)) { + if (!t->events.isEmpty() || !t->condition.isNull()) { + error(t->xmlLocation, QStringLiteral("the transition in an initial state cannot have an event or a condition")); + } + if (t->targets.isEmpty()) { + error(t->xmlLocation, QStringLiteral("the transition in an initial state must have at least one target")); + } + } + foreach (DocumentModel::StateOrTransition *child, state->children) { + if (DocumentModel::State *s = child->asState()) { + error(s->xmlLocation, QStringLiteral("substates are not allowed in initial states")); + } + } + if (parentState() == nullptr) { + error(state->xmlLocation, QStringLiteral("initial states can only occur in a state")); + } + break; + case DocumentModel::State::Final: + break; + default: + Q_UNREACHABLE(); + } + + m_parentNodes.append(state); + return true; + } + + void endVisit(DocumentModel::State *) Q_DECL_OVERRIDE + { + m_parentNodes.removeLast(); + } + + bool visit(DocumentModel::Transition *transition) Q_DECL_OVERRIDE + { + Q_ASSERT(transition->targetStates.isEmpty()); + + if (int size = transition->targets.size()) + transition->targetStates.reserve(size); + foreach (const QString &target, transition->targets) { + if (DocumentModel::AbstractState *s = m_stateById.value(target)) { + if (transition->targetStates.contains(s)) { + error(transition->xmlLocation, QStringLiteral("duplicate target '%1'").arg(target)); + } else { + transition->targetStates.append(s); + } + } else if (!target.isEmpty()) { + error(transition->xmlLocation, QStringLiteral("unknown state '%1' in target").arg(target)); + } + } + + m_parentNodes.append(transition); + return true; + } + + void endVisit(DocumentModel::Transition *) Q_DECL_OVERRIDE + { + m_parentNodes.removeLast(); + } + + bool visit(DocumentModel::HistoryState *state) Q_DECL_OVERRIDE + { + bool seenTransition = false; + foreach (DocumentModel::StateOrTransition *sot, state->children) { + if (DocumentModel::State *s = sot->asState()) { + error(s->xmlLocation, QStringLiteral("history state cannot have substates")); + } else if (DocumentModel::Transition *t = sot->asTransition()) { + if (seenTransition) { + error(t->xmlLocation, QStringLiteral("history state can only have one transition")); + } else { + seenTransition = true; + m_parentNodes.append(state); + t->accept(this); + m_parentNodes.removeLast(); + } + } + } + + return false; + } + + bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE + { + checkExpr(node->xmlLocation, QStringLiteral("send"), QStringLiteral("eventexpr"), node->eventexpr); + return true; + } + + void visit(DocumentModel::Cancel *node) Q_DECL_OVERRIDE + { + checkExpr(node->xmlLocation, QStringLiteral("cancel"), QStringLiteral("sendidexpr"), node->sendidexpr); + } + + bool visit(DocumentModel::DoneData *node) Q_DECL_OVERRIDE + { + checkExpr(node->xmlLocation, QStringLiteral("donedata"), QStringLiteral("expr"), node->expr); + return false; + } + +private: + static int transitionCount(DocumentModel::State *state) + { + int count = 0; + foreach (DocumentModel::StateOrTransition *child, state->children) { + if (child->asTransition()) + ++count; + } + return count; + } + + static DocumentModel::Transition *firstTransition(DocumentModel::State *state) + { + foreach (DocumentModel::StateOrTransition *child, state->children) { + if (DocumentModel::Transition *t = child->asTransition()) + return t; + } + return nullptr; + } + + static DocumentModel::AbstractState *firstAbstractState(DocumentModel::StateContainer *container) + { + QVector<DocumentModel::StateOrTransition *> children; + if (auto state = container->asState()) + children = state->children; + else if (auto scxml = container->asScxml()) + children = scxml->children; + else + Q_UNREACHABLE(); + foreach (DocumentModel::StateOrTransition *child, children) { + if (DocumentModel::State *s = child->asState()) + return s; + else if (DocumentModel::HistoryState *h = child->asHistoryState()) + return h; + } + return nullptr; + } + + void checkExpr(const DocumentModel::XmlLocation &loc, const QString &tag, const QString &attrName, const QString &attrValue) + { + if (m_doc->root->dataModel == DocumentModel::Scxml::NullDataModel && !attrValue.isEmpty()) { + error(loc, QStringLiteral("%1 in <%2> cannot be used with data model 'null'").arg(attrName, tag)); + } + } + + void error(const DocumentModel::XmlLocation &location, const QString &message) + { + m_hasErrors = true; + if (m_errorHandler) + m_errorHandler(location, message); + } + + DocumentModel::Node *parentState() const + { + for (int i = m_parentNodes.size() - 1; i >= 0; --i) { + if (DocumentModel::State *s = m_parentNodes.at(i)->asState()) + return s; + } + + return nullptr; + } + +private: + std::function<void (const DocumentModel::XmlLocation &, const QString &)> m_errorHandler; + DocumentModel::ScxmlDocument *m_doc; + bool m_hasErrors = false; + QHash<QString, DocumentModel::AbstractState *> m_stateById; + QVector<DocumentModel::Node *> m_parentNodes; +}; + +class StateTableBuilder: public ExecutableContent::Builder +{ + StateTable *m_table = nullptr; + +public: + StateTable *build(DocumentModel::ScxmlDocument *doc) + { + m_table = nullptr; + m_parents.reserve(32); + m_allTransitions.reserve(doc->allTransitions.size()); + m_docStatesToQStates.reserve(doc->allStates.size()); + + doc->root->accept(this); + wireTransitions(); + applyInitialStates(); + + ExecutableContent::DynamicTableData *td = tableData(); + td->setParent(m_table); + m_table->setTableData(td); + + m_parents.clear(); + m_allTransitions.clear(); + m_docStatesToQStates.clear(); + m_currentTransition = nullptr; + + return m_table; + } + +private: + using NodeVisitor::visit; + using ExecutableContent::Builder::createContext; + + bool visit(DocumentModel::Scxml *node) Q_DECL_OVERRIDE + { + m_table = new StateTable; + + switch (node->binding) { + case DocumentModel::Scxml::EarlyBinding: + m_table->setDataBinding(StateTable::EarlyBinding); + break; + case DocumentModel::Scxml::LateBinding: + m_table->setDataBinding(StateTable::LateBinding); + m_bindLate = true; + break; + default: + Q_UNREACHABLE(); + } + + m_table->setName(node->name); + + m_parents.append(m_table); + visit(node->children); + + m_dataElements.append(node->dataElements); + if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { + m_table->setInitialSetup(startNewSequence()); + generate(m_dataElements); + if (node->script) { + node->script->accept(this); + } + visit(&node->initialSetup); + endSequence(); + } + + m_parents.removeLast(); + + foreach (auto initialState, node->initialStates) { + Q_ASSERT(initialState); + m_initialStates.append(qMakePair(m_table, initialState)); + } + + switch (node->dataModel) { + case DocumentModel::Scxml::NullDataModel: + m_table->setDataModel(new NullDataModel(m_table)); + break; + case DocumentModel::Scxml::JSDataModel: + m_table->setDataModel(new EcmaScriptDataModel(m_table)); + break; + default: + Q_UNREACHABLE(); + } + + return false; + } + + bool visit(DocumentModel::State *node) Q_DECL_OVERRIDE + { + QAbstractState *newState = nullptr; + switch (node->type) { + case DocumentModel::State::Normal: { + auto s = new ScxmlState(currentParent()); + newState = s; + if (node->initialState) + m_initialStates.append(qMakePair(s, node->initialState)); + } break; + case DocumentModel::State::Parallel: { + auto s = new ScxmlState(currentParent()); + s->setChildMode(QState::ParallelStates); + newState = s; + } break; + case DocumentModel::State::Initial: { + auto s = new ScxmlState(currentParent()); + currentParent()->setInitialState(s); + newState = s; + } break; + case DocumentModel::State::Final: { + auto s = new ScxmlFinalState(currentParent()); + newState = s; + s->setDoneData(generate(node->doneData)); + } break; + default: + Q_UNREACHABLE(); + } + + newState->setObjectName(node->id); + + m_docStatesToQStates.insert(node, newState); + m_parents.append(newState); + + if (!node->dataElements.isEmpty()) { + if (m_bindLate) { + qobject_cast<ScxmlState *>(newState)->setInitInstructions(startNewSequence()); + generate(node->dataElements); + endSequence(); + } else { + m_dataElements.append(node->dataElements); + } + } + + ExecutableContent::ContainerId onEntry = generate(node->onEntry); + ExecutableContent::ContainerId onExit = generate(node->onExit); + if (ScxmlState *s = qobject_cast<ScxmlState *>(newState)) { + s->setOnEntryInstructions(onEntry); + s->setOnExitInstructions(onExit); + } else if (ScxmlFinalState *f = qobject_cast<ScxmlFinalState *>(newState)) { + f->setOnEntryInstructions(onEntry); + f->setOnExitInstructions(onExit); + } else { + Q_UNREACHABLE(); + } + + visit(node->children); + + m_parents.removeLast(); + return false; + } + + bool visit(DocumentModel::Transition *node) Q_DECL_OVERRIDE + { + QState *parentState = 0; + if (QHistoryState *parent = qobject_cast<QHistoryState*>(m_parents.last())) { + // QHistoryState cannot have an initial transition, only an initial state. + // So, work around that by creating an initial state, and add the transition to that. + parentState = new ScxmlState(parent->parentState()); + parent->setDefaultState(parentState); + } else { + parentState = currentParent(); + } + + auto newTransition = new ScxmlTransition(parentState, toUtf8(node->events)); + if (node->condition) { + auto cond = createEvaluatorBool(QStringLiteral("transition"), QStringLiteral("cond"), *node->condition.data()); + newTransition->setConditionalExpression(cond); + } + + parentState->addTransition(newTransition); + switch (node->type) { + case DocumentModel::Transition::External: + newTransition->setTransitionType(QAbstractTransition::ExternalTransition); + break; + case DocumentModel::Transition::Internal: + newTransition->setTransitionType(QAbstractTransition::InternalTransition); + break; + default: + Q_UNREACHABLE(); + } + + m_allTransitions.insert(newTransition, node); + if (!node->instructionsOnTransition.isEmpty()) { + m_currentTransition = newTransition; + newTransition->setInstructionsOnTransition(startNewSequence()); + visit(&node->instructionsOnTransition); + endSequence(); + m_currentTransition = 0; + } + Q_ASSERT(newTransition->table()); + return false; + } + + bool visit(DocumentModel::HistoryState *state) Q_DECL_OVERRIDE + { + QHistoryState *newState = new QHistoryState(currentParent()); + switch (state->type) { + case DocumentModel::HistoryState::Shallow: + newState->setHistoryType(QHistoryState::ShallowHistory); + break; + case DocumentModel::HistoryState::Deep: + newState->setHistoryType(QHistoryState::DeepHistory); + break; + default: + Q_UNREACHABLE(); + } + + newState->setObjectName(state->id); + m_docStatesToQStates.insert(state, newState); + m_parents.append(newState); + return true; + } + + void endVisit(DocumentModel::HistoryState *) Q_DECL_OVERRIDE + { + m_parents.removeLast(); + } + +private: // Utility methods + static QList<QByteArray> toUtf8(const QStringList &l) + { + QList<QByteArray> res; + foreach (const QString &s, l) + res.append(s.toUtf8()); + return res; + } + + QState *currentParent() const + { + if (m_parents.isEmpty()) + return nullptr; + + QState *parent = qobject_cast<QState*>(m_parents.last()); + Q_ASSERT(parent); + return parent; + } + + void wireTransitions() + { + for (QHash<QAbstractTransition *, DocumentModel::Transition*>::const_iterator i = m_allTransitions.begin(), ei = m_allTransitions.end(); i != ei; ++i) { + QList<QAbstractState *> targets; + targets.reserve(i.value()->targets.size()); + foreach (DocumentModel::AbstractState *targetState, i.value()->targetStates) { + QAbstractState *target = m_docStatesToQStates.value(targetState); + Q_ASSERT(target); + targets.append(target); + } + i.key()->setTargetStates(targets); + } + } + + void applyInitialStates() + { + foreach (const auto &init, m_initialStates) { + Q_ASSERT(init.second); + auto initialState = m_docStatesToQStates.value(init.second); + Q_ASSERT(initialState); + init.first->setInitialState(initialState); + } + } + + QString createContextString(const QString &instrName) const Q_DECL_OVERRIDE + { + if (m_currentTransition) { + QString state; + if (QState *s = m_currentTransition->sourceState()) { + state = QStringLiteral(" of state '%1'").arg(s->objectName()); + } + return QStringLiteral("%1 instruction in transition %2 %3").arg(instrName, m_currentTransition->objectName(), state); + } else { + return QStringLiteral("%1 instruction in state %2").arg(instrName, m_parents.last()->objectName()); + } + } + + QString createContext(const QString &instrName, const QString &attrName, const QString &attrValue) const Q_DECL_OVERRIDE + { + QString location = createContextString(instrName); + return QStringLiteral("%1 with %2=\"%3\"").arg(location, attrName, attrValue); + } + + DataModel *dataModel() const + { return m_table->dataModel(); } + +private: + QVector<QAbstractState *> m_parents; + QHash<QAbstractTransition *, DocumentModel::Transition*> m_allTransitions; + QHash<DocumentModel::AbstractState *, QAbstractState *> m_docStatesToQStates; + QAbstractTransition *m_currentTransition = nullptr; + QVector<QPair<QState *, DocumentModel::AbstractState *>> m_initialStates; + bool m_bindLate = false; + QVector<DocumentModel::DataElement *> m_dataElements; +}; + +ScxmlParser::ScxmlParser(QXmlStreamReader *reader, LoaderFunction loader) + : m_currentParent(0) + , m_currentState(0) + , m_loader(loader) + , m_reader(reader) + , m_state(StartingParsing) +{ } + +QString ScxmlParser::fileName() const +{ + return m_fileName; +} + +void ScxmlParser::setFileName(const QString &fileName) +{ + m_fileName = fileName; +} + +DocumentModel::AbstractState *ScxmlParser::currentParent() const +{ + DocumentModel::AbstractState *parent = m_currentParent->asAbstractState(); + Q_ASSERT(!m_currentParent || parent); + return parent; +} + +void ScxmlParser::parse() +{ + m_doc.reset(new DocumentModel::ScxmlDocument); + m_currentParent = m_doc->root; + m_currentState = m_doc->root; + while (!m_reader->atEnd()) { + QXmlStreamReader::TokenType tt = m_reader->readNext(); + switch (tt) { + case QXmlStreamReader::NoToken: + // The reader has not yet read anything. + continue; + case QXmlStreamReader::Invalid: + // An error has occurred, reported in error() and errorString(). + break; + case QXmlStreamReader::StartDocument: + // The reader reports the XML version number in documentVersion(), and the encoding + // as specified in the XML document in documentEncoding(). If the document is declared + // standalone, isStandaloneDocument() returns true; otherwise it returns false. + break; + case QXmlStreamReader::EndDocument: + // The reader reports the end of the document. + if (!m_stack.isEmpty() || m_state != FinishedParsing) { + addError(QStringLiteral("document finished without a proper scxml item")); + m_state = ParsingError; + } + break; + case QXmlStreamReader::StartElement: + // The reader reports the start of an element with namespaceUri() and name(). Empty + // elements are also reported as StartElement, followed directly by EndElement. + // The convenience function readElementText() can be called to concatenate all content + // until the corresponding EndElement. Attributes are reported in attributes(), + // namespace declarations in namespaceDeclarations(). + { + QStringRef elName = m_reader->name(); + QXmlStreamAttributes attributes = m_reader->attributes(); + if (!m_stack.isEmpty() && (m_stack.last().kind == ParserState::DataElement + || m_stack.last().kind == ParserState::Data)) { + /*switch (m_table->dataModel()) { + case StateTable::None: + break; // error? + case StateTable::Json: + case StateTable::Javascript: + { + ParserState pNew = ParserState(ParserState::DataElement); + QJsonObject obj; + foreach (const QXmlStreamAttribute &attribute, attributes) + obj.insert(QStringLiteral("@").append(attribute.name()), attribute.value().toString()); + pNew.jsonValue = obj; + m_stack.append(pNew); + break; + } + case StateTable::Xml: + { + ParserState pNew = ParserState(ParserState::DataElement); + Q_ASSERT(0); + } + }*/ + break; + } else if (elName == QLatin1String("scxml")) { + m_doc->root = new DocumentModel::Scxml(xmlLocation()); + m_doc->root->xmlLocation = xmlLocation(); + auto scxml = m_doc->root; + if (m_state != StartingParsing || !m_stack.isEmpty()) { + addError(xmlLocation(), QStringLiteral("found scxml tag mid stream")); + m_state = ParsingError; + return; + } else { + m_state = ParsingScxml; + } + if (!checkAttributes(attributes, "version|initial,datamodel,binding,name,classname")) return; + if (m_reader->namespaceUri() != QLatin1String("http://www.w3.org/2005/07/scxml")) { + addError(QStringLiteral("default namespace must be set with xmlns=\"http://www.w3.org/2005/07/scxml\" in the scxml tag")); + return; + } + if (attributes.value(QLatin1String("version")) != QLatin1String("1.0")) { + addError(QStringLiteral("unsupported scxml version, expected 1.0 in scxml tag")); + return; + } + ParserState pNew = ParserState(ParserState::Scxml); + pNew.initialId = attributes.value(QLatin1String("initial")).toUtf8(); + QStringRef datamodel = attributes.value(QLatin1String("datamodel")); + if (datamodel.isEmpty() || datamodel == QLatin1String("null")) { + scxml->dataModel = DocumentModel::Scxml::NullDataModel; + } else if (datamodel == QLatin1String("ecmascript")) { + scxml->dataModel = DocumentModel::Scxml::JSDataModel; + } else { + addError(QStringLiteral("Unsupported data model '%1' in scxml") + .arg(datamodel.toString())); + } + QStringRef binding = attributes.value(QLatin1String("binding")); + if (binding.isEmpty() || binding == QLatin1String("early")) { + scxml->binding = DocumentModel::Scxml::EarlyBinding; + } else if (binding == QLatin1String("late")) { + scxml->binding = DocumentModel::Scxml::LateBinding; + } else { + addError(QStringLiteral("Unsupperted binding type '%1'") + .arg(binding.toString())); + return; + } + QStringRef name = attributes.value(QLatin1String("name")); + if (!name.isEmpty()) { + scxml->name = name.toString(); + } + QStringRef qtClassname = attributes.value(qtScxmlNamespace, QStringLiteral("classname")); + if (!qtClassname.isEmpty()) { + scxml->qtClassname = qtClassname.toString(); + } + m_currentState = m_currentParent = m_doc->root; + pNew.instructionContainer = &m_doc->root->initialSetup; + m_stack.append(pNew); + } else if (elName == QLatin1String("state")) { + if (!checkAttributes(attributes, "|id,initial")) return; + auto newState = m_doc->newState(m_currentParent, DocumentModel::State::Normal, xmlLocation()); + if (!maybeId(attributes, &newState->id)) return; + ParserState pNew = ParserState(ParserState::State); + pNew.initialId = attributes.value(QLatin1String("initial")).toUtf8(); + m_currentState = m_currentParent = newState; + m_stack.append(pNew); + } else if (elName == QLatin1String("parallel")) { + if (!checkAttributes(attributes, "|id")) return; + auto newState = m_doc->newState(m_currentParent, DocumentModel::State::Parallel, xmlLocation()); + if (!maybeId(attributes, &newState->id)) return; + m_currentState = m_currentParent = newState; + m_stack.append(ParserState(ParserState::Parallel)); + } else if (elName == QLatin1String("initial")) { + if (!checkAttributes(attributes, "")) return; + if (currentParent()->asState()->type == DocumentModel::State::Parallel) { + addError(QStringLiteral("Explicit initial state for parallel states not supported (only implicitly through the initial states of its substates)")); + m_state = ParsingError; + return; + } + ParserState pNew(ParserState::Initial); + auto newState = m_doc->newState(m_currentParent, DocumentModel::State::Initial, xmlLocation()); + m_currentState = m_currentParent = newState; + m_stack.append(pNew); + } else if (elName == QLatin1String("transition")) { + if (!checkAttributes(attributes, "|event,cond,target,type")) return; + auto transition = m_doc->newTransition(m_currentParent, xmlLocation()); + transition->events = attributes.value(QLatin1String("event")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts); + transition->targets = attributes.value(QLatin1String("target")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts); + if (attributes.hasAttribute(QStringLiteral("cond"))) + transition->condition.reset(new QString(attributes.value(QLatin1String("cond")).toString())); + QStringRef type = attributes.value(QLatin1String("type")); + if (type.isEmpty() || type == QLatin1String("external")) { + transition->type = DocumentModel::Transition::External; +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + } else if (type == QLatin1String("internal")) { + transition->type = DocumentModel::Transition::Internal; +#endif + } else { + addError(QStringLiteral("invalid transition type '%1', valid values are 'external' and 'internal'").arg(type.toString())); + m_state = ParsingError; + break; + } + ParserState pNew = ParserState(ParserState::Transition); + pNew.instructionContainer = &transition->instructionsOnTransition; + m_stack.append(pNew); + } else if (elName == QLatin1String("final")) { + if (!checkAttributes(attributes, "|id")) return; + auto newState = m_doc->newState(m_currentParent, DocumentModel::State::Final, xmlLocation()); + if (!maybeId(attributes, &newState->id)) return; + m_currentState = m_currentParent = newState; + m_stack.append(ParserState(ParserState::Final)); + } else if (elName == QLatin1String("history")) { + if (!checkAttributes(attributes, "|id,type")) return; + auto newState = m_doc->newHistoryState(currentParent(), xmlLocation()); + if (!maybeId(attributes, &newState->id)) return; + QStringRef type = attributes.value(QLatin1String("type")); + if (type.isEmpty() || type == QLatin1String("shallow")) { + newState->type = DocumentModel::HistoryState::Shallow; + } else if (type == QLatin1String("deep")) { + newState->type = DocumentModel::HistoryState::Deep; + } else { + addError(QStringLiteral("invalid history type %1, valid values are 'shallow' and 'deep'").arg(type.toString())); + m_state = ParsingError; + return; + } + ParserState pNew = ParserState(ParserState::History); + m_currentState = m_currentParent = newState; + m_stack.append(pNew); + } else if (elName == QLatin1String("onentry")) { + if (!checkAttributes(attributes, "")) return; + ParserState pNew(ParserState::OnEntry); + switch (m_stack.last().kind) { + case ParserState::Final: + case ParserState::State: + case ParserState::Parallel: + if (DocumentModel::State *s = m_currentState->asState()) { + pNew.instructionContainer = m_doc->newSequence(&s->onEntry); + break; + } + // intentional fall-through + default: + addError(QStringLiteral("unexpected container state for onentry")); + m_state = ParsingError; + break; + } + m_stack.append(pNew); + } else if (elName == QLatin1String("onexit")) { + if (!checkAttributes(attributes, "")) return; + ParserState pNew(ParserState::OnExit); + switch (m_stack.last().kind) { + case ParserState::Final: + case ParserState::State: + case ParserState::Parallel: + if (DocumentModel::State *s = m_currentState->asState()) { + pNew.instructionContainer = m_doc->newSequence(&s->onExit); + break; + } + // intentional fall-through + default: + addError(QStringLiteral("unexpected container state for onexit")); + m_state = ParsingError; + break; + } + m_stack.append(pNew); + } else if (elName == QLatin1String("raise")) { + if (!checkAttributes(attributes, "event")) return; + ParserState pNew = ParserState(ParserState::Raise); + auto raise = m_doc->newNode<DocumentModel::Raise>(xmlLocation()); + raise->event = attributes.value(QLatin1String("event")).toString(); + pNew.instruction = raise; + m_stack.append(pNew); + } else if (elName == QLatin1String("if")) { + if (!checkAttributes(attributes, "cond")) return; + ParserState pNew = ParserState(ParserState::If); + auto *ifI = m_doc->newNode<DocumentModel::If>(xmlLocation()); + pNew.instruction = ifI; + ifI->conditions.append(attributes.value(QLatin1String("cond")).toString()); + pNew.instructionContainer = m_doc->newSequence(&ifI->blocks); + m_stack.append(pNew); + } else if (elName == QLatin1String("elseif")) { + if (!checkAttributes(attributes, "cond")) return; + DocumentModel::If *ifI = m_stack.last().instruction->asIf(); + Q_ASSERT(ifI); + ifI->conditions.append(attributes.value(QLatin1String("cond")).toString()); + m_stack.last().instructionContainer = m_doc->newSequence(&ifI->blocks); + m_stack.append(ParserState(ParserState::ElseIf)); + } else if (elName == QLatin1String("else")) { + if (!checkAttributes(attributes, "")) return; + DocumentModel::If *ifI = m_stack.last().instruction->asIf(); + Q_ASSERT(ifI); + m_stack.last().instructionContainer = m_doc->newSequence(&ifI->blocks); + m_stack.append(ParserState(ParserState::Else)); + } else if (elName == QLatin1String("foreach")) { + if (!checkAttributes(attributes, "array,item|index")) return; + ParserState pNew = ParserState(ParserState::Foreach); + auto foreachI = m_doc->newNode<DocumentModel::Foreach>(xmlLocation()); + foreachI->array = attributes.value(QLatin1String("array")).toString(); + foreachI->item = attributes.value(QLatin1String("item")).toString(); + foreachI->index = attributes.value(QLatin1String("index")).toString(); + pNew.instruction = foreachI; + pNew.instructionContainer = &foreachI->block; + m_stack.append(pNew); + } else if (elName == QLatin1String("log")) { + if (!checkAttributes(attributes, "|label,expr")) return; + ParserState pNew = ParserState(ParserState::Log); + auto logI = m_doc->newNode<DocumentModel::Log>(xmlLocation()); + logI->label = attributes.value(QLatin1String("label")).toString(); + logI->expr = attributes.value(QLatin1String("expr")).toString(); + pNew.instruction = logI; + m_stack.append(pNew); + } else if (elName == QLatin1String("datamodel")) { + if (!checkAttributes(attributes, "")) return; + m_stack.append(ParserState(ParserState::DataModel)); + } else if (elName == QLatin1String("data")) { + if (!checkAttributes(attributes, "id|src,expr")) return; + auto data = m_doc->newNode<DocumentModel::DataElement>(xmlLocation()); + data->id = attributes.value(QLatin1String("id")).toString(); + data->src = attributes.value(QLatin1String("src")).toString(); + data->expr = attributes.value(QLatin1String("expr")).toString(); + if (!data->src.isEmpty()) { + addError(QStringLiteral("the source attribute in a data tag is unsupported")); // FIXME: use a loader like in <script> + } + if (DocumentModel::Scxml *scxml = m_currentParent->asScxml()) { + scxml->dataElements.append(data); + } else if (DocumentModel::State *state = m_currentParent->asState()) { + state->dataElements.append(data); + } else { + Q_UNREACHABLE(); + } + m_stack.append(ParserState(ParserState::Data)); + } else if (elName == QLatin1String("assign")) { + if (!checkAttributes(attributes, "location|expr")) return; + ParserState pNew = ParserState(ParserState::Assign); + auto assign = m_doc->newNode<DocumentModel::Assign>(xmlLocation()); + assign->location = attributes.value(QLatin1String("location")).toString(); + assign->expr = attributes.value(QLatin1String("expr")).toString(); + pNew.instruction = assign; + m_stack.append(pNew); + } else if (elName == QLatin1String("donedata")) { + if (!checkAttributes(attributes, "")) return; + ParserState pNew = ParserState(ParserState::DoneData); + m_stack.append(pNew); + bool handled = false; + if (DocumentModel::State *s = m_currentState->asState()) { + if (s->type == DocumentModel::State::Final) { + handled = true; + if (s->doneData) { + addError(QLatin1String("state can only have one donedata")); + m_state = ParsingError; + } else { + s->doneData = m_doc->newNode<DocumentModel::DoneData>(xmlLocation()); + } + } + } + if (!handled) { + addError(QStringLiteral("donedata can only occur in a final state")); + m_state = ParsingError; + } + } else if (elName == QLatin1String("content")) { + if (!checkAttributes(attributes, "|expr")) return; + switch (m_stack.last().kind) { + case ParserState::DoneData: { + DocumentModel::State *s = m_currentState->asState(); + Q_ASSERT(s); + s->doneData->expr = attributes.value(QLatin1String("expr")).toString(); + } break; + case ParserState::Send: { + DocumentModel::Send *s = m_stack.last().instruction->asSend(); + Q_ASSERT(s); + s->content = attributes.value(QLatin1String("expr")).toString(); + } break; + default: + addError(QStringLiteral("unexpected parent of content %1").arg(m_stack.last().kind)); + m_state = ParsingError; + } + ParserState pNew = ParserState(ParserState::Content); + m_stack.append(pNew); + } else if (elName == QLatin1String("param")) { + if (!checkAttributes(attributes, "name|expr,location")) return; + ParserState pNew = ParserState(ParserState::Param); + auto param = m_doc->newNode<DocumentModel::Param>(xmlLocation()); + param->name = attributes.value(QLatin1String("name")).toString(); + param->expr = attributes.value(QLatin1String("expr")).toString(); + param->location = attributes.value(QLatin1String("location")).toString(); + switch (m_stack.last().kind) { + case ParserState::DoneData: { + DocumentModel::State *s = m_currentState->asState(); + Q_ASSERT(s); + Q_ASSERT(s->doneData); + s->doneData->params.append(param); + } break; + case ParserState::Send: { + DocumentModel::Send *s = m_stack.last().instruction->asSend(); + Q_ASSERT(s); + s->params.append(param); + } break; + case ParserState::Invoke: { + DocumentModel::Invoke *i = m_stack.last().instruction->asInvoke(); + Q_ASSERT(i); + i->params.append(param); + } break; + default: + addError(QStringLiteral("unexpected parent of param %1").arg(m_stack.last().kind)); + m_state = ParsingError; + } + m_stack.append(pNew); + } else if (elName == QLatin1String("script")) { + if (!checkAttributes(attributes, "|src")) return; + ParserState pNew = ParserState(ParserState::Script); + auto *script = m_doc->newNode<DocumentModel::Script>(xmlLocation()); + script->src = attributes.value(QLatin1String("src")).toString(); + pNew.instruction = script; + m_stack.append(pNew); + } else if (elName == QLatin1String("send")) { + if (!checkAttributes(attributes, "|event,eventexpr,id,idlocation,type,typeexpr,namelist,delay,delayexpr,target,targetexpr")) return; + ParserState pNew = ParserState(ParserState::Send); + auto *send = m_doc->newNode<DocumentModel::Send>(xmlLocation()); + send->event = attributes.value(QLatin1String("event")).toString(); + send->eventexpr = attributes.value(QLatin1String("eventexpr")).toString(); + send->delay = attributes.value(QLatin1String("delay")).toString(); + send->delayexpr = attributes.value(QLatin1String("delayexpr")).toString(); + send->id = attributes.value(QLatin1String("id")).toString(); + send->idLocation = attributes.value(QLatin1String("idlocation")).toString(); + send->type = attributes.value(QLatin1String("type")).toString(); + send->typeexpr = attributes.value(QLatin1String("typeexpr")).toString(); + send->target = attributes.value(QLatin1String("target")).toString(); + send->targetexpr = attributes.value(QLatin1String("targetexpr")).toString(); + if (attributes.hasAttribute(QLatin1String("namelist"))) + send->namelist = attributes.value(QLatin1String("namelist")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts); + pNew.instruction = send; + m_stack.append(pNew); + } else if (elName == QLatin1String("cancel")) { + if (!checkAttributes(attributes, "|sendid,sendidexpr")) return; + ParserState pNew = ParserState(ParserState::Cancel); + auto *cancel = m_doc->newNode<DocumentModel::Cancel>(xmlLocation()); + cancel->sendid = attributes.value(QLatin1String("sendid")).toString(); + cancel->sendidexpr = attributes.value(QLatin1String("sendidexpr")).toString(); + pNew.instruction = cancel; + m_stack.append(pNew); + } else if (elName == QLatin1String("invoke")) { + if (true) { + addError(xmlLocation(), QStringLiteral("<invoke> is not supported")); + m_state = ParsingError; + } else { + if (!checkAttributes(attributes, "|event,eventexpr,id,idlocation,type,typeexpr,namelist,delay,delayexpr")) return; + ParserState pNew = ParserState(ParserState::Invoke); + auto *invoke = m_doc->newNode<DocumentModel::Invoke>(xmlLocation()); + invoke->src = attributes.value(QLatin1String("src")).toString(); + invoke->srcexpr = attributes.value(QLatin1String("srcexpr")).toString(); + invoke->id = attributes.value(QLatin1String("id")).toString(); + invoke->idLocation = attributes.value(QLatin1String("idlocation")).toString(); + invoke->type = attributes.value(QLatin1String("type")).toString(); + invoke->typeexpr = attributes.value(QLatin1String("typeexpr")).toString(); + QStringRef autoforwardS = attributes.value(QLatin1String("autoforward")); + if (QStringRef::compare(autoforwardS, QLatin1String("true"), Qt::CaseInsensitive) == 0 + || QStringRef::compare(autoforwardS, QLatin1String("yes"), Qt::CaseInsensitive) == 0 + || QStringRef::compare(autoforwardS, QLatin1String("t"), Qt::CaseInsensitive) == 0 + || QStringRef::compare(autoforwardS, QLatin1String("y"), Qt::CaseInsensitive) == 0 + || autoforwardS == QLatin1String("1")) + invoke->autoforward = true; + else + invoke->autoforward = false; + invoke->namelist = attributes.value(QLatin1String("namelist")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts); + pNew.instruction = invoke; + m_stack.append(pNew); + } + } else if (elName == QLatin1String("finalize")) { + ParserState pNew(ParserState::Finalize); + auto invoke = m_stack.last().instruction->asInvoke(); + Q_ASSERT(invoke); + pNew.instructionContainer = &invoke->finalize; + m_stack.append(pNew); + } else { + addError(QStringLiteral("unexpected element %1").arg(elName.toString())); + } + if (m_stack.size()>1 && !m_stack.at(m_stack.size()-2).validChild(m_stack.last().kind)) { + addError(QStringLiteral("invalid child")); + m_state = ParsingError; + } + break; + } + case QXmlStreamReader::EndElement: + // The reader reports the end of an element with namespaceUri() and name(). + { + ParserState p = m_stack.last(); + m_stack.removeLast(); + switch (p.kind) { + case ParserState::Scxml: + if (m_state == ParsingScxml) { + m_state = FinishedParsing; + } else { + m_state = ParsingError; + } + return; + case ParserState::State: + case ParserState::Parallel: + case ParserState::Initial: + case ParserState::Final: + case ParserState::History: + Q_ASSERT(m_currentParent->parent); + m_currentState = m_currentParent = m_currentParent->parent; + break; + case ParserState::Transition: + case ParserState::OnEntry: + case ParserState::OnExit: + case ParserState::ElseIf: + case ParserState::Else: + break; + case ParserState::Script: + { + DocumentModel::Script *scriptI = p.instruction->asScript(); + if (!p.chars.trimmed().isEmpty()) { + scriptI->content = p.chars.trimmed(); + if (!scriptI->src.isEmpty()) + addError(QStringLiteral("both scr and source content given to script, will ignore external content")); + } else if (!scriptI->src.isEmpty()) { + if (!m_loader) { + addError(QStringLiteral("cannot parse a document with external dependencies without a loader")); + m_state = ParsingError; + } else { + bool ok; + QByteArray data = m_loader(scriptI->src, ok, this); + if (!ok) { + addError(QStringLiteral("failed to load external dependency")); + m_state = ParsingError; + } else { + scriptI->content = QString::fromUtf8(data); + } + } + } + } // very intentional fallthrough! + case ParserState::Raise: + case ParserState::If: + case ParserState::Foreach: + case ParserState::Log: + case ParserState::Assign: + case ParserState::Send: + case ParserState::Cancel: + case ParserState::Invoke: { + DocumentModel::InstructionSequence *instructions = m_stack.last().instructionContainer; + if (!instructions) { + addError(QStringLiteral("got executable content within an element that did not set instructionContainer")); + m_state = ParsingError; + return; + } + instructions->append(p.instruction); + p.instruction = 0; + break; + } + case ParserState::Finalize: + case ParserState::DataModel: + case ParserState::DataElement: + case ParserState::DoneData: + case ParserState::Param: + case ParserState::None: + break; + case ParserState::Content: + if (!p.chars.trimmed().isEmpty()) { + Q_ASSERT(!m_stack.isEmpty()); + switch (m_stack.last().kind) { + case ParserState::DoneData: // see test529 + m_currentState->asState()->doneData->contents = p.chars.simplified(); + break; + case ParserState::Send: // see test179 + m_stack.last().instruction->asSend()->content = p.chars.simplified(); + break; + default: + break; + } + } + break; + case ParserState::Data: { + DocumentModel::DataElement *data = nullptr; + if (auto state = m_currentParent->asState()) { + data = state->dataElements.last(); + } else if (auto scxml = m_currentParent->asNode()->asScxml()) { + data = scxml->dataElements.last(); + } else { + Q_UNREACHABLE(); + } + if (!data->src.isEmpty() && !data->expr.isEmpty()) { + addError(QStringLiteral("data element with both 'src' and 'expr' attributes")); + m_state = ParsingError; + return; + } + if (!p.chars.trimmed().isEmpty()) { + if (!data->src.isEmpty()) { + addError(QStringLiteral("data element with both 'src' attribute and CDATA")); + m_state = ParsingError; + return; + } else if (!data->expr.isEmpty()) { + addError(QStringLiteral("data element with both 'expr' attribute and CDATA")); + m_state = ParsingError; + return; + } else { + data->expr = p.chars.simplified(); + } + } + } break; + } // parser state + } // QXmlStreamReader::EndElement + case QXmlStreamReader::Characters: + // The reader reports characters in text(). If the characters are all white-space, + // isWhitespace() returns true. If the characters stem from a CDATA section, + // isCDATA() returns true. + if (m_stack.isEmpty()) + break; + if (m_stack.last().collectChars()) + m_stack.last().chars.append(m_reader->text()); + break; + case QXmlStreamReader::Comment: + // The reader reports a comment in text(). + break; + case QXmlStreamReader::DTD: + // The reader reports a DTD in text(), notation declarations in notationDeclarations(), + // and entity declarations in entityDeclarations(). Details of the DTD declaration are + // reported in in dtdName(), dtdPublicId(), and dtdSystemId(). + break; + case QXmlStreamReader::EntityReference: + // The reader reports an entity reference that could not be resolved. The name of + // the reference is reported in name(), the replacement text in text(). + break; + case QXmlStreamReader::ProcessingInstruction: + break; + } + } + if (m_reader->hasError() + && m_reader->error() != QXmlStreamReader::PrematureEndOfDocumentError) { + addError(QStringLiteral("Error parsing scxml file")); + addError(m_reader->errorString()); + m_state = ParsingError; + } +} + +DocumentModel::XmlLocation ScxmlParser::xmlLocation() const +{ + return DocumentModel::XmlLocation(m_reader->lineNumber(), m_reader->columnNumber()); +} + +DocumentModel::ScxmlDocument *ScxmlParser::scxmlDocument() +{ + if (!m_doc) + return nullptr; + + auto handler = [this](const DocumentModel::XmlLocation &location, const QString &msg) { + this->addError(location, msg); + }; + + if (ScxmlVerifier(handler).verify(m_doc.data())) + return m_doc.data(); + else + return nullptr; +} + +StateTable *ScxmlParser::table() +{ + if (DocumentModel::ScxmlDocument *doc = scxmlDocument()) + return StateTableBuilder().build(doc); + else + return nullptr; +} + +void ScxmlParser::addError(const QString &msg, ErrorMessage::Severity severity) +{ + m_errors.append(ErrorMessage(m_fileName, + m_reader->lineNumber(), + m_reader->columnNumber(), + severity, + msg)); + if (severity == ErrorMessage::Error) + m_state = ParsingError; +} + +void ScxmlParser::addError(const DocumentModel::XmlLocation &location, const QString &msg) +{ + m_errors.append(ErrorMessage(m_fileName, + location.line, + location.column, + ErrorMessage::Error, + msg)); + m_state = ParsingError; +} + +bool ScxmlParser::maybeId(const QXmlStreamAttributes &attributes, QString *id) +{ + Q_ASSERT(id); + QString idStr = attributes.value(QLatin1String("id")).toString(); + if (!idStr.isEmpty()) { + if (m_allIds.contains(idStr)) { + addError(xmlLocation(), QStringLiteral("duplicate id '%1'").arg(idStr)); + } else { + m_allIds.insert(idStr); + *id = idStr; + } + } + return true; +} + +bool ScxmlParser::checkAttributes(const QXmlStreamAttributes &attributes, const char *attribStr) +{ + QString allAttrib = QString::fromLatin1(attribStr); + QStringList attrSplit = allAttrib.split(QLatin1Char('|')); + QStringList requiredNames, optionalNames; + requiredNames = attrSplit.value(0).split(QLatin1Char(','), QString::SkipEmptyParts); + optionalNames = attrSplit.value(1).split(QLatin1Char(','), QString::SkipEmptyParts); + if (attrSplit.size() > 2) { + addError(QStringLiteral("Internal error, invalid attribStr in checkAttributes")); + m_state = ParsingError; + } + foreach (const QString &rName, requiredNames) + if (rName.isEmpty()) + requiredNames.removeOne(rName); + foreach (const QString &oName, optionalNames) + if (oName.isEmpty()) + optionalNames.removeOne(oName); + return checkAttributes(attributes, requiredNames, optionalNames); +} + +bool ScxmlParser::checkAttributes(const QXmlStreamAttributes &attributes, QStringList requiredNames, QStringList optionalNames) +{ + foreach (const QXmlStreamAttribute &attribute, attributes) { + QStringRef ns = attribute.namespaceUri(); + if (!ns.isEmpty() && ns != scxmlNamespace && ns != qtScxmlNamespace) { + foreach (const QString &nsToIgnore, m_namespacesToIgnore) { + if (ns == nsToIgnore) + continue; + } + m_namespacesToIgnore << ns.toString(); + addError(QStringLiteral("Ignoring unexpected namespace %1").arg(ns.toString()), + ErrorMessage::Info); + continue; + } + const QString name = attribute.name().toString(); + if (!requiredNames.removeOne(name) && !optionalNames.contains(name)) { + addError(QStringLiteral("Unexpected attribute '%1'").arg(name)); + m_state = ParsingError; + return false; + } + } + if (!requiredNames.isEmpty()) { + addError(QStringLiteral("Missing required attributes: '%1'") + .arg(requiredNames.join(QLatin1String("', '")))); + m_state = ParsingError; + return false; + } + return true; +} + +ScxmlParser::LoaderFunction ScxmlParser::loaderForDir(const QString &basedir) +{ + return [basedir](const QString &path, bool &ok, ScxmlParser *parser) -> QByteArray { + ok = false; + QFileInfo fInfo(path); + if (fInfo.isRelative()) + fInfo = QFileInfo(QDir(basedir).filePath(path)); + if (!fInfo.exists()) { + parser->addError(QStringLiteral("src attribute resolves to non existing file (%1)").arg(fInfo.absoluteFilePath())); + } else { + QFile f(fInfo.absoluteFilePath()); + if (f.open(QFile::ReadOnly)) { + ok = true; + return f.readAll(); + } else { + parser->addError(QStringLiteral("Failure opening file %1: %2") + .arg(fInfo.absoluteFilePath(), f.errorString())); + } + } + return QByteArray(); + }; +} + +bool Scxml::ParserState::collectChars() { + switch (kind) { + case Content: + case Data: + case Script: + return true; + default: + break; + } + return false; +} + +bool ParserState::validChild(ParserState::Kind child) const { + return validChild(kind, child); +} + +bool ParserState::validChild(ParserState::Kind parent, ParserState::Kind child) +{ + switch (parent) { + case ParserState::Scxml: + switch (child) { + case ParserState::State: + case ParserState::Parallel: + case ParserState::Final: + case ParserState::DataModel: + case ParserState::Script: + case ParserState::Transition: + return true; + default: + break; + } + return false; + case ParserState::State: + switch (child) { + case ParserState::OnEntry: + case ParserState::OnExit: + case ParserState::Transition: + case ParserState::Initial: + case ParserState::State: + case ParserState::Parallel: + case ParserState::Final: + case ParserState::History: + case ParserState::DataModel: + case ParserState::Invoke: + return true; + default: + break; + } + return false; + case ParserState::Parallel: + switch (child) { + case ParserState::OnEntry: + case ParserState::OnExit: + case ParserState::Transition: + case ParserState::State: + case ParserState::Parallel: + case ParserState::History: + case ParserState::DataModel: + case ParserState::Invoke: + return true; + default: + break; + } + return false; + case ParserState::Transition: + return isExecutableContent(child); + case ParserState::Initial: + return (child == ParserState::Transition); + case ParserState::Final: + switch (child) { + case ParserState::OnEntry: + case ParserState::OnExit: + case ParserState::DoneData: + return true; + default: + break; + } + return false; + case ParserState::OnEntry: + case ParserState::OnExit: + return isExecutableContent(child); + return false; + case ParserState::History: + return (child == ParserState::Transition); + case ParserState::Raise: + return false; + case ParserState::If: + return (child == ParserState::ElseIf || child == ParserState::Else + || isExecutableContent(child)); + case ParserState::ElseIf: + case ParserState::Else: + return false; + case ParserState::Foreach: + return isExecutableContent(child); + case ParserState::Log: + return false; + case ParserState::DataModel: + return (child == ParserState::Data); + case ParserState::Data: + return (child == ParserState::DataElement); + case ParserState::DataElement: + return (child == ParserState::DataElement); + case ParserState::Assign: + return (child == ParserState::DataElement); + case ParserState::DoneData: + return (child == ParserState::Content || child == ParserState::Param); + case ParserState::Send: + return (child == ParserState::Param || child == ParserState::Content + || isExecutableContent(child)); + case ParserState::Content: + case ParserState::Param: + case ParserState::Cancel: + case ParserState::Invoke: + case ParserState::Finalize: + return isExecutableContent(child); + break; + case ParserState::Script: + case ParserState::None: + break; + } + return false; +} + +bool Scxml::ParserState::isExecutableContent(Scxml::ParserState::Kind kind) { + switch (kind) { + case Raise: + case Send: + case Log: + case Script: + case Assign: + case If: + case Foreach: + case Cancel: + case Invoke: + return true; + default: + break; + } + return false; +} + +DocumentModel::Node::~Node() +{ +} + +void DocumentModel::DataElement::accept(DocumentModel::NodeVisitor *visitor) +{ + visitor->visit(this); +} + +void DocumentModel::Param::accept(DocumentModel::NodeVisitor *visitor) +{ + visitor->visit(this); +} + +void DocumentModel::DoneData::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + foreach (Param *param, params) + param->accept(visitor); + } + visitor->endVisit(this); +} + +void DocumentModel::Send::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + visitor->visit(params); + } + visitor->endVisit(this); +} + +void DocumentModel::Invoke::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + visitor->visit(params); + visitor->visit(&finalize); + } + visitor->endVisit(this); +} + +void DocumentModel::Raise::accept(DocumentModel::NodeVisitor *visitor) +{ + visitor->visit(this); +} + +void DocumentModel::Log::accept(DocumentModel::NodeVisitor *visitor) +{ + visitor->visit(this); +} + +void DocumentModel::Script::accept(DocumentModel::NodeVisitor *visitor) +{ + visitor->visit(this); +} + +void DocumentModel::Assign::accept(DocumentModel::NodeVisitor *visitor) +{ + visitor->visit(this); +} + +void DocumentModel::If::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + visitor->visit(blocks); + } + visitor->endVisit(this); +} + +void DocumentModel::Foreach::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + visitor->visit(&block); + } + visitor->endVisit(this); +} + +void DocumentModel::Cancel::accept(DocumentModel::NodeVisitor *visitor) +{ + visitor->visit(this); +} + +void DocumentModel::State::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + visitor->visit(dataElements); + visitor->visit(children); + visitor->visit(onEntry); + visitor->visit(onExit); + if (doneData) + doneData->accept(visitor); + } + visitor->endVisit(this); +} + +void DocumentModel::Transition::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + visitor->visit(&instructionsOnTransition); + } + visitor->endVisit(this); +} + +void DocumentModel::HistoryState::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + if (Transition *t = defaultConfiguration()) + t->accept(visitor); + } + visitor->endVisit(this); +} + +void DocumentModel::Scxml::accept(DocumentModel::NodeVisitor *visitor) +{ + if (visitor->visit(this)) { + visitor->visit(children); + visitor->visit(dataElements); + if (script) + script->accept(visitor); + visitor->visit(&initialSetup); + } + visitor->endVisit(this); +} + +DocumentModel::NodeVisitor::~NodeVisitor() +{} + +} // namespace Scxml |