summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJarek Kobus <jaroslaw.kobus@theqtcompany.com>2016-05-04 16:02:06 +0200
committerJarek Kobus <jaroslaw.kobus@qt.io>2016-05-20 11:09:03 +0000
commit3277abece0c1c258cd2fcbdb3044b9c64b0ccfef (patch)
treed57c11c39fc10e735fc3d731940b8fc5dafd7ff3
parent6b1884cac1e54b6aa6fd19daf354ee07eeb6379b (diff)
Refactor parser, parse recursively now
Splits the parsering function into several smaller functions per element kind. Fixes parsing of 11 scion tests. Fixes parsing of non-terminated comment inside <script> element. Change-Id: I1d82c3164415a71e69e90bc6d3b96bda67dabb6c Reviewed-by: Erik Verbruggen <erik.verbruggen@qt.io>
-rw-r--r--src/scxml/qscxmlparser.cpp1555
-rw-r--r--src/scxml/qscxmlparser.h8
-rw-r--r--src/scxml/qscxmlparser_p.h69
-rw-r--r--tests/auto/qscxmlc/data/commentInScript.scxml19
-rw-r--r--tests/auto/qscxmlc/tst_qscxmlc.qrc1
-rw-r--r--tests/auto/scion/tst_scion.cpp61
6 files changed, 1078 insertions, 635 deletions
diff --git a/src/scxml/qscxmlparser.cpp b/src/scxml/qscxmlparser.cpp
index 04c7828..b7e8924 100644
--- a/src/scxml/qscxmlparser.cpp
+++ b/src/scxml/qscxmlparser.cpp
@@ -1488,7 +1488,7 @@ void QScxmlParser::setLoader(QScxmlParser::Loader *newLoader)
*/
void QScxmlParser::parse()
{
- d->parse();
+ d->readDocument();
d->verifyDocument();
}
@@ -1548,14 +1548,6 @@ void QScxmlParser::instantiateDataModel(QScxmlStateMachine *stateMachine) const
}
/*!
- * Returns the current parser state.
- */
-QScxmlParser::State QScxmlParser::state() const
-{
- return d->state();
-}
-
-/*!
* Returns the list of parse errors.
*/
QVector<QScxmlError> QScxmlParser::errors() const
@@ -1681,12 +1673,12 @@ bool QScxmlParserPrivate::ParserState::validChild(ParserState::Kind parent, Pars
case ParserState::OnExit:
return isExecutableContent(child);
case ParserState::History:
- return (child == ParserState::Transition);
+ return child == ParserState::Transition;
case ParserState::Raise:
return false;
case ParserState::If:
- return (child == ParserState::ElseIf || child == ParserState::Else
- || isExecutableContent(child));
+ return child == ParserState::ElseIf || child == ParserState::Else
+ || isExecutableContent(child);
case ParserState::ElseIf:
case ParserState::Else:
return false;
@@ -1701,11 +1693,10 @@ bool QScxmlParserPrivate::ParserState::validChild(ParserState::Kind parent, Pars
case ParserState::Assign:
return false;
case ParserState::DoneData:
- return (child == ParserState::Content || child == ParserState::Param);
case ParserState::Send:
- return (child == ParserState::Param || child == ParserState::Content);
+ return child == ParserState::Content || child == ParserState::Param;
case ParserState::Content:
- return isExecutableContent(child);
+ return child == ParserState::Scxml || isExecutableContent(child);
case ParserState::Param:
case ParserState::Cancel:
return false;
@@ -2065,7 +2056,6 @@ QScxmlParserPrivate::QScxmlParserPrivate(QScxmlParser *parser, QXmlStreamReader
, m_defaultLoader(parser)
, m_loader(&m_defaultLoader)
, m_reader(reader)
- , m_state(QScxmlParser::StartingParsing)
, m_qtMode(QScxmlParser::QtModeFromInputFile)
{}
@@ -2113,12 +2103,23 @@ void QScxmlParserPrivate::parseSubDocument(DocumentModel::Invoke *parentInvoke,
{
QScxmlParser p(reader);
p.setFileName(fileName);
- p.d->parse();
+ p.d->readDocument();
+ parentInvoke->content.reset(p.d->m_doc.take());
+ m_doc->allSubDocuments.append(parentInvoke->content.data());
+ m_errors.append(p.errors());
+}
+
+bool QScxmlParserPrivate::parseSubElement(DocumentModel::Invoke *parentInvoke, QXmlStreamReader *reader, const QString &fileName)
+{
+ QScxmlParser p(reader);
+ p.setFileName(fileName);
+ p.d->resetDocument();
+ bool ok = p.d->readElement();
parentInvoke->content.reset(p.d->m_doc.take());
m_doc->allSubDocuments.append(parentInvoke->content.data());
m_errors.append(p.errors());
- if (p.state() == QScxmlParser::ParsingError)
- m_state = QScxmlParser::ParsingError;
+ parentInvoke->content->qtMode = m_doc->qtMode;
+ return ok;
}
static bool isWordEnd(const QStringRef &str, int start)
@@ -2131,612 +2132,954 @@ static bool isWordEnd(const QStringRef &str, int start)
return ch.isSpace();
}
-void QScxmlParserPrivate::parse()
+bool QScxmlParserPrivate::preReadElementScxml()
+{
+ if (m_doc->root) {
+ addError(QLatin1String("Doc root already allocated"));
+ return false;
+ }
+ m_doc->root = new DocumentModel::Scxml(xmlLocation());
+
+ auto scxml = m_doc->root;
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ if (attributes.hasAttribute(QStringLiteral("initial"))) {
+ const QString initial = attributes.value(QStringLiteral("initial")).toString();
+ scxml->initial += initial.split(QChar::Space, QString::SkipEmptyParts);
+ }
+
+ const 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 if (datamodel.startsWith(QLatin1String("cplusplus"))) {
+ scxml->dataModel = DocumentModel::Scxml::CppDataModel;
+ int firstColon = datamodel.indexOf(QLatin1Char(':'));
+ if (firstColon == -1) {
+ scxml->cppDataModelClassName = attributes.value(QStringLiteral("name")).toString() + QStringLiteral("DataModel");
+ scxml->cppDataModelHeaderName = scxml->cppDataModelClassName + QStringLiteral(".h");
+ } else {
+ int lastColon = datamodel.lastIndexOf(QLatin1Char(':'));
+ if (lastColon == -1) {
+ lastColon = datamodel.length();
+ } else {
+ scxml->cppDataModelHeaderName = datamodel.mid(lastColon + 1).toString();
+ }
+ scxml->cppDataModelClassName = datamodel.mid(firstColon + 1, lastColon - firstColon - 1).toString();
+ }
+ } else {
+ addError(QStringLiteral("Unsupported data model '%1' in scxml")
+ .arg(datamodel.toString()));
+ }
+ const 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 false;
+ }
+ const QStringRef name = attributes.value(QLatin1String("name"));
+ if (!name.isEmpty()) {
+ scxml->name = name.toString();
+ }
+ m_currentState = m_doc->root;
+ current().instructionContainer = &m_doc->root->initialSetup;
+ return true;
+}
+
+
+bool QScxmlParserPrivate::preReadElementState()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto newState = m_doc->newState(m_currentState, DocumentModel::State::Normal, xmlLocation());
+ if (!maybeId(attributes, &newState->id))
+ return false;
+
+ if (attributes.hasAttribute(QStringLiteral("initial"))) {
+ const QString initial = attributes.value(QStringLiteral("initial")).toString();
+ newState->initial += initial.split(QChar::Space, QString::SkipEmptyParts);
+ }
+ m_currentState = newState;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementParallel()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto newState = m_doc->newState(m_currentState, DocumentModel::State::Parallel, xmlLocation());
+ if (!maybeId(attributes, &newState->id))
+ return false;
+
+ m_currentState = newState;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementInitial()
+{
+ DocumentModel::AbstractState *parent = currentParent();
+ if (!parent) {
+ addError(QStringLiteral("<initial> found outside a state"));
+ return false;
+ }
+
+ DocumentModel::State *parentState = parent->asState();
+ if (!parentState) {
+ addError(QStringLiteral("<initial> found outside a state"));
+ return false;
+ }
+
+ if (parentState->type == DocumentModel::State::Parallel) {
+ addError(QStringLiteral("Explicit initial state for parallel states not supported (only implicitly through the initial states of its substates)"));
+ return false;
+ }
+ auto newState = m_doc->newState(m_currentState, DocumentModel::State::Initial, xmlLocation());
+ m_currentState = newState;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementTransition()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto transition = m_doc->newTransition(m_currentState, 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;
+ } else if (type == QLatin1String("internal")) {
+ transition->type = DocumentModel::Transition::Internal;
+ } else {
+ addError(QStringLiteral("invalid transition type '%1', valid values are 'external' and 'internal'").arg(type.toString()));
+ return true; // TODO: verify me
+ }
+ current().instructionContainer = &transition->instructionsOnTransition;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementFinal()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto newState = m_doc->newState(m_currentState, DocumentModel::State::Final, xmlLocation());
+ if (!maybeId(attributes, &newState->id))
+ return false;
+ m_currentState = newState;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementHistory()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+
+ DocumentModel::AbstractState *parent = currentParent();
+ if (!parent) {
+ addError(QStringLiteral("<history> found outside a state"));
+ return false;
+ }
+ auto newState = m_doc->newHistoryState(parent, xmlLocation());
+ if (!maybeId(attributes, &newState->id))
+ return false;
+
+ const 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()));
+ return false;
+ }
+ m_currentState = newState;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementOnEntry()
+{
+ const ParserState::Kind previousKind = previous().kind;
+ switch (previousKind) {
+ case ParserState::Final:
+ case ParserState::State:
+ case ParserState::Parallel:
+ if (DocumentModel::State *s = m_currentState->asState()) {
+ current().instructionContainer = m_doc->newSequence(&s->onEntry);
+ break;
+ }
+ // intentional fall-through
+ default:
+ addError(QStringLiteral("unexpected container state for onentry"));
+ break;
+ }
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementOnExit()
+{
+ ParserState::Kind previousKind = previous().kind;
+ switch (previousKind) {
+ case ParserState::Final:
+ case ParserState::State:
+ case ParserState::Parallel:
+ if (DocumentModel::State *s = m_currentState->asState()) {
+ current().instructionContainer = m_doc->newSequence(&s->onExit);
+ break;
+ }
+ // intentional fall-through
+ default:
+ addError(QStringLiteral("unexpected container state for onexit"));
+ break;
+ }
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementRaise()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto raise = m_doc->newNode<DocumentModel::Raise>(xmlLocation());
+ raise->event = attributes.value(QLatin1String("event")).toString();
+ current().instruction = raise;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementIf()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto *ifI = m_doc->newNode<DocumentModel::If>(xmlLocation());
+ current().instruction = ifI;
+ ifI->conditions.append(attributes.value(QLatin1String("cond")).toString());
+ current().instructionContainer = m_doc->newSequence(&ifI->blocks);
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementElseIf()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+
+ DocumentModel::If *ifI = lastIf();
+ if (!ifI)
+ return false;
+
+ ifI->conditions.append(attributes.value(QLatin1String("cond")).toString());
+ previous().instructionContainer = m_doc->newSequence(&ifI->blocks);
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementElse()
+{
+ DocumentModel::If *ifI = lastIf();
+ if (!ifI)
+ return false;
+
+ previous().instructionContainer = m_doc->newSequence(&ifI->blocks);
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementForeach()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ 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();
+ current().instruction = foreachI;
+ current().instructionContainer = &foreachI->block;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementLog()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto logI = m_doc->newNode<DocumentModel::Log>(xmlLocation());
+ logI->label = attributes.value(QLatin1String("label")).toString();
+ logI->expr = attributes.value(QLatin1String("expr")).toString();
+ current().instruction = logI;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementDataModel()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementData()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ 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 (DocumentModel::Scxml *scxml = m_currentState->asScxml()) {
+ scxml->dataElements.append(data);
+ } else if (DocumentModel::State *state = m_currentState->asState()) {
+ state->dataElements.append(data);
+ } else {
+ Q_UNREACHABLE();
+ }
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementAssign()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto assign = m_doc->newNode<DocumentModel::Assign>(xmlLocation());
+ assign->location = attributes.value(QLatin1String("location")).toString();
+ assign->expr = attributes.value(QLatin1String("expr")).toString();
+ current().instruction = assign;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementDoneData()
+{
+ DocumentModel::State *s = m_currentState->asState();
+ if (s && s->type == DocumentModel::State::Final) {
+ if (s->doneData) {
+ addError(QLatin1String("state can only have one donedata"));
+ } else {
+ s->doneData = m_doc->newNode<DocumentModel::DoneData>(xmlLocation());
+ }
+ } else {
+ addError(QStringLiteral("donedata can only occur in a final state"));
+ }
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementContent()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ ParserState::Kind previousKind = previous().kind;
+ switch (previousKind) {
+ 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 = previous().instruction->asSend();
+ Q_ASSERT(s);
+ s->content = attributes.value(QLatin1String("expr")).toString();
+ } break;
+ case ParserState::Invoke: {
+ DocumentModel::Invoke *i = previous().instruction->asInvoke();
+ Q_ASSERT(i);
+ Q_UNUSED(i);
+ if (attributes.hasAttribute(QStringLiteral("expr"))) {
+ addError(QStringLiteral("expr attribute in content of invoke is not supported"));
+ break;
+ }
+ } break;
+ default:
+ addError(QStringLiteral("unexpected parent of content %1").arg(previous().kind));
+ }
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementParam()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ 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();
+
+ ParserState::Kind previousKind = previous().kind;
+ switch (previousKind) {
+ 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 = previous().instruction->asSend();
+ Q_ASSERT(s);
+ s->params.append(param);
+ } break;
+ case ParserState::Invoke: {
+ DocumentModel::Invoke *i = previous().instruction->asInvoke();
+ Q_ASSERT(i);
+ i->params.append(param);
+ } break;
+ default:
+ addError(QStringLiteral("unexpected parent of param %1").arg(previous().kind));
+ }
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementScript()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto *script = m_doc->newNode<DocumentModel::Script>(xmlLocation());
+ script->src = attributes.value(QLatin1String("src")).toString();
+ current().instruction = script;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementSend()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ 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);
+ current().instruction = send;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementCancel()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ auto *cancel = m_doc->newNode<DocumentModel::Cancel>(xmlLocation());
+ cancel->sendid = attributes.value(QLatin1String("sendid")).toString();
+ cancel->sendidexpr = attributes.value(QLatin1String("sendidexpr")).toString();
+ current().instruction = cancel;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementInvoke()
+{
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+ DocumentModel::State *parentState = m_currentState->asState();
+ if (!parentState ||
+ (parentState->type != DocumentModel::State::Normal && parentState->type != DocumentModel::State::Parallel)) {
+ addError(QStringLiteral("invoke can only occur in <state> or <parallel>"));
+ return true; // TODO: verify me
+ }
+ auto *invoke = m_doc->newNode<DocumentModel::Invoke>(xmlLocation());
+ parentState->invokes.append(invoke);
+ 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);
+ current().instruction = invoke;
+ return true;
+}
+
+bool QScxmlParserPrivate::preReadElementFinalize()
+{
+ auto instr = previous().instruction;
+ if (!instr) {
+ addError(QStringLiteral("no previous instruction found for <finalize>"));
+ return false;
+ }
+ auto invoke = instr->asInvoke();
+ if (!invoke) {
+ addError(QStringLiteral("instruction before <finalize> is not <invoke>"));
+ return false;
+ }
+ current().instructionContainer = &invoke->finalize;
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementScxml()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementState()
+{
+ currentStateUp();
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementParallel()
+{
+ currentStateUp();
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementInitial()
+{
+ currentStateUp();
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementTransition()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementFinal()
+{
+ currentStateUp();
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementHistory()
+{
+ currentStateUp();
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementOnEntry()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementOnExit()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementRaise()
+{
+ return flushInstruction();
+}
+
+bool QScxmlParserPrivate::postReadElementIf()
+{
+ return flushInstruction();
+}
+
+bool QScxmlParserPrivate::postReadElementElseIf()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementElse()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementForeach()
+{
+ return flushInstruction();
+}
+
+bool QScxmlParserPrivate::postReadElementLog()
+{
+ return flushInstruction();
+}
+
+bool QScxmlParserPrivate::postReadElementDataModel()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementData()
+{
+ const ParserState parserState = current();
+ DocumentModel::DataElement *data = Q_NULLPTR;
+ if (auto state = m_currentState->asState()) {
+ data = state->dataElements.last();
+ } else if (auto scxml = m_currentState->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"));
+ return false;
+ }
+ if (!parserState.chars.trimmed().isEmpty()) {
+ if (!data->src.isEmpty()) {
+ addError(QStringLiteral("data element with both 'src' attribute and CDATA"));
+ return false;
+ } else if (!data->expr.isEmpty()) {
+ addError(QStringLiteral("data element with both 'expr' attribute and CDATA"));
+ return false;
+ } else {
+ // w3c-ecma/test558 - "if a child element of <data> is not a XML,
+ // treat it as a string with whitespace normalization"
+ // We've modified the test, so that a string is enclosed with quotes.
+ data->expr = parserState.chars;
+ }
+ } else if (!data->src.isEmpty()) {
+ if (!m_loader) {
+ addError(QStringLiteral("cannot parse a document with external dependencies without a loader"));
+ } else {
+ bool ok;
+ const QByteArray ba = load(data->src, &ok);
+ if (!ok) {
+ addError(QStringLiteral("failed to load external dependency"));
+ } else {
+ // w3c-ecma/test558 - "if XML is loaded via "src" attribute,
+ // treat it as a string with whitespace normalization"
+ // We've enclosed the text in file with quotes.
+ data->expr = QString::fromUtf8(ba);
+ }
+ }
+ }
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementAssign()
+{
+ return flushInstruction();
+}
+
+bool QScxmlParserPrivate::postReadElementDoneData()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementContent()
+{
+ const ParserState parserState = current();
+ if (!parserState.chars.trimmed().isEmpty()) {
+
+ switch (previous().kind) {
+ case ParserState::DoneData: // see test529
+ m_currentState->asState()->doneData->contents = parserState.chars.simplified();
+ break;
+ case ParserState::Send: // see test179
+ previous().instruction->asSend()->content = parserState.chars.simplified();
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementParam()
+{
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementScript()
+{
+ const ParserState parserState = current();
+ DocumentModel::Script *scriptI = parserState.instruction->asScript();
+ if (!parserState.chars.trimmed().isEmpty()) {
+ scriptI->content = parserState.chars.trimmed();
+ if (!scriptI->src.isEmpty())
+ addError(QStringLiteral("both src 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"));
+ } else {
+ bool ok;
+ const QByteArray data = load(scriptI->src, &ok);
+ if (!ok) {
+ addError(QStringLiteral("failed to load external dependency"));
+ } else {
+ scriptI->content = QString::fromUtf8(data);
+ }
+ }
+ }
+ return flushInstruction();
+}
+
+bool QScxmlParserPrivate::postReadElementSend()
+{
+ return flushInstruction();
+}
+
+bool QScxmlParserPrivate::postReadElementCancel()
+{
+ return flushInstruction();
+}
+
+bool QScxmlParserPrivate::postReadElementInvoke()
+{
+ DocumentModel::Invoke *i = current().instruction->asInvoke();
+ const QString fileName = i->src;
+ if (!i->content.data()) {
+ if (!fileName.isEmpty()) {
+ bool ok = true;
+ const QByteArray data = load(fileName, &ok);
+ if (!ok) {
+ addError(QStringLiteral("failed to load external dependency"));
+ } else {
+ QXmlStreamReader reader(data);
+ parseSubDocument(i, &reader, fileName);
+ }
+ } else { // invoke always expects sub document
+ DocumentModel::ScxmlDocument *doc = new DocumentModel::ScxmlDocument(m_fileName);
+ doc->root = new DocumentModel::Scxml(DocumentModel::XmlLocation(0, 0));
+ i->content.reset(doc);
+ m_doc->allSubDocuments.append(i->content.data());
+ }
+ } else if (!fileName.isEmpty()) {
+ addError(QStringLiteral("both src and content given to invoke"));
+ }
+
+ return true;
+}
+
+bool QScxmlParserPrivate::postReadElementFinalize()
+{
+ return true;
+}
+
+void QScxmlParserPrivate::resetDocument()
{
m_doc.reset(new DocumentModel::ScxmlDocument(fileName()));
+}
+
+bool QScxmlParserPrivate::readDocument()
+{
+ resetDocument();
if (m_qtMode == QScxmlParser::QtModeEnabled)
m_doc->qtMode = true;
else if (m_qtMode == QScxmlParser::QtModeDisabled)
m_doc->qtMode = false;
m_currentState = m_doc->root;
- while (!m_reader->atEnd()) {
- QXmlStreamReader::TokenType tt = m_reader->readNext();
- switch (tt) {
- case QXmlStreamReader::Comment: {
- static const QString qtModeSwitch = QStringLiteral("enable-qt-mode:");
- QStringRef commentText = m_reader->text();
- int qtModeIdx = commentText.indexOf(qtModeSwitch);
- if (qtModeIdx != -1) {
- qtModeIdx += qtModeSwitch.size();
- while (qtModeIdx < commentText.size()) {
- if (commentText.at(qtModeIdx).isSpace()) {
- ++qtModeIdx;
- } else {
- break;
- }
- }
- QStringRef value = commentText.mid(qtModeIdx);
- if (value.startsWith(QStringLiteral("yes")) && isWordEnd(value, 3)) {
- if (m_qtMode == QScxmlParser::QtModeFromInputFile)
- m_doc->qtMode = true;
- } else if (value.startsWith(QStringLiteral("no")) && isWordEnd(value, 2)) {
- if (m_qtMode == QScxmlParser::QtModeFromInputFile)
- m_doc->qtMode = false;
- } else {
- addError(QStringLiteral("expected 'yes' or 'no' after enable-qt-mode in comment"));
- }
+ for (bool finished = false; !finished && !m_reader->hasError();) {
+ switch (m_reader->readNext()) {
+ case QXmlStreamReader::StartElement : {
+ const QStringRef newTag = m_reader->name();
+ const ParserState::Kind newElementKind = ParserState::nameToParserStateKind(newTag);
+
+ auto ns = m_reader->namespaceUri();
+
+ if (ns != scxmlNamespace) {
+ m_reader->skipCurrentElement();
+ } else if (newElementKind == ParserState::None) {
+ addError(QStringLiteral("Unknown element %1").arg(newTag.toString()));
+ m_reader->skipCurrentElement();
+ } else if (newElementKind == ParserState::Scxml) {
+ if (readElement() == false)
+ return false;
+ } else {
+ addError(QStringLiteral("Unexpected element %1").arg(newTag.toString()));
+ m_reader->skipCurrentElement();
}
- } break;
- 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.
+ case QXmlStreamReader::EndElement :
+ finished = true;
break;
- case QXmlStreamReader::EndDocument:
- // The reader reports the end of the document.
- if (!m_stack.isEmpty() || m_state != QScxmlParser::FinishedParsing) {
- addError(QStringLiteral("document finished without a proper scxml item"));
- }
+ case QXmlStreamReader::Comment:
+ parseComment();
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().
- {
+ default :
+ break;
+ }
+ }
+ if (!m_doc->root) {
+ addError(QStringLiteral("Missing root element"));
+ return false;
+ }
+
+ if (m_reader->hasError() && m_reader->error() != QXmlStreamReader::PrematureEndOfDocumentError) {
+ addError(QStringLiteral("Error parsing SCXML file: %1").arg(m_reader->errorString()));
+ return false;
+ }
+
+ return true;
+}
+
+bool QScxmlParserPrivate::readElement()
+{
+ const QStringRef currentTag = m_reader->name();
+ const QXmlStreamAttributes attributes = m_reader->attributes();
+
+ const ParserState::Kind elementKind = ParserState::nameToParserStateKind(currentTag);
+
+ if (!checkAttributes(attributes, elementKind))
+ return false;
+
+ if (elementKind == ParserState::Scxml && m_doc->root) {
+ if (!hasPrevious()) {
+ addError(QStringLiteral("misplaced scxml"));
+ return false;
+ }
+
+ DocumentModel::Invoke *i = previous().instruction->asInvoke();
+ if (!i) {
+ addError(QStringLiteral("misplaced scxml"));
+ return false;
+ }
+
+ return parseSubElement(i, m_reader, m_fileName);
+ }
+
+ if (elementKind != ParserState::Scxml && !m_stack.count()) {
+ addError(QStringLiteral("misplaced %1").arg(currentTag.toString()));
+ return false;
+ }
+
+ ParserState pNew = ParserState(elementKind);
+
+ m_stack.append(pNew);
+
+ switch (elementKind) {
+ case ParserState::Scxml: if (!preReadElementScxml()) return false; break;
+ case ParserState::State: if (!preReadElementState()) return false; break;
+ case ParserState::Parallel: if (!preReadElementParallel()) return false; break;
+ case ParserState::Initial: if (!preReadElementInitial()) return false; break;
+ case ParserState::Transition: if (!preReadElementTransition()) return false; break;
+ case ParserState::Final: if (!preReadElementFinal()) return false; break;
+ case ParserState::History: if (!preReadElementHistory()) return false; break;
+ case ParserState::OnEntry: if (!preReadElementOnEntry()) return false; break;
+ case ParserState::OnExit: if (!preReadElementOnExit()) return false; break;
+ case ParserState::Raise: if (!preReadElementRaise()) return false; break;
+ case ParserState::If: if (!preReadElementIf()) return false; break;
+ case ParserState::ElseIf: if (!preReadElementElseIf()) return false; break;
+ case ParserState::Else: if (!preReadElementElse()) return false; break;
+ case ParserState::Foreach: if (!preReadElementForeach()) return false; break;
+ case ParserState::Log: if (!preReadElementLog()) return false; break;
+ case ParserState::DataModel: if (!preReadElementDataModel()) return false; break;
+ case ParserState::Data: if (!preReadElementData()) return false; break;
+ case ParserState::Assign: if (!preReadElementAssign()) return false; break;
+ case ParserState::DoneData: if (!preReadElementDoneData()) return false; break;
+ case ParserState::Content: if (!preReadElementContent()) return false; break;
+ case ParserState::Param: if (!preReadElementParam()) return false; break;
+ case ParserState::Script: if (!preReadElementScript()) return false; break;
+ case ParserState::Send: if (!preReadElementSend()) return false; break;
+ case ParserState::Cancel: if (!preReadElementCancel()) return false; break;
+ case ParserState::Invoke: if (!preReadElementInvoke()) return false; break;
+ case ParserState::Finalize: if (!preReadElementFinalize()) return false; break;
+ default: addError(QStringLiteral("Unknown element %1").arg(currentTag.toString())); return false;
+ }
+
+ for (bool finished = false; !finished && !m_reader->hasError();) {
+ switch (m_reader->readNext()) {
+ case QXmlStreamReader::StartElement : {
+ const QStringRef newTag = m_reader->name();
+ const ParserState::Kind newElementKind = ParserState::nameToParserStateKind(newTag);
+
auto ns = m_reader->namespaceUri();
+
if (ns != scxmlNamespace) {
m_reader->skipCurrentElement();
- continue;
- }
-
- const QStringRef elName = m_reader->name();
- const QXmlStreamAttributes attributes = m_reader->attributes();
- if (!m_stack.isEmpty() && m_stack.last().kind == ParserState::Data) {
- break;
- }
- const ParserState::Kind elKind = ParserState::nameToParserStateKind(elName);
- if (elKind == ParserState::None) {
- addError(QStringLiteral("unexpected element %1").arg(elName.toString()));
- break;
- }
- if (!m_doc->root && elKind != ParserState::Scxml) {
- addError(QStringLiteral("found %1 instead of <scxml> as root element")
- .arg(elName.toString()));
- return;
- }
- ParserState pNew = ParserState(elKind);
- if (!checkAttributes(attributes, elKind))
- return;
-
- if (elKind == ParserState::Scxml) {
- if (m_state != QScxmlParser::StartingParsing || !m_stack.isEmpty()) {
- addError(xmlLocation(), QStringLiteral("found scxml tag mid stream"));
- return;
- } else {
- m_state = QScxmlParser::ParsingScxml;
- }
- m_doc->root = new DocumentModel::Scxml(xmlLocation());
- auto scxml = m_doc->root;
- if (attributes.hasAttribute(QStringLiteral("initial"))) {
- QString initial = attributes.value(QStringLiteral("initial")).toString();
- scxml->initial += initial.split(QChar::Space, QString::SkipEmptyParts);
- }
- 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 if (datamodel.startsWith(QLatin1String("cplusplus"))) {
- scxml->dataModel = DocumentModel::Scxml::CppDataModel;
- int firstColon = datamodel.indexOf(QLatin1Char(':'));
- if (firstColon == -1) {
- scxml->cppDataModelClassName = attributes.value(QStringLiteral("name")).toString() + QStringLiteral("DataModel");
- scxml->cppDataModelHeaderName = scxml->cppDataModelClassName + QStringLiteral(".h");
- } else {
- int lastColon = datamodel.lastIndexOf(QLatin1Char(':'));
- if (lastColon == -1) {
- lastColon = datamodel.length();
- } else {
- scxml->cppDataModelHeaderName = datamodel.mid(lastColon + 1).toString();
- }
- scxml->cppDataModelClassName = datamodel.mid(firstColon + 1, lastColon - firstColon - 1).toString();
- }
- } 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();
- }
- m_currentState = m_doc->root;
- pNew.instructionContainer = &m_doc->root->initialSetup;
- } else if (elKind == ParserState::State) {
- auto newState = m_doc->newState(m_currentState, DocumentModel::State::Normal, xmlLocation());
- if (!maybeId(attributes, &newState->id)) return;
- if (attributes.hasAttribute(QStringLiteral("initial"))) {
- QString initial = attributes.value(QStringLiteral("initial")).toString();
- newState->initial += initial.split(QChar::Space, QString::SkipEmptyParts);
- }
- m_currentState = newState;
- } else if (elKind == ParserState::Parallel) {
- auto newState = m_doc->newState(m_currentState, DocumentModel::State::Parallel, xmlLocation());
- if (!maybeId(attributes, &newState->id)) return;
- m_currentState = newState;
- } else if (elKind == ParserState::Initial) {
- DocumentModel::AbstractState *parent = currentParent();
- if (!parent) {
- addError(QStringLiteral("<initial> found outside a state"));
- return;
- } else if (parent->asState()->type == DocumentModel::State::Parallel) {
- addError(QStringLiteral("Explicit initial state for parallel states not supported (only implicitly through the initial states of its substates)"));
- return;
- }
- auto newState = m_doc->newState(m_currentState, DocumentModel::State::Initial, xmlLocation());
- m_currentState = newState;
- } else if (elKind == ParserState::Transition) {
- auto transition = m_doc->newTransition(m_currentState, 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;
- } else if (type == QLatin1String("internal")) {
- transition->type = DocumentModel::Transition::Internal;
- } else {
- addError(QStringLiteral("invalid transition type '%1', valid values are 'external' and 'internal'").arg(type.toString()));
- break;
- }
- pNew.instructionContainer = &transition->instructionsOnTransition;
- } else if (elKind == ParserState::Final) {
- auto newState = m_doc->newState(m_currentState, DocumentModel::State::Final, xmlLocation());
- if (!maybeId(attributes, &newState->id)) return;
- m_currentState = newState;
- } else if (elKind == ParserState::History) {
- DocumentModel::AbstractState *parent = currentParent();
- if (!parent) {
- addError(QStringLiteral("<history> found outside a state"));
- return;
- }
- auto newState = m_doc->newHistoryState(parent, 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()));
- return;
- }
- m_currentState = newState;
- } else if (elKind == 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"));
- break;
- }
- } else if (elKind == 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"));
- break;
- }
- } else if (elKind == ParserState::Raise) {
- auto raise = m_doc->newNode<DocumentModel::Raise>(xmlLocation());
- raise->event = attributes.value(QLatin1String("event")).toString();
- pNew.instruction = raise;
- } else if (elKind == 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);
- } else if (elKind == ParserState::ElseIf) {
- DocumentModel::If *ifI = lastIf();
- if (!ifI) return;
- ifI->conditions.append(attributes.value(QLatin1String("cond")).toString());
- m_stack.last().instructionContainer = m_doc->newSequence(&ifI->blocks);
- } else if (elKind == ParserState::Else) {
- DocumentModel::If *ifI = lastIf();
- if (!ifI) return;
- m_stack.last().instructionContainer = m_doc->newSequence(&ifI->blocks);
- } else if (elKind == 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;
- } else if (elKind == 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;
- } else if (elKind == ParserState::DataModel) {
- } else if (elKind == ParserState::Data) {
- 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 (DocumentModel::Scxml *scxml = m_currentState->asScxml()) {
- scxml->dataElements.append(data);
- } else if (DocumentModel::State *state = m_currentState->asState()) {
- state->dataElements.append(data);
- } else {
- Q_UNREACHABLE();
- }
- } else if (elKind == 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;
- } else if (elKind == ParserState::DoneData) {
- 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"));
- } else {
- s->doneData = m_doc->newNode<DocumentModel::DoneData>(xmlLocation());
- }
- }
- }
- if (!handled) {
- addError(QStringLiteral("donedata can only occur in a final state"));
- }
- } else if (elKind == ParserState::Content) {
- 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;
- case ParserState::Invoke: {
- DocumentModel::Invoke *i = m_stack.last().instruction->asInvoke();
- Q_ASSERT(i);
- if (attributes.hasAttribute(QStringLiteral("expr"))) {
- addError(QStringLiteral("expr attribute in content of invoke is not supported"));
- break;
- }
- parseSubDocument(i, m_reader, m_fileName);
- i->content->qtMode = m_doc->qtMode;
- } break;
- default:
- addError(QStringLiteral("unexpected parent of content %1").arg(m_stack.last().kind));
- }
- } else if (elKind == 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));
- }
- } else if (elKind == ParserState::Script) {
- auto *script = m_doc->newNode<DocumentModel::Script>(xmlLocation());
- script->src = attributes.value(QLatin1String("src")).toString();
- pNew.instruction = script;
- } else if (elKind == 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;
- } else if (elKind == 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;
- } else if (elKind == ParserState::Invoke) {
- auto *invoke = m_doc->newNode<DocumentModel::Invoke>(xmlLocation());
- DocumentModel::State *parentState = m_currentState->asState();
- if (!parentState ||
- (parentState->type != DocumentModel::State::Normal && parentState->type != DocumentModel::State::Parallel)) {
- addError(QStringLiteral("invoke can only occur in <state> or <parallel>"));
- break;
- }
- parentState->invokes.append(invoke);
- 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;
- } else if (elKind == ParserState::Finalize) {
- auto instr = m_stack.last().instruction;
- if (!instr) {
- addError(QStringLiteral("no previous instruction found for <finalize>"));
- return;
- }
- auto invoke = instr->asInvoke();
- if (!invoke) {
- addError(QStringLiteral("instruction before <finalize> is not <invoke>"));
- return;
- }
- pNew.instructionContainer = &invoke->finalize;
- }
- m_stack.append(pNew);
- if (m_stack.size()>1 && !m_stack.at(m_stack.size()-2).validChild(m_stack.last().kind)) {
- addError(QStringLiteral("invalid child"));
+ } else if (newElementKind == ParserState::None) {
+ addError(QStringLiteral("Unknown element %1").arg(newTag.toString()));
+ m_reader->skipCurrentElement();
+ } else if (pNew.validChild(newElementKind)) {
+ if (readElement() == false)
+ return false;
+ } else {
+ addError(QStringLiteral("Unexpected element %1").arg(newTag.toString()));
+ m_reader->skipCurrentElement();
}
- break;
}
- case QXmlStreamReader::EndElement:
- // The reader reports the end of an element with namespaceUri() and name().
- {
- const QStringRef elName = m_reader->name();
- if (m_stack.isEmpty()) {
- addError(QStringLiteral("unbalanced XML tag %1").arg(elName.toString()));
- break;
- }
- ParserState p = m_stack.last();
- m_stack.removeLast();
- switch (p.kind) {
- case ParserState::Scxml:
- if (m_state == QScxmlParser::ParsingScxml) {
- m_state = QScxmlParser::FinishedParsing;
- } else {
- m_state = QScxmlParser::ParsingError;
- }
- return;
- case ParserState::State:
- case ParserState::Parallel:
- case ParserState::Initial:
- case ParserState::Final:
- case ParserState::History:
- Q_ASSERT(m_currentState->parent);
- m_currentState = m_currentState->parent;
- break;
- case ParserState::Invoke:
- {
- DocumentModel::Invoke *i = p.instruction->asInvoke();
- const QString fileName = i->src;
- if (!i->content.data()) {
- if (!fileName.isEmpty()) {
- bool ok = true;
- QByteArray data = load(fileName, &ok);
- if (!ok) {
- addError(QStringLiteral("failed to load external dependency"));
- } else {
- QXmlStreamReader reader(data);
- parseSubDocument(i, &reader, fileName);
- }
- } else { // invoke always expects sub document
- DocumentModel::ScxmlDocument *doc = new DocumentModel::ScxmlDocument(m_fileName);
- doc->root = new DocumentModel::Scxml(DocumentModel::XmlLocation(0, 0));
- i->content.reset(doc);
- m_doc->allSubDocuments.append(i->content.data());
- }
- } else if (!fileName.isEmpty()) {
- addError(QStringLiteral("both src and content given to invoke"));
- }
- }
- 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 src 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"));
- } else {
- bool ok;
- QByteArray data = load(scriptI->src, &ok);
- if (!ok) {
- addError(QStringLiteral("failed to load external dependency"));
- } 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: {
- DocumentModel::InstructionSequence *instructions = m_stack.last().instructionContainer;
- if (!instructions) {
- addError(QStringLiteral("got executable content within an element that did not set instructionContainer"));
- return;
- }
- instructions->append(p.instruction);
- p.instruction = 0;
- break;
- }
- case ParserState::Finalize:
- case ParserState::DataModel:
- 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 = Q_NULLPTR;
- if (auto state = m_currentState->asState()) {
- data = state->dataElements.last();
- } else if (auto scxml = m_currentState->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"));
- return;
- }
- if (!p.chars.trimmed().isEmpty()) {
- if (!data->src.isEmpty()) {
- addError(QStringLiteral("data element with both 'src' attribute and CDATA"));
- return;
- } else if (!data->expr.isEmpty()) {
- addError(QStringLiteral("data element with both 'expr' attribute and CDATA"));
- return;
- } else {
- // w3c-ecma/test558 - "if a child element of <data> is not a XML,
- // treat it as a string with whitespace normalization"
- // We've modified the test, so that a string is enclosed with quotes.
- data->expr = p.chars;
- }
- } else if (!data->src.isEmpty()) {
- if (!m_loader) {
- addError(QStringLiteral("cannot parse a document with external dependencies without a loader"));
- } else {
- bool ok;
- QByteArray ba = load(data->src, &ok);
- if (!ok) {
- addError(QStringLiteral("failed to load external dependency"));
- } else {
- // w3c-ecma/test558 - "if XML is loaded via "src" attribute,
- // treat it as a string with whitespace normalization"
- // We've enclosed the text in file with quotes.
- data->expr = QString::fromUtf8(ba);
- }
- }
- }
- } 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.
+ break;
+ case QXmlStreamReader::EndElement :
+ finished = true;
+ break;
+ case QXmlStreamReader::Characters :
if (m_stack.isEmpty())
break;
- if (m_stack.last().collectChars())
- m_stack.last().chars.append(m_reader->text());
+ if (current().collectChars())
+ current().chars.append(m_reader->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().
+ case QXmlStreamReader::Comment:
+ parseComment();
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:
+ default :
break;
}
}
- if (m_reader->hasError() && (m_reader->error() != QXmlStreamReader::PrematureEndOfDocumentError
- || !m_doc->root)) {
+
+ switch (elementKind) {
+ case ParserState::Scxml: if (!postReadElementScxml()) return false; break;
+ case ParserState::State: if (!postReadElementState()) return false; break;
+ case ParserState::Parallel: if (!postReadElementParallel()) return false; break;
+ case ParserState::Initial: if (!postReadElementInitial()) return false; break;
+ case ParserState::Transition: if (!postReadElementTransition()) return false; break;
+ case ParserState::Final: if (!postReadElementFinal()) return false; break;
+ case ParserState::History: if (!postReadElementHistory()) return false; break;
+ case ParserState::OnEntry: if (!postReadElementOnEntry()) return false; break;
+ case ParserState::OnExit: if (!postReadElementOnExit()) return false; break;
+ case ParserState::Raise: if (!postReadElementRaise()) return false; break;
+ case ParserState::If: if (!postReadElementIf()) return false; break;
+ case ParserState::ElseIf: if (!postReadElementElseIf()) return false; break;
+ case ParserState::Else: if (!postReadElementElse()) return false; break;
+ case ParserState::Foreach: if (!postReadElementForeach()) return false; break;
+ case ParserState::Log: if (!postReadElementLog()) return false; break;
+ case ParserState::DataModel: if (!postReadElementDataModel()) return false; break;
+ case ParserState::Data: if (!postReadElementData()) return false; break;
+ case ParserState::Assign: if (!postReadElementAssign()) return false; break;
+ case ParserState::DoneData: if (!postReadElementDoneData()) return false; break;
+ case ParserState::Content: if (!postReadElementContent()) return false; break;
+ case ParserState::Param: if (!postReadElementParam()) return false; break;
+ case ParserState::Script: if (!postReadElementScript()) return false; break;
+ case ParserState::Send: if (!postReadElementSend()) return false; break;
+ case ParserState::Cancel: if (!postReadElementCancel()) return false; break;
+ case ParserState::Invoke: if (!postReadElementInvoke()) return false; break;
+ case ParserState::Finalize: if (!postReadElementFinalize()) return false; break;
+ default: break;
+ }
+
+ m_stack.removeLast();
+
+ if (m_reader->hasError()/* && m_reader->error() != QXmlStreamReader::PrematureEndOfDocumentError*/) {
addError(QStringLiteral("Error parsing SCXML file: %1").arg(m_reader->errorString()));
+ return false;
}
+
+ return true;
}
-QByteArray QScxmlParserPrivate::load(const QString &name, bool *ok) const
+void QScxmlParserPrivate::parseComment()
{
- return m_loader->load(name, m_fileName.isEmpty() ?
- QString() : QFileInfo(m_fileName).path(), ok);
+ static const QString qtModeSwitch = QStringLiteral("enable-qt-mode:");
+ const QStringRef commentText = m_reader->text();
+ int qtModeIdx = commentText.indexOf(qtModeSwitch);
+ if (qtModeIdx != -1) {
+ qtModeIdx += qtModeSwitch.size();
+ while (qtModeIdx < commentText.size()) {
+ if (commentText.at(qtModeIdx).isSpace()) {
+ ++qtModeIdx;
+ } else {
+ break;
+ }
+ }
+ const QStringRef value = commentText.mid(qtModeIdx);
+ if (value.startsWith(QStringLiteral("yes")) && isWordEnd(value, 3)) {
+ if (m_qtMode == QScxmlParser::QtModeFromInputFile)
+ m_doc->qtMode = true;
+ } else if (value.startsWith(QStringLiteral("no")) && isWordEnd(value, 2)) {
+ if (m_qtMode == QScxmlParser::QtModeFromInputFile)
+ m_doc->qtMode = false;
+ } else {
+ addError(QStringLiteral("expected 'yes' or 'no' after enable-qt-mode in comment"));
+ }
+ }
+}
+
+void QScxmlParserPrivate::currentStateUp()
+{
+ Q_ASSERT(m_currentState->parent);
+ m_currentState = m_currentState->parent;
}
-QScxmlParser::State QScxmlParserPrivate::state() const
+bool QScxmlParserPrivate::flushInstruction()
+{
+ if (!hasPrevious()) {
+ addError(QStringLiteral("missing instructionContainer"));
+ return false;
+ }
+ DocumentModel::InstructionSequence *instructions = previous().instructionContainer;
+ if (!instructions) {
+ addError(QStringLiteral("got executable content within an element that did not set instructionContainer"));
+ return false;
+ }
+ instructions->append(current().instruction);
+ return true;
+}
+
+
+QByteArray QScxmlParserPrivate::load(const QString &name, bool *ok) const
{
- return m_state;
+ return m_loader->load(name, m_fileName.isEmpty() ?
+ QString() : QFileInfo(m_fileName).path(), ok);
}
QVector<QScxmlError> QScxmlParserPrivate::errors() const
@@ -2747,13 +3090,11 @@ QVector<QScxmlError> QScxmlParserPrivate::errors() const
void QScxmlParserPrivate::addError(const QString &msg)
{
m_errors.append(QScxmlError(m_fileName, m_reader->lineNumber(), m_reader->columnNumber(), msg));
- m_state = QScxmlParser::ParsingError;
}
void QScxmlParserPrivate::addError(const DocumentModel::XmlLocation &location, const QString &msg)
{
m_errors.append(QScxmlError(m_fileName, location.line, location.column, msg));
- m_state = QScxmlParser::ParsingError;
}
QScxmlParser::QtMode QScxmlParserPrivate::qtMode() const
@@ -2793,7 +3134,12 @@ bool QScxmlParserPrivate::maybeId(const QXmlStreamAttributes &attributes, QStrin
DocumentModel::If *QScxmlParserPrivate::lastIf()
{
- DocumentModel::Instruction *lastI = m_stack.last().instruction;
+ if (!hasPrevious()) {
+ addError(QStringLiteral("No previous instruction found for else block"));
+ return Q_NULLPTR;
+ }
+
+ DocumentModel::Instruction *lastI = previous().instruction;
if (!lastI) {
addError(QStringLiteral("No previous instruction found for else block"));
return Q_NULLPTR;
@@ -2806,6 +3152,21 @@ DocumentModel::If *QScxmlParserPrivate::lastIf()
return ifI;
}
+QScxmlParserPrivate::ParserState &QScxmlParserPrivate::current()
+{
+ return m_stack.last();
+}
+
+QScxmlParserPrivate::ParserState &QScxmlParserPrivate::previous()
+{
+ return m_stack[m_stack.count() - 2];
+}
+
+bool QScxmlParserPrivate::hasPrevious() const
+{
+ return m_stack.count() > 1;
+}
+
bool QScxmlParserPrivate::checkAttributes(const QXmlStreamAttributes &attributes,
QScxmlParserPrivate::ParserState::Kind kind)
{
diff --git a/src/scxml/qscxmlparser.h b/src/scxml/qscxmlparser.h
index fcd3197..3ce749f 100644
--- a/src/scxml/qscxmlparser.h
+++ b/src/scxml/qscxmlparser.h
@@ -67,13 +67,6 @@ public:
QScxmlParser *m_parser;
};
- enum State {
- StartingParsing,
- ParsingScxml,
- ParsingError,
- FinishedParsing,
- };
-
enum QtMode {
QtModeDisabled,
QtModeEnabled,
@@ -94,7 +87,6 @@ public:
QScxmlStateMachine *instantiateStateMachine() const;
void instantiateDataModel(QScxmlStateMachine *stateMachine) const;
- State state() const;
QVector<QScxmlError> errors() const;
void addError(const QString &msg);
diff --git a/src/scxml/qscxmlparser_p.h b/src/scxml/qscxmlparser_p.h
index 162b611..3103605 100644
--- a/src/scxml/qscxmlparser_p.h
+++ b/src/scxml/qscxmlparser_p.h
@@ -566,11 +566,11 @@ public:
QScxmlParser::Loader *loader() const;
void setLoader(QScxmlParser::Loader *loader);
- void parse();
+ bool readDocument();
void parseSubDocument(DocumentModel::Invoke *parentInvoke, QXmlStreamReader *reader, const QString &fileName);
+ bool parseSubElement(DocumentModel::Invoke *parentInvoke, QXmlStreamReader *reader, const QString &fileName);
QByteArray load(const QString &name, bool *ok) const;
- QScxmlParser::State state() const;
QVector<QScxmlError> errors() const;
void addError(const QString &msg);
@@ -588,6 +588,67 @@ private:
const QStringList &requiredNames,
const QStringList &optionalNames);
+ bool preReadElementScxml();
+ bool preReadElementState();
+ bool preReadElementParallel();
+ bool preReadElementInitial();
+ bool preReadElementTransition();
+ bool preReadElementFinal();
+ bool preReadElementHistory();
+ bool preReadElementOnEntry();
+ bool preReadElementOnExit();
+ bool preReadElementRaise();
+ bool preReadElementIf();
+ bool preReadElementElseIf();
+ bool preReadElementElse();
+ bool preReadElementForeach();
+ bool preReadElementLog();
+ bool preReadElementDataModel();
+ bool preReadElementData();
+ bool preReadElementAssign();
+ bool preReadElementDoneData();
+ bool preReadElementContent();
+ bool preReadElementParam();
+ bool preReadElementScript();
+ bool preReadElementSend();
+ bool preReadElementCancel();
+ bool preReadElementInvoke();
+ bool preReadElementFinalize();
+
+ bool postReadElementScxml();
+ bool postReadElementState();
+ bool postReadElementParallel();
+ bool postReadElementInitial();
+ bool postReadElementTransition();
+ bool postReadElementFinal();
+ bool postReadElementHistory();
+ bool postReadElementOnEntry();
+ bool postReadElementOnExit();
+ bool postReadElementRaise();
+ bool postReadElementIf();
+ bool postReadElementElseIf();
+ bool postReadElementElse();
+ bool postReadElementForeach();
+ bool postReadElementLog();
+ bool postReadElementDataModel();
+ bool postReadElementData();
+ bool postReadElementAssign();
+ bool postReadElementDoneData();
+ bool postReadElementContent();
+ bool postReadElementParam();
+ bool postReadElementScript();
+ bool postReadElementSend();
+ bool postReadElementCancel();
+ bool postReadElementInvoke();
+ bool postReadElementFinalize();
+
+ bool readElement();
+
+ void resetDocument();
+ void parseComment();
+ void currentStateUp();
+ bool flushInstruction();
+
private:
struct ParserState {
enum Kind {
@@ -645,6 +706,9 @@ private:
};
bool checkAttributes(const QXmlStreamAttributes &attributes, QScxmlParserPrivate::ParserState::Kind kind);
+ ParserState &current();
+ ParserState &previous();
+ bool hasPrevious() const;
private:
QString m_fileName;
@@ -657,7 +721,6 @@ private:
QXmlStreamReader *m_reader;
QVector<ParserState> m_stack;
- QScxmlParser::State m_state;
QVector<QScxmlError> m_errors;
QScxmlParser::QtMode m_qtMode;
};
diff --git a/tests/auto/qscxmlc/data/commentInScript.scxml b/tests/auto/qscxmlc/data/commentInScript.scxml
new file mode 100644
index 0000000..2af8286
--- /dev/null
+++ b/tests/auto/qscxmlc/data/commentInScript.scxml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- enable-qt-mode: yes -->
+<scxml
+ xmlns="http://www.w3.org/2005/07/scxml"
+ version="1.0"
+ name="CommentInScript"
+ datamodel="ecmascript"
+>
+ <datamodel>
+ <data id="media"/>
+ </datamodel>
+
+ <script><!--
+ function isValidMedia() {
+ var m = _event.data.media
+ return (m + "").length > 0
+ }
+ <initial>
+</scxml>
diff --git a/tests/auto/qscxmlc/tst_qscxmlc.qrc b/tests/auto/qscxmlc/tst_qscxmlc.qrc
index d6b8a6a..a86c786 100644
--- a/tests/auto/qscxmlc/tst_qscxmlc.qrc
+++ b/tests/auto/qscxmlc/tst_qscxmlc.qrc
@@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/tst_qscxmlc">
+ <file>data/commentInScript.scxml</file>
<file>data/empty.scxml</file>
<file>data/misplacedinvoke.scxml</file>
<file>data/invalidRoot.scxml</file>
diff --git a/tests/auto/scion/tst_scion.cpp b/tests/auto/scion/tst_scion.cpp
index 92dc291..0e017c7 100644
--- a/tests/auto/scion/tst_scion.cpp
+++ b/tests/auto/scion/tst_scion.cpp
@@ -43,7 +43,17 @@ Q_DECLARE_METATYPE(std::function<QScxmlStateMachine *()>);
enum { SpyWaitTime = 12000 };
-static QSet<QString> weFailOnThese = QSet<QString>()
+static QSet<QString> testFailOnParse = QSet<QString>()
+ // Currently we do not support loading data as XML content inside the <data> tag.
+ << QLatin1String("w3c-ecma/test557.txml")
+ // The following test uses the undocumented "exmode" attribute.
+ << QLatin1String("w3c-ecma/test441a.txml")
+ // The following test needs manual inspection of the result. However, note that we do not support the undocumented "exmode" attribute.
+ << QLatin1String("w3c-ecma/test441b.txml")
+ // The following test needs manual inspection of the result.
+ ;
+
+static QSet<QString> testFailOnRun = QSet<QString>()
// The following test needs manual inspection of the result. However, note that we do not support multiple identical keys for event data.
<< QLatin1String("delayedSend/send1") // same as above
<< QLatin1String("delayedSend/send2") // same as above
@@ -54,20 +64,12 @@ static QSet<QString> weFailOnThese = QSet<QString>()
<< QLatin1String("w3c-ecma/test201.txml")
<< QLatin1String("w3c-ecma/test364.txml") // initial attribute on <state>
<< QLatin1String("w3c-ecma/test388.txml") // Qt refuses to set an initial state to a "deep" state
-
- // Currently we do not support loading data as XML content inside the <data> tag.
- << QLatin1String("w3c-ecma/test557.txml")
- // The following test uses the undocumented "exmode" attribute.
- << QLatin1String("w3c-ecma/test441a.txml")
- // The following test needs manual inspection of the result. However, note that we do not support the undocumented "exmode" attribute.
- << QLatin1String("w3c-ecma/test441b.txml")
- // The following test needs manual inspection of the result.
<< QLatin1String("w3c-ecma/test230.txml")
<< QLatin1String("w3c-ecma/test250.txml")
<< QLatin1String("w3c-ecma/test307.txml")
;
-static QSet<QString> differentSemantics = QSet<QString>()
+static QSet<QString> testUseDifferentSemantics = QSet<QString>()
// FIXME: looks like a bug in internal event ordering when writing to read-only variables.
<< QLatin1String("w3c-ecma/test329.txml")
// Qt does not support forcing initial states that are not marked as such.
@@ -165,7 +167,8 @@ void TestScion::initTestCase()
enum TestStatus {
TestIsOk,
- TestFails,
+ TestFailsOnParse,
+ TestFailsOnRun,
TestUsesDifferentSemantics
};
Q_DECLARE_METATYPE(TestStatus)
@@ -181,10 +184,12 @@ void TestScion::generateData()
for (int i = 0; i < nrOfTests; ++i) {
TestStatus testStatus;
QString base = QString::fromUtf8(testBases[i]);
- if (differentSemantics.contains(base))
+ if (testUseDifferentSemantics.contains(base))
testStatus = TestUsesDifferentSemantics;
- else if (weFailOnThese.contains(base))
- testStatus = TestFails;
+ else if (testFailOnParse.contains(base))
+ testStatus = TestFailsOnParse;
+ else if (testFailOnRun.contains(base))
+ testStatus = TestFailsOnRun;
else
testStatus = TestIsOk;
QTest::newRow(testBases[i]) << base + QLatin1String(".scxml")
@@ -226,22 +231,21 @@ void TestScion::dynamic()
DynamicLoader loader(&parser);
parser.setLoader(&loader);
parser.parse();
- if (testStatus == TestFails && parser.state() != QScxmlParser::FinishedParsing)
- QEXPECT_FAIL("", "This is expected to fail", Abort);
- QCOMPARE(parser.state(), QScxmlParser::FinishedParsing);
+ if (testStatus == TestFailsOnParse)
+ QEXPECT_FAIL("", "This is expected to fail on parse", Abort);
QVERIFY(parser.errors().isEmpty());
scxmlFile.close();
QScopedPointer<QScxmlStateMachine> stateMachine(parser.instantiateStateMachine());
- if (stateMachine == Q_NULLPTR && testStatus == TestFails) {
- QEXPECT_FAIL("", "This is expected to fail", Abort);
- }
QVERIFY(stateMachine != Q_NULLPTR);
+
parser.instantiateDataModel(stateMachine.data());
- if (testStatus == TestFails)
- QEXPECT_FAIL("", "This is expected to fail", Abort);
- QVERIFY(runTest(stateMachine.data(), testDescription.object()));
+ const bool runResult = runTest(stateMachine.data(), testDescription.object());
+ if (runResult == false && testStatus == TestFailsOnRun)
+ QEXPECT_FAIL("", "This is expected to fail on run", Abort);
+
+ QVERIFY(runResult);
QCoreApplication::processEvents(); // flush any pending events
}
@@ -279,14 +283,17 @@ void TestScion::compiled()
jsonFile.close();
QScopedPointer<QScxmlStateMachine> stateMachine(creator());
- if (stateMachine == Q_NULLPTR && testStatus == TestFails) {
+ if (stateMachine == Q_NULLPTR && testStatus == TestFailsOnRun) {
QEXPECT_FAIL("", "This is expected to fail", Abort);
}
QVERIFY(stateMachine != Q_NULLPTR);
- if (testStatus == TestFails)
- QEXPECT_FAIL("", "This is expected to fail", Abort);
- QVERIFY(runTest(stateMachine.data(), testDescription.object()));
+ const bool runResult = runTest(stateMachine.data(), testDescription.object());
+ if (runResult == false && testStatus == TestFailsOnRun)
+ QEXPECT_FAIL("", "This is expected to fail on run", Abort);
+
+ QVERIFY(runResult);
+
QCoreApplication::processEvents(); // flush any pending events
}