1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qscxmlcompiler_p.h"
5#include "qscxmlexecutablecontent_p.h"
6
7#include <qxmlstream.h>
8#include <qloggingcategory.h>
9#include <qfile.h>
10#include <qlist.h>
11#include <qstring.h>
12
13#ifndef BUILD_QSCXMLC
14#include "qscxmlinvokableservice_p.h"
15#include "qscxmldatamodel_p.h"
16#include "qscxmlstatemachine_p.h"
17#include "qscxmlstatemachine.h"
18#include "qscxmltabledata_p.h"
19
20#include <private/qmetaobjectbuilder_p.h>
21#endif // BUILD_QSCXMLC
22
23#include <QtCore/qmap.h>
24
25#include <functional>
26
27namespace {
28enum {
29 DebugHelper_NameTransitions = 0
30};
31} // anonymous namespace
32
33QT_BEGIN_NAMESPACE
34
35static QString scxmlNamespace = QStringLiteral("http://www.w3.org/2005/07/scxml");
36static QString qtScxmlNamespace = QStringLiteral("http://theqtcompany.com/scxml/2015/06/");
37
38namespace {
39
40class ScxmlVerifier: public DocumentModel::NodeVisitor
41{
42public:
43 ScxmlVerifier(std::function<void (const DocumentModel::XmlLocation &, const QString &)> errorHandler)
44 : m_errorHandler(errorHandler)
45 , m_doc(nullptr)
46 , m_hasErrors(false)
47 {}
48
49 bool verify(DocumentModel::ScxmlDocument *doc)
50 {
51 if (doc->isVerified)
52 return true;
53
54 doc->isVerified = true;
55 m_doc = doc;
56 for (DocumentModel::AbstractState *state : std::as_const(t&: doc->allStates)) {
57 if (state->id.isEmpty()) {
58 continue;
59#ifndef QT_NO_DEBUG
60 } else if (m_stateById.contains(key: state->id)) {
61 Q_ASSERT(!"Should be unreachable: the compiler should check for this case!");
62#endif // QT_NO_DEBUG
63 } else {
64 m_stateById[state->id] = state;
65 }
66 }
67
68 if (doc->root)
69 doc->root->accept(visitor: this);
70 return !m_hasErrors;
71 }
72
73private:
74 bool visit(DocumentModel::Scxml *scxml) override
75 {
76 if (!scxml->name.isEmpty() && !isValidToken(id: scxml->name, tokenType: XmlNmtoken)) {
77 error(location: scxml->xmlLocation,
78 QStringLiteral("scxml name '%1' is not a valid XML Nmtoken").arg(a: scxml->name));
79 }
80
81 if (scxml->initial.isEmpty()) {
82 if (auto firstChild = firstAbstractState(container: scxml)) {
83 scxml->initialTransition = createInitialTransition(states: {firstChild});
84 }
85 } else {
86 QList<DocumentModel::AbstractState *> initialStates;
87 for (const QString &initial : std::as_const(t&: scxml->initial)) {
88 if (DocumentModel::AbstractState *s = m_stateById.value(key: initial))
89 initialStates.append(t: s);
90 else
91 error(location: scxml->xmlLocation, QStringLiteral("initial state '%1' not found for <scxml> element").arg(a: initial));
92 }
93 scxml->initialTransition = createInitialTransition(states: initialStates);
94 }
95
96 m_parentNodes.append(t: scxml);
97
98 return true;
99 }
100
101 void endVisit(DocumentModel::Scxml *) override
102 {
103 m_parentNodes.removeLast();
104 }
105
106 bool visit(DocumentModel::State *state) override
107 {
108 if (!state->id.isEmpty() && !isValidToken(id: state->id, tokenType: XmlNCName)) {
109 error(location: state->xmlLocation, QStringLiteral("'%1' is not a valid XML ID").arg(a: state->id));
110 }
111
112 if (state->initialTransition == nullptr) {
113 if (state->initial.isEmpty()) {
114 if (state->type == DocumentModel::State::Parallel) {
115 auto allChildren = allAbstractStates(container: state);
116 state->initialTransition = createInitialTransition(states: allChildren);
117 } else {
118 if (auto firstChild = firstAbstractState(container: state)) {
119 state->initialTransition = createInitialTransition(states: {firstChild});
120 }
121 }
122 } else {
123 Q_ASSERT(state->type == DocumentModel::State::Normal);
124 QList<DocumentModel::AbstractState *> initialStates;
125 for (const QString &initialState : std::as_const(t&: state->initial)) {
126 if (DocumentModel::AbstractState *s = m_stateById.value(key: initialState)) {
127 initialStates.append(t: s);
128 } else {
129 error(location: state->xmlLocation,
130 QStringLiteral("undefined initial state '%1' for state '%2'")
131 .arg(args: initialState, args&: state->id));
132 }
133 }
134 state->initialTransition = createInitialTransition(states: initialStates);
135 }
136 } else {
137 if (state->initial.isEmpty()) {
138 visit(transition: state->initialTransition);
139 } else {
140 error(location: state->xmlLocation,
141 QStringLiteral("initial transition and initial attribute for state '%1'")
142 .arg(a: state->id));
143 }
144 }
145
146 switch (state->type) {
147 case DocumentModel::State::Normal:
148 break;
149 case DocumentModel::State::Parallel:
150 if (!state->initial.isEmpty()) {
151 error(location: state->xmlLocation,
152 QStringLiteral("parallel states cannot have an initial state"));
153 }
154 break;
155 case DocumentModel::State::Final:
156 break;
157 default:
158 Q_UNREACHABLE();
159 }
160
161 m_parentNodes.append(t: state);
162 return true;
163 }
164
165 void endVisit(DocumentModel::State *) override
166 {
167 m_parentNodes.removeLast();
168 }
169
170 bool visit(DocumentModel::Transition *transition) override
171 {
172 Q_ASSERT(transition->targetStates.isEmpty());
173
174 if (int size = transition->targets.size())
175 transition->targetStates.reserve(asize: size);
176 for (const QString &target : std::as_const(t&: transition->targets)) {
177 if (DocumentModel::AbstractState *s = m_stateById.value(key: target)) {
178 if (transition->targetStates.contains(t: s)) {
179 error(location: transition->xmlLocation, QStringLiteral("duplicate target '%1'").arg(a: target));
180 } else {
181 transition->targetStates.append(t: s);
182 }
183 } else if (!target.isEmpty()) {
184 error(location: transition->xmlLocation, QStringLiteral("unknown state '%1' in target").arg(a: target));
185 }
186 }
187 for (const QString &event : std::as_const(t&: transition->events))
188 checkEvent(event, loc: transition->xmlLocation, wildCardMode: AllowWildCards);
189
190 m_parentNodes.append(t: transition);
191 return true;
192 }
193
194 void endVisit(DocumentModel::Transition *) override
195 {
196 m_parentNodes.removeLast();
197 }
198
199 bool visit(DocumentModel::HistoryState *state) override
200 {
201 bool seenTransition = false;
202 for (DocumentModel::StateOrTransition *sot : std::as_const(t&: state->children)) {
203 if (DocumentModel::State *s = sot->asState()) {
204 error(location: s->xmlLocation, QStringLiteral("history state cannot have substates"));
205 } else if (DocumentModel::Transition *t = sot->asTransition()) {
206 if (seenTransition) {
207 error(location: t->xmlLocation, QStringLiteral("history state can only have one transition"));
208 } else {
209 seenTransition = true;
210 m_parentNodes.append(t: state);
211 t->accept(visitor: this);
212 m_parentNodes.removeLast();
213 }
214 }
215 }
216
217 return false;
218 }
219
220 bool visit(DocumentModel::Send *node) override
221 {
222 checkEvent(event: node->event, loc: node->xmlLocation, wildCardMode: ForbidWildCards);
223 checkExpr(loc: node->xmlLocation, QStringLiteral("send"), QStringLiteral("eventexpr"), attrValue: node->eventexpr);
224 return true;
225 }
226
227 void visit(DocumentModel::Cancel *node) override
228 {
229 checkExpr(loc: node->xmlLocation, QStringLiteral("cancel"), QStringLiteral("sendidexpr"), attrValue: node->sendidexpr);
230 }
231
232 bool visit(DocumentModel::DoneData *node) override
233 {
234 checkExpr(loc: node->xmlLocation, QStringLiteral("donedata"), QStringLiteral("expr"), attrValue: node->expr);
235 return false;
236 }
237
238 bool visit(DocumentModel::Invoke *node) override
239 {
240 if (!node->srcexpr.isEmpty())
241 return false;
242
243 if (node->content.isNull()) {
244 error(location: node->xmlLocation, QStringLiteral("no valid content found in <invoke> tag"));
245 } else {
246 ScxmlVerifier subVerifier(m_errorHandler);
247 m_hasErrors = !subVerifier.verify(doc: node->content.data());
248 }
249 return false;
250 }
251
252private:
253 enum TokenType {
254 XmlNCName,
255 XmlNmtoken,
256 };
257
258 static bool isValidToken(const QString &id, TokenType tokenType)
259 {
260 Q_ASSERT(!id.isEmpty());
261 int i = 0;
262 if (tokenType == XmlNCName) {
263 const QChar c = id.at(i: i++);
264 if (!isLetter(c) && c != QLatin1Char('_'))
265 return false;
266 }
267 for (int ei = id.size(); i != ei; ++i) {
268 const QChar c = id.at(i);
269 if (isLetter(c) || c.isDigit() || c == QLatin1Char('.') || c == QLatin1Char('-')
270 || c == QLatin1Char('_') || isNameTail(c))
271 continue;
272 else if (tokenType == XmlNmtoken && c == QLatin1Char(':'))
273 continue;
274 else
275 return false;
276 }
277
278 return true;
279 }
280
281 static bool isLetter(QChar c)
282 {
283 switch (c.category()) {
284 case QChar::Letter_Lowercase:
285 case QChar::Letter_Uppercase:
286 case QChar::Letter_Other:
287 case QChar::Letter_Titlecase:
288 case QChar::Number_Letter:
289 return true;
290 default:
291 return false;
292 }
293 }
294
295 static bool isNameTail(QChar c)
296 {
297 switch (c.category()) {
298 case QChar::Mark_SpacingCombining:
299 case QChar::Mark_Enclosing:
300 case QChar::Mark_NonSpacing:
301 case QChar::Letter_Modifier:
302 case QChar::Number_DecimalDigit:
303 return true;
304 default:
305 return false;
306 }
307 }
308
309 enum WildCardMode {
310 ForbidWildCards,
311 AllowWildCards
312 };
313
314 void checkEvent(const QString &event, const DocumentModel::XmlLocation &loc,
315 WildCardMode wildCardMode)
316 {
317 if (event.isEmpty())
318 return;
319
320 if (!isValidEvent(event, wildCardMode)) {
321 error(location: loc, QStringLiteral("'%1' is not a valid event").arg(a: event));
322 }
323 }
324
325 static bool isValidEvent(const QString &event, WildCardMode wildCardMode)
326 {
327 if (event.isEmpty())
328 return false;
329
330 if (wildCardMode == AllowWildCards && event == QLatin1String(".*"))
331 return true;
332
333 const QStringList parts = event.split(sep: QLatin1Char('.'));
334
335 for (const QString &part : parts) {
336 if (part.isEmpty())
337 return false;
338
339 if (wildCardMode == AllowWildCards && part.size() == 1
340 && part.at(i: 0) == QLatin1Char('*')) {
341 continue;
342 }
343
344 for (int i = 0, ei = part.size(); i != ei; ++i) {
345 const QChar c = part.at(i);
346 if (!isLetter(c) && !c.isDigit() && c != QLatin1Char('-') && c != QLatin1Char('_')
347 && c != QLatin1Char(':')) {
348 return false;
349 }
350 }
351 }
352
353 return true;
354 }
355
356 static const QList<DocumentModel::StateOrTransition *> &allChildrenOfContainer(
357 DocumentModel::StateContainer *container)
358 {
359 if (auto state = container->asState())
360 return state->children;
361 else if (auto scxml = container->asScxml())
362 return scxml->children;
363 else
364 Q_UNREACHABLE();
365 }
366
367 static DocumentModel::AbstractState *firstAbstractState(DocumentModel::StateContainer *container)
368 {
369 const auto &allChildren = allChildrenOfContainer(container);
370
371 QList<DocumentModel::AbstractState *> childStates;
372 for (DocumentModel::StateOrTransition *child : std::as_const(t: allChildren)) {
373 if (DocumentModel::State *s = child->asState())
374 return s;
375 else if (DocumentModel::HistoryState *h = child->asHistoryState())
376 return h;
377 }
378 return nullptr;
379 }
380
381 static QList<DocumentModel::AbstractState *> allAbstractStates(
382 DocumentModel::StateContainer *container)
383 {
384 const auto &allChildren = allChildrenOfContainer(container);
385
386 QList<DocumentModel::AbstractState *> childStates;
387 for (DocumentModel::StateOrTransition *child : std::as_const(t: allChildren)) {
388 if (DocumentModel::State *s = child->asState())
389 childStates.append(t: s);
390 else if (DocumentModel::HistoryState *h = child->asHistoryState())
391 childStates.append(t: h);
392 }
393 return childStates;
394 }
395
396 DocumentModel::Transition *createInitialTransition(
397 const QList<DocumentModel::AbstractState *> &states)
398 {
399 auto *newTransition = m_doc->newTransition(parent: nullptr, xmlLocation: DocumentModel::XmlLocation(-1, -1));
400 newTransition->type = DocumentModel::Transition::Synthetic;
401 for (auto *s : states) {
402 newTransition->targets.append(t: s->id);
403 }
404
405 newTransition->targetStates = states;
406 return newTransition;
407 }
408
409 void checkExpr(const DocumentModel::XmlLocation &loc, const QString &tag, const QString &attrName, const QString &attrValue)
410 {
411 if (m_doc->root->dataModel == DocumentModel::Scxml::NullDataModel && !attrValue.isEmpty()) {
412 error(location: loc, QStringLiteral(
413 "%1 in <%2> cannot be used with data model 'null'").arg(args: attrName, args: tag));
414 }
415 }
416
417 void error(const DocumentModel::XmlLocation &location, const QString &message)
418 {
419 m_hasErrors = true;
420 if (m_errorHandler)
421 m_errorHandler(location, message);
422 }
423
424private:
425 std::function<void (const DocumentModel::XmlLocation &, const QString &)> m_errorHandler;
426 DocumentModel::ScxmlDocument *m_doc;
427 bool m_hasErrors;
428 QHash<QString, DocumentModel::AbstractState *> m_stateById;
429 QList<DocumentModel::Node *> m_parentNodes;
430};
431
432#ifndef BUILD_QSCXMLC
433class InvokeDynamicScxmlFactory: public QScxmlInvokableServiceFactory
434{
435 Q_OBJECT
436public:
437 InvokeDynamicScxmlFactory(const QScxmlExecutableContent::InvokeInfo &invokeInfo,
438 const QList<QScxmlExecutableContent::StringId> &namelist,
439 const QList<QScxmlExecutableContent::ParameterInfo> &params)
440 : QScxmlInvokableServiceFactory(invokeInfo, namelist, params)
441 {}
442
443 void setContent(const QSharedPointer<DocumentModel::ScxmlDocument> &content)
444 { m_content = content; }
445
446 QScxmlInvokableService *invoke(QScxmlStateMachine *child) override;
447
448private:
449 QSharedPointer<DocumentModel::ScxmlDocument> m_content;
450};
451
452class DynamicStateMachinePrivate : public QScxmlStateMachinePrivate
453{
454 struct DynamicMetaObject : public QAbstractDynamicMetaObject
455 {
456 QMetaObject *toDynamicMetaObject(QObject *) override
457 {
458 return this;
459 }
460
461 int metaCall(QObject *o, QMetaObject::Call c, int id, void **a) override
462 {
463 return o->qt_metacall(c, id, a);
464 }
465 };
466
467public:
468 DynamicStateMachinePrivate() :
469 QScxmlStateMachinePrivate(&QScxmlStateMachine::staticMetaObject)
470 {
471 metaObject = new DynamicMetaObject;
472 }
473
474 void setDynamicMetaObject(const QMetaObject *m) {
475 // Prevent the QML engine from creating a property cache for this thing.
476 static_cast<DynamicMetaObject *>(metaObject)->d = m->d;
477 m_metaObject = m;
478 }
479};
480
481class DynamicStateMachine: public QScxmlStateMachine, public QScxmlInternal::GeneratedTableData
482{
483 Q_DECLARE_PRIVATE(DynamicStateMachine)
484 // Manually expanded from Q_OBJECT macro:
485public:
486 const QMetaObject *metaObject() const override
487 { return d_func()->m_metaObject; }
488
489 int qt_metacall(QMetaObject::Call _c, int _id, void **_a) override
490 {
491 Q_D(DynamicStateMachine);
492 _id = QScxmlStateMachine::qt_metacall(_c, _id, _a);
493 if (_id < 0)
494 return _id;
495 int ownMethodCount = d->m_metaObject->methodCount() - d->m_metaObject->methodOffset();
496 if (_c == QMetaObject::InvokeMetaMethod) {
497 if (_id < ownMethodCount)
498 qt_static_metacall(o: this, _c, _id, _a);
499 _id -= ownMethodCount;
500 } else if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty
501 || _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) {
502 qt_static_metacall(o: this, _c, _id, _a);
503 _id -= d->m_metaObject->propertyCount();
504 }
505 return _id;
506 }
507
508private:
509 static void qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
510 {
511 if (_c == QMetaObject::RegisterPropertyMetaType) {
512 *reinterpret_cast<int*>(_a[0]) = qRegisterMetaType<bool>();
513 } else if (_c == QMetaObject::ReadProperty) {
514 DynamicStateMachine *_t = static_cast<DynamicStateMachine *>(_o);
515 void *_v = _a[0];
516 if (_id >= 0 && _id < _t->m_propertyCount) {
517 // getter for the state
518 *reinterpret_cast<bool*>(_v) = _t->isActive(stateIndex: _id);
519 }
520 }
521 }
522 // end of Q_OBJECT macro
523
524private:
525 DynamicStateMachine()
526 : QScxmlStateMachine(*new DynamicStateMachinePrivate)
527 , m_propertyCount(0)
528 {
529 // Temporarily wire up the QMetaObject
530 Q_D(DynamicStateMachine);
531 QMetaObjectBuilder b;
532 b.setClassName("DynamicStateMachine");
533 b.setSuperClass(&QScxmlStateMachine::staticMetaObject);
534 b.setStaticMetacallFunction(qt_static_metacall);
535 d->setDynamicMetaObject(b.toMetaObject());
536 }
537
538 void initDynamicParts(const MetaDataInfo &info)
539 {
540 Q_D(DynamicStateMachine);
541 // Release the temporary QMetaObject.
542 Q_ASSERT(d->m_metaObject != &QScxmlStateMachine::staticMetaObject);
543 free(ptr: const_cast<QMetaObject *>(d->m_metaObject));
544 d->setDynamicMetaObject(&QScxmlStateMachine::staticMetaObject);
545
546 // Build the real one.
547 QMetaObjectBuilder b;
548 b.setClassName("DynamicStateMachine");
549 b.setSuperClass(&QScxmlStateMachine::staticMetaObject);
550 b.setStaticMetacallFunction(qt_static_metacall);
551
552 // signals
553 for (const QString &stateName : info.stateNames) {
554 auto name = stateName.toUtf8();
555 const QByteArray signalName = name + "Changed(bool)";
556 QMetaMethodBuilder signalBuilder = b.addSignal(signature: signalName);
557 signalBuilder.setParameterNames(init(s: "active"));
558 }
559
560 // properties
561 int notifier = 0;
562 for (const QString &stateName : info.stateNames) {
563 QMetaPropertyBuilder prop = b.addProperty(name: stateName.toUtf8(), type: "bool", notifierId: notifier);
564 prop.setWritable(false);
565 ++m_propertyCount;
566 ++notifier;
567 }
568
569 // And we're done
570 d->setDynamicMetaObject(b.toMetaObject());
571 }
572
573public:
574 ~DynamicStateMachine()
575 {
576 Q_D(DynamicStateMachine);
577 if (d->m_metaObject != &QScxmlStateMachine::staticMetaObject) {
578 free(ptr: const_cast<QMetaObject *>(d->m_metaObject));
579 d->setDynamicMetaObject(&QScxmlStateMachine::staticMetaObject);
580 }
581 }
582
583 QScxmlInvokableServiceFactory *serviceFactory(int id) const override final
584 { return m_allFactoriesById.at(i: id); }
585
586 static DynamicStateMachine *build(DocumentModel::ScxmlDocument *doc)
587 {
588 auto stateMachine = new DynamicStateMachine;
589 MetaDataInfo info;
590 DataModelInfo dm;
591 auto factoryIdCreator = [stateMachine](
592 const QScxmlExecutableContent::InvokeInfo &invokeInfo,
593 const QList<QScxmlExecutableContent::StringId> &namelist,
594 const QList<QScxmlExecutableContent::ParameterInfo> &params,
595 const QSharedPointer<DocumentModel::ScxmlDocument> &content) -> int {
596 auto factory = new InvokeDynamicScxmlFactory(invokeInfo, namelist, params);
597 factory->setContent(content);
598 stateMachine->m_allFactoriesById.append(t: factory);
599 return stateMachine->m_allFactoriesById.size() - 1;
600 };
601
602 GeneratedTableData::build(doc, table: stateMachine, metaDataInfo: &info, dataModelInfo: &dm, func: factoryIdCreator);
603 stateMachine->setTableData(stateMachine);
604 stateMachine->initDynamicParts(info);
605
606 return stateMachine;
607 }
608
609private:
610 static QList<QByteArray> init(const char *s)
611 {
612#ifdef Q_COMPILER_INITIALIZER_LISTS
613 return QList<QByteArray>({ QByteArray::fromRawData(data: s, size: int(strlen(s: s))) });
614#else // insane compiler:
615 return QList<QByteArray>() << QByteArray::fromRawData(s, int(strlen(s)));
616#endif
617 }
618
619private:
620 QList<QScxmlInvokableServiceFactory *> m_allFactoriesById;
621 int m_propertyCount;
622};
623
624inline QScxmlInvokableService *InvokeDynamicScxmlFactory::invoke(
625 QScxmlStateMachine *parentStateMachine)
626{
627 bool ok = true;
628 auto srcexpr = calculateSrcexpr(parent: parentStateMachine, srcexpr: invokeInfo().expr, ok: &ok);
629 if (!ok)
630 return nullptr;
631
632 if (!srcexpr.isEmpty())
633 return invokeDynamicScxmlService(sourceUrl: srcexpr, parentStateMachine, factory: this);
634
635 auto childStateMachine = DynamicStateMachine::build(doc: m_content.data());
636
637 auto dm = QScxmlDataModelPrivate::instantiateDataModel(type: m_content->root->dataModel);
638 dm->setParent(childStateMachine);
639 childStateMachine->setDataModel(dm);
640
641 return invokeStaticScxmlService(childStateMachine, parentStateMachine, factory: this);
642}
643#endif // BUILD_QSCXMLC
644
645} // anonymous namespace
646
647#ifndef BUILD_QSCXMLC
648QScxmlScxmlService *invokeDynamicScxmlService(const QString &sourceUrl,
649 QScxmlStateMachine *parentStateMachine,
650 QScxmlInvokableServiceFactory *factory)
651{
652 QScxmlCompiler::Loader *loader = parentStateMachine->loader();
653
654 const QString baseDir = sourceUrl.isEmpty() ? QString() : QFileInfo(sourceUrl).path();
655 QStringList errs;
656 const QByteArray data = loader->load(name: sourceUrl, baseDir, errors: &errs);
657
658 if (!errs.isEmpty()) {
659 qWarning() << errs;
660 return nullptr;
661 }
662
663 QXmlStreamReader reader(data);
664 QScxmlCompiler compiler(&reader);
665 compiler.setFileName(sourceUrl);
666 compiler.setLoader(parentStateMachine->loader());
667 compiler.compile();
668 if (!compiler.errors().isEmpty()) {
669 const auto errors = compiler.errors();
670 for (const QScxmlError &error : errors)
671 qWarning().noquote() << error.toString();
672 return nullptr;
673 }
674
675 auto mainDoc = QScxmlCompilerPrivate::get(compiler: &compiler)->scxmlDocument();
676 if (mainDoc == nullptr) {
677 Q_ASSERT(!compiler.errors().isEmpty());
678 const auto errors = compiler.errors();
679 for (const QScxmlError &error : errors)
680 qWarning().noquote() << error.toString();
681 return nullptr;
682 }
683
684 auto childStateMachine = DynamicStateMachine::build(doc: mainDoc);
685
686 auto dm = QScxmlDataModelPrivate::instantiateDataModel(type: mainDoc->root->dataModel);
687 dm->setParent(childStateMachine);
688 childStateMachine->setDataModel(dm);
689
690 return invokeStaticScxmlService(childStateMachine, parentStateMachine, factory);
691}
692#endif // BUILD_QSCXMLC
693
694/*!
695 * \class QScxmlCompiler
696 * \brief The QScxmlCompiler class is a compiler for SCXML files.
697 * \since 5.7
698 * \inmodule QtScxml
699 *
700 * Parses an \l{SCXML Specification}{SCXML} file and dynamically instantiates a
701 * state machine for a successfully parsed SCXML file. If parsing fails, the
702 * new state machine cannot start. All errors are returned by
703 * QScxmlStateMachine::parseErrors().
704 *
705 * To load an SCXML file, QScxmlStateMachine::fromFile or QScxmlStateMachine::fromData should be
706 * used. Using QScxmlCompiler directly is only needed when the compiler needs to use a custom
707 * QScxmlCompiler::Loader.
708 */
709
710/*!
711 * Creates a new SCXML compiler for the specified \a reader.
712 */
713QScxmlCompiler::QScxmlCompiler(QXmlStreamReader *reader)
714 : d(new QScxmlCompilerPrivate(reader))
715{ }
716
717/*!
718 * Destroys the SCXML compiler.
719 */
720QScxmlCompiler::~QScxmlCompiler()
721{
722 delete d;
723}
724
725/*!
726 * Returns the file name associated with the current input.
727 *
728 * \sa setFileName()
729 */
730QString QScxmlCompiler::fileName() const
731{
732 return d->fileName();
733}
734
735/*!
736 * Sets the file name for the current input to \a fileName.
737 *
738 * The file name is used for error reporting and for resolving relative path URIs.
739 *
740 * \sa fileName()
741 */
742void QScxmlCompiler::setFileName(const QString &fileName)
743{
744 d->setFileName(fileName);
745}
746
747/*!
748 * Returns the loader that is currently used to resolve and load URIs for the
749 * SCXML compiler.
750 *
751 * \sa setLoader()
752 */
753QScxmlCompiler::Loader *QScxmlCompiler::loader() const
754{
755 return d->loader();
756}
757
758/*!
759 * Sets \a newLoader to be used for resolving and loading URIs for the SCXML
760 * compiler.
761 *
762 * \sa loader()
763 */
764void QScxmlCompiler::setLoader(QScxmlCompiler::Loader *newLoader)
765{
766 d->setLoader(newLoader);
767}
768
769/*!
770 * Parses an SCXML file and creates a new state machine from it.
771 *
772 * If parsing is successful, the returned state machine can be initialized and started. If
773 * parsing fails, QScxmlStateMachine::parseErrors() can be used to retrieve a list of errors.
774 */
775QScxmlStateMachine *QScxmlCompiler::compile()
776{
777 d->readDocument();
778 if (d->errors().isEmpty()) {
779 // Only verify the document if there were no parse errors: if there were any, the document
780 // is incomplete and will contain errors for sure. There is no need to heap more errors on
781 // top of other errors.
782 d->verifyDocument();
783 }
784 return d->instantiateStateMachine();
785}
786
787/*!
788 * \internal
789 * Instantiates a new state machine from the parsed SCXML.
790 *
791 * If parsing is successful, the returned state machine can be initialized and started. If
792 * parsing fails, QScxmlStateMachine::parseErrors() can be used to retrieve a list of errors.
793 *
794 * \note The instantiated state machine will not have an associated data model set.
795 * \sa QScxmlCompilerPrivate::instantiateDataModel
796 */
797QScxmlStateMachine *QScxmlCompilerPrivate::instantiateStateMachine() const
798{
799#ifdef BUILD_QSCXMLC
800 return nullptr;
801#else // BUILD_QSCXMLC
802 DocumentModel::ScxmlDocument *doc = scxmlDocument();
803 if (doc && doc->root) {
804 auto stateMachine = DynamicStateMachine::build(doc);
805 instantiateDataModel(stateMachine);
806 return stateMachine;
807 } else {
808 class InvalidStateMachine: public QScxmlStateMachine {
809 public:
810 InvalidStateMachine() : QScxmlStateMachine(&QScxmlStateMachine::staticMetaObject)
811 {}
812 };
813
814 auto stateMachine = new InvalidStateMachine;
815 QScxmlStateMachinePrivate::get(t: stateMachine)->parserData()->m_errors = errors();
816 instantiateDataModel(stateMachine);
817 return stateMachine;
818 }
819#endif // BUILD_QSCXMLC
820}
821
822/*!
823 * \internal
824 * Instantiates the data model as described in the SCXML file.
825 *
826 * After instantiation, the \a stateMachine takes ownership of the data model.
827 */
828void QScxmlCompilerPrivate::instantiateDataModel(QScxmlStateMachine *stateMachine) const
829{
830#ifdef BUILD_QSCXMLC
831 Q_UNUSED(stateMachine);
832#else
833 if (!m_errors.isEmpty()) {
834 qWarning() << "SCXML document has errors";
835 return;
836 }
837
838 auto doc = scxmlDocument();
839 auto root = doc ? doc->root : nullptr;
840 if (root == nullptr) {
841 qWarning() << "SCXML document has no root element";
842 } else {
843 QScxmlDataModel *dm = QScxmlDataModelPrivate::instantiateDataModel(type: root->dataModel);
844 QScxmlStateMachinePrivate::get(t: stateMachine)->parserData()->m_ownedDataModel.reset(other: dm);
845 stateMachine->setDataModel(dm);
846 if (dm == nullptr)
847 qWarning() << "No data-model instantiated";
848 }
849#endif // BUILD_QSCXMLC
850}
851
852/*!
853 * Returns the list of parse errors.
854 */
855QList<QScxmlError> QScxmlCompiler::errors() const
856{
857 return d->errors();
858}
859
860bool QScxmlCompilerPrivate::ParserState::collectChars() {
861 switch (kind) {
862 case Content:
863 case Data:
864 case Script:
865 return true;
866 default:
867 break;
868 }
869 return false;
870}
871
872bool QScxmlCompilerPrivate::ParserState::validChild(ParserState::Kind child) const {
873 return validChild(parent: kind, child);
874}
875
876bool QScxmlCompilerPrivate::ParserState::validChild(ParserState::Kind parent, ParserState::Kind child)
877{
878 switch (parent) {
879 case ParserState::Scxml:
880 switch (child) {
881 case ParserState::State:
882 case ParserState::Parallel:
883 case ParserState::Final:
884 case ParserState::DataModel:
885 case ParserState::Script:
886 case ParserState::Transition:
887 return true;
888 default:
889 break;
890 }
891 return false;
892 case ParserState::State:
893 switch (child) {
894 case ParserState::OnEntry:
895 case ParserState::OnExit:
896 case ParserState::Transition:
897 case ParserState::Initial:
898 case ParserState::State:
899 case ParserState::Parallel:
900 case ParserState::Final:
901 case ParserState::History:
902 case ParserState::DataModel:
903 case ParserState::Invoke:
904 return true;
905 default:
906 break;
907 }
908 return false;
909 case ParserState::Parallel:
910 switch (child) {
911 case ParserState::OnEntry:
912 case ParserState::OnExit:
913 case ParserState::Transition:
914 case ParserState::State:
915 case ParserState::Parallel:
916 case ParserState::History:
917 case ParserState::DataModel:
918 case ParserState::Invoke:
919 return true;
920 default:
921 break;
922 }
923 return false;
924 case ParserState::Transition:
925 return isExecutableContent(kind: child);
926 case ParserState::Initial:
927 return (child == ParserState::Transition);
928 case ParserState::Final:
929 switch (child) {
930 case ParserState::OnEntry:
931 case ParserState::OnExit:
932 case ParserState::DoneData:
933 return true;
934 default:
935 break;
936 }
937 return false;
938 case ParserState::OnEntry:
939 case ParserState::OnExit:
940 return isExecutableContent(kind: child);
941 case ParserState::History:
942 return child == ParserState::Transition;
943 case ParserState::Raise:
944 return false;
945 case ParserState::If:
946 return child == ParserState::ElseIf || child == ParserState::Else
947 || isExecutableContent(kind: child);
948 case ParserState::ElseIf:
949 case ParserState::Else:
950 return false;
951 case ParserState::Foreach:
952 return isExecutableContent(kind: child);
953 case ParserState::Log:
954 return false;
955 case ParserState::DataModel:
956 return (child == ParserState::Data);
957 case ParserState::Data:
958 return false;
959 case ParserState::Assign:
960 return false;
961 case ParserState::DoneData:
962 case ParserState::Send:
963 return child == ParserState::Content || child == ParserState::Param;
964 case ParserState::Content:
965 return child == ParserState::Scxml || isExecutableContent(kind: child);
966 case ParserState::Param:
967 case ParserState::Cancel:
968 return false;
969 case ParserState::Finalize:
970 return isExecutableContent(kind: child);
971 case ParserState::Invoke:
972 return child == ParserState::Content || child == ParserState::Finalize
973 || child == ParserState::Param;
974 case ParserState::Script:
975 case ParserState::None:
976 break;
977 }
978 return false;
979}
980
981bool QScxmlCompilerPrivate::ParserState::isExecutableContent(ParserState::Kind kind) {
982 switch (kind) {
983 case Raise:
984 case Send:
985 case Log:
986 case Script:
987 case Assign:
988 case If:
989 case Foreach:
990 case Cancel:
991 case Invoke:
992 return true;
993 default:
994 break;
995 }
996 return false;
997}
998
999QScxmlCompilerPrivate::ParserState::Kind QScxmlCompilerPrivate::ParserState::nameToParserStateKind(QStringView name)
1000{
1001 static QMap<QString, ParserState::Kind> nameToKind;
1002 if (nameToKind.isEmpty()) {
1003 nameToKind.insert(key: QLatin1String("scxml"), value: Scxml);
1004 nameToKind.insert(key: QLatin1String("state"), value: State);
1005 nameToKind.insert(key: QLatin1String("parallel"), value: Parallel);
1006 nameToKind.insert(key: QLatin1String("transition"), value: Transition);
1007 nameToKind.insert(key: QLatin1String("initial"), value: Initial);
1008 nameToKind.insert(key: QLatin1String("final"), value: Final);
1009 nameToKind.insert(key: QLatin1String("onentry"), value: OnEntry);
1010 nameToKind.insert(key: QLatin1String("onexit"), value: OnExit);
1011 nameToKind.insert(key: QLatin1String("history"), value: History);
1012 nameToKind.insert(key: QLatin1String("raise"), value: Raise);
1013 nameToKind.insert(key: QLatin1String("if"), value: If);
1014 nameToKind.insert(key: QLatin1String("elseif"), value: ElseIf);
1015 nameToKind.insert(key: QLatin1String("else"), value: Else);
1016 nameToKind.insert(key: QLatin1String("foreach"), value: Foreach);
1017 nameToKind.insert(key: QLatin1String("log"), value: Log);
1018 nameToKind.insert(key: QLatin1String("datamodel"), value: DataModel);
1019 nameToKind.insert(key: QLatin1String("data"), value: Data);
1020 nameToKind.insert(key: QLatin1String("assign"), value: Assign);
1021 nameToKind.insert(key: QLatin1String("donedata"), value: DoneData);
1022 nameToKind.insert(key: QLatin1String("content"), value: Content);
1023 nameToKind.insert(key: QLatin1String("param"), value: Param);
1024 nameToKind.insert(key: QLatin1String("script"), value: Script);
1025 nameToKind.insert(key: QLatin1String("send"), value: Send);
1026 nameToKind.insert(key: QLatin1String("cancel"), value: Cancel);
1027 nameToKind.insert(key: QLatin1String("invoke"), value: Invoke);
1028 nameToKind.insert(key: QLatin1String("finalize"), value: Finalize);
1029 }
1030 QMap<QString, ParserState::Kind>::ConstIterator it = nameToKind.constBegin();
1031 const QMap<QString, ParserState::Kind>::ConstIterator itEnd = nameToKind.constEnd();
1032 while (it != itEnd) {
1033 if (it.key() == name)
1034 return it.value();
1035 ++it;
1036 }
1037 return None;
1038}
1039
1040QStringList QScxmlCompilerPrivate::ParserState::requiredAttributes(QScxmlCompilerPrivate::ParserState::Kind kind)
1041{
1042 switch (kind) {
1043 case Scxml: return QStringList() << QStringLiteral("version");
1044 case State: return QStringList();
1045 case Parallel: return QStringList();
1046 case Transition: return QStringList();
1047 case Initial: return QStringList();
1048 case Final: return QStringList();
1049 case OnEntry: return QStringList();
1050 case OnExit: return QStringList();
1051 case History: return QStringList();
1052 case Raise: return QStringList() << QStringLiteral("event");
1053 case If: return QStringList() << QStringLiteral("cond");
1054 case ElseIf: return QStringList() << QStringLiteral("cond");
1055 case Else: return QStringList();
1056 case Foreach: return QStringList() << QStringLiteral("array")
1057 << QStringLiteral("item");
1058 case Log: return QStringList();
1059 case DataModel: return QStringList();
1060 case Data: return QStringList() << QStringLiteral("id");
1061 case Assign: return QStringList() << QStringLiteral("location");
1062 case DoneData: return QStringList();
1063 case Content: return QStringList();
1064 case Param: return QStringList() << QStringLiteral("name");
1065 case Script: return QStringList();
1066 case Send: return QStringList();
1067 case Cancel: return QStringList();
1068 case Invoke: return QStringList();
1069 case Finalize: return QStringList();
1070 default: return QStringList();
1071 }
1072 return QStringList();
1073}
1074
1075QStringList QScxmlCompilerPrivate::ParserState::optionalAttributes(QScxmlCompilerPrivate::ParserState::Kind kind)
1076{
1077 switch (kind) {
1078 case Scxml: return QStringList() << QStringLiteral("initial")
1079 << QStringLiteral("datamodel")
1080 << QStringLiteral("binding")
1081 << QStringLiteral("name");
1082 case State: return QStringList() << QStringLiteral("id")
1083 << QStringLiteral("initial");
1084 case Parallel: return QStringList() << QStringLiteral("id");
1085 case Transition: return QStringList() << QStringLiteral("event")
1086 << QStringLiteral("cond")
1087 << QStringLiteral("target")
1088 << QStringLiteral("type");
1089 case Initial: return QStringList();
1090 case Final: return QStringList() << QStringLiteral("id");
1091 case OnEntry: return QStringList();
1092 case OnExit: return QStringList();
1093 case History: return QStringList() << QStringLiteral("id")
1094 << QStringLiteral("type");
1095 case Raise: return QStringList();
1096 case If: return QStringList();
1097 case ElseIf: return QStringList();
1098 case Else: return QStringList();
1099 case Foreach: return QStringList() << QStringLiteral("index");
1100 case Log: return QStringList() << QStringLiteral("label")
1101 << QStringLiteral("expr");
1102 case DataModel: return QStringList();
1103 case Data: return QStringList() << QStringLiteral("src")
1104 << QStringLiteral("expr");
1105 case Assign: return QStringList() << QStringLiteral("expr");
1106 case DoneData: return QStringList();
1107 case Content: return QStringList() << QStringLiteral("expr");
1108 case Param: return QStringList() << QStringLiteral("expr")
1109 << QStringLiteral("location");
1110 case Script: return QStringList() << QStringLiteral("src");
1111 case Send: return QStringList() << QStringLiteral("event")
1112 << QStringLiteral("eventexpr")
1113 << QStringLiteral("id")
1114 << QStringLiteral("idlocation")
1115 << QStringLiteral("type")
1116 << QStringLiteral("typeexpr")
1117 << QStringLiteral("namelist")
1118 << QStringLiteral("delay")
1119 << QStringLiteral("delayexpr")
1120 << QStringLiteral("target")
1121 << QStringLiteral("targetexpr");
1122 case Cancel: return QStringList() << QStringLiteral("sendid")
1123 << QStringLiteral("sendidexpr");
1124 case Invoke: return QStringList() << QStringLiteral("type")
1125 << QStringLiteral("typeexpr")
1126 << QStringLiteral("src")
1127 << QStringLiteral("srcexpr")
1128 << QStringLiteral("id")
1129 << QStringLiteral("idlocation")
1130 << QStringLiteral("namelist")
1131 << QStringLiteral("autoforward");
1132 case Finalize: return QStringList();
1133 default: return QStringList();
1134 }
1135 return QStringList();
1136}
1137
1138DocumentModel::Node::~Node()
1139{
1140}
1141
1142DocumentModel::AbstractState *DocumentModel::Node::asAbstractState()
1143{
1144 if (State *state = asState())
1145 return state;
1146 if (HistoryState *history = asHistoryState())
1147 return history;
1148 return nullptr;
1149}
1150
1151void DocumentModel::DataElement::accept(DocumentModel::NodeVisitor *visitor)
1152{
1153 visitor->visit(this);
1154}
1155
1156void DocumentModel::Param::accept(DocumentModel::NodeVisitor *visitor)
1157{
1158 visitor->visit(this);
1159}
1160
1161void DocumentModel::DoneData::accept(DocumentModel::NodeVisitor *visitor)
1162{
1163 if (visitor->visit(this)) {
1164 for (Param *param : std::as_const(t&: params))
1165 param->accept(visitor);
1166 }
1167 visitor->endVisit(this);
1168}
1169
1170void DocumentModel::Send::accept(DocumentModel::NodeVisitor *visitor)
1171{
1172 if (visitor->visit(this)) {
1173 visitor->visit(params);
1174 }
1175 visitor->endVisit(this);
1176}
1177
1178void DocumentModel::Invoke::accept(DocumentModel::NodeVisitor *visitor)
1179{
1180 if (visitor->visit(this)) {
1181 visitor->visit(params);
1182 visitor->visit(sequence: &finalize);
1183 }
1184 visitor->endVisit(this);
1185}
1186
1187void DocumentModel::Raise::accept(DocumentModel::NodeVisitor *visitor)
1188{
1189 visitor->visit(this);
1190}
1191
1192void DocumentModel::Log::accept(DocumentModel::NodeVisitor *visitor)
1193{
1194 visitor->visit(this);
1195}
1196
1197void DocumentModel::Script::accept(DocumentModel::NodeVisitor *visitor)
1198{
1199 visitor->visit(this);
1200}
1201
1202void DocumentModel::Assign::accept(DocumentModel::NodeVisitor *visitor)
1203{
1204 visitor->visit(this);
1205}
1206
1207void DocumentModel::If::accept(DocumentModel::NodeVisitor *visitor)
1208{
1209 if (visitor->visit(this)) {
1210 visitor->visit(sequences: blocks);
1211 }
1212 visitor->endVisit(this);
1213}
1214
1215void DocumentModel::Foreach::accept(DocumentModel::NodeVisitor *visitor)
1216{
1217 if (visitor->visit(this)) {
1218 visitor->visit(sequence: &block);
1219 }
1220 visitor->endVisit(this);
1221}
1222
1223void DocumentModel::Cancel::accept(DocumentModel::NodeVisitor *visitor)
1224{
1225 visitor->visit(this);
1226}
1227
1228void DocumentModel::State::accept(DocumentModel::NodeVisitor *visitor)
1229{
1230 if (visitor->visit(this)) {
1231 visitor->visit(dataElements);
1232 visitor->visit(children);
1233 visitor->visit(sequences: onEntry);
1234 visitor->visit(sequences: onExit);
1235 if (doneData)
1236 doneData->accept(visitor);
1237 for (Invoke *invoke : std::as_const(t&: invokes))
1238 invoke->accept(visitor);
1239 }
1240 visitor->endVisit(this);
1241}
1242
1243void DocumentModel::Transition::accept(DocumentModel::NodeVisitor *visitor)
1244{
1245 if (visitor->visit(this)) {
1246 visitor->visit(sequence: &instructionsOnTransition);
1247 }
1248 visitor->endVisit(this);
1249}
1250
1251void DocumentModel::HistoryState::accept(DocumentModel::NodeVisitor *visitor)
1252{
1253 if (visitor->visit(this)) {
1254 if (Transition *t = defaultConfiguration())
1255 t->accept(visitor);
1256 }
1257 visitor->endVisit(this);
1258}
1259
1260void DocumentModel::Scxml::accept(DocumentModel::NodeVisitor *visitor)
1261{
1262 if (visitor->visit(this)) {
1263 visitor->visit(children);
1264 visitor->visit(dataElements);
1265 if (script)
1266 script->accept(visitor);
1267 visitor->visit(sequence: &initialSetup);
1268 }
1269 visitor->endVisit(this);
1270}
1271
1272DocumentModel::NodeVisitor::~NodeVisitor()
1273{}
1274
1275/*!
1276 * \class QScxmlCompiler::Loader
1277 * \brief The Loader class is a URI resolver and resource loader for an SCXML compiler.
1278 * \since 5.8
1279 * \inmodule QtScxml
1280 */
1281
1282/*!
1283 * Creates a new loader.
1284 */
1285QScxmlCompiler::Loader::Loader()
1286{
1287}
1288
1289/*!
1290 * Destroys the loader.
1291 */
1292QScxmlCompiler::Loader::~Loader()
1293{}
1294
1295/*!
1296 * \fn QScxmlCompiler::Loader::load(const QString &name, const QString &baseDir, QStringList *errors)
1297 * Resolves the URI \a name and loads an SCXML file from the directory
1298 * specified by \a baseDir. \a errors contains information about the errors that
1299 * might have occurred.
1300 *
1301 * Returns a QByteArray that stores the contents of the file.
1302 */
1303
1304QScxmlCompilerPrivate *QScxmlCompilerPrivate::get(QScxmlCompiler *compiler)
1305{
1306 return compiler->d;
1307}
1308
1309QScxmlCompilerPrivate::QScxmlCompilerPrivate(QXmlStreamReader *reader)
1310 : m_currentState(nullptr)
1311 , m_loader(&m_defaultLoader)
1312 , m_reader(reader)
1313{}
1314
1315bool QScxmlCompilerPrivate::verifyDocument()
1316{
1317 if (!m_doc)
1318 return false;
1319
1320 auto handler = [this](const DocumentModel::XmlLocation &location, const QString &msg) {
1321 this->addError(location, msg);
1322 };
1323
1324 if (ScxmlVerifier(handler).verify(doc: m_doc.get()))
1325 return true;
1326 else
1327 return false;
1328}
1329
1330DocumentModel::ScxmlDocument *QScxmlCompilerPrivate::scxmlDocument() const
1331{
1332 return m_doc && m_errors.isEmpty() ? m_doc.get() : nullptr;
1333}
1334
1335QString QScxmlCompilerPrivate::fileName() const
1336{
1337 return m_fileName;
1338}
1339
1340void QScxmlCompilerPrivate::setFileName(const QString &fileName)
1341{
1342 m_fileName = fileName;
1343}
1344
1345QScxmlCompiler::Loader *QScxmlCompilerPrivate::loader() const
1346{
1347 return m_loader;
1348}
1349
1350void QScxmlCompilerPrivate::setLoader(QScxmlCompiler::Loader *loader)
1351{
1352 m_loader = loader;
1353}
1354
1355void QScxmlCompilerPrivate::parseSubDocument(DocumentModel::Invoke *parentInvoke,
1356 QXmlStreamReader *reader,
1357 const QString &fileName)
1358{
1359 QScxmlCompiler p(reader);
1360 p.setFileName(fileName);
1361 p.setLoader(loader());
1362 p.d->readDocument();
1363 parentInvoke->content.reset(t: p.d->m_doc.release());
1364 m_doc->allSubDocuments.append(t: parentInvoke->content.data());
1365 m_errors.append(other: p.errors());
1366}
1367
1368bool QScxmlCompilerPrivate::parseSubElement(DocumentModel::Invoke *parentInvoke,
1369 QXmlStreamReader *reader,
1370 const QString &fileName)
1371{
1372 QScxmlCompiler p(reader);
1373 p.setFileName(fileName);
1374 p.setLoader(loader());
1375 p.d->resetDocument();
1376 bool ok = p.d->readElement();
1377 parentInvoke->content.reset(t: p.d->m_doc.release());
1378 m_doc->allSubDocuments.append(t: parentInvoke->content.data());
1379 m_errors.append(other: p.errors());
1380 return ok;
1381}
1382
1383bool QScxmlCompilerPrivate::preReadElementScxml()
1384{
1385 if (m_doc->root) {
1386 addError(msg: QLatin1String("Doc root already allocated"));
1387 return false;
1388 }
1389 m_doc->root = new DocumentModel::Scxml(xmlLocation());
1390
1391 auto scxml = m_doc->root;
1392 const QXmlStreamAttributes attributes = m_reader->attributes();
1393 if (attributes.hasAttribute(QStringLiteral("initial"))) {
1394 const QString initial = attributes.value(QStringLiteral("initial")).toString();
1395 scxml->initial += initial.split(sep: QChar::Space, behavior: Qt::SkipEmptyParts);
1396 }
1397
1398 const QStringView datamodel = attributes.value(qualifiedName: QLatin1String("datamodel"));
1399 if (datamodel.isEmpty() || datamodel == QLatin1String("null")) {
1400 scxml->dataModel = DocumentModel::Scxml::NullDataModel;
1401 } else if (datamodel == QLatin1String("ecmascript")) {
1402 scxml->dataModel = DocumentModel::Scxml::JSDataModel;
1403 } else if (datamodel.startsWith(s: QLatin1String("cplusplus"))) {
1404 scxml->dataModel = DocumentModel::Scxml::CppDataModel;
1405 int firstColon = datamodel.indexOf(c: QLatin1Char(':'));
1406 if (firstColon == -1) {
1407 scxml->cppDataModelClassName = attributes.value(QStringLiteral("name")).toString() + QStringLiteral("DataModel");
1408 scxml->cppDataModelHeaderName = scxml->cppDataModelClassName + QStringLiteral(".h");
1409 } else {
1410 int lastColon = datamodel.lastIndexOf(c: QLatin1Char(':'));
1411 if (lastColon == -1) {
1412 lastColon = datamodel.size();
1413 } else {
1414 scxml->cppDataModelHeaderName = datamodel.mid(pos: lastColon + 1).toString();
1415 }
1416 scxml->cppDataModelClassName = datamodel.mid(pos: firstColon + 1, n: lastColon - firstColon - 1).toString();
1417 }
1418 } else {
1419 addError(QStringLiteral("Unsupported data model '%1' in scxml")
1420 .arg(a: datamodel.toString()));
1421 }
1422 const QStringView binding = attributes.value(qualifiedName: QLatin1String("binding"));
1423 if (binding.isEmpty() || binding == QLatin1String("early")) {
1424 scxml->binding = DocumentModel::Scxml::EarlyBinding;
1425 } else if (binding == QLatin1String("late")) {
1426 scxml->binding = DocumentModel::Scxml::LateBinding;
1427 } else {
1428 addError(QStringLiteral("Unsupperted binding type '%1'")
1429 .arg(a: binding.toString()));
1430 return false;
1431 }
1432 const QStringView name = attributes.value(qualifiedName: QLatin1String("name"));
1433 if (!name.isEmpty()) {
1434 scxml->name = name.toString();
1435 }
1436 m_currentState = m_doc->root;
1437 current().instructionContainer = &m_doc->root->initialSetup;
1438 return true;
1439}
1440
1441
1442bool QScxmlCompilerPrivate::preReadElementState()
1443{
1444 const QXmlStreamAttributes attributes = m_reader->attributes();
1445 auto newState = m_doc->newState(parent: m_currentState, type: DocumentModel::State::Normal, xmlLocation: xmlLocation());
1446 if (!maybeId(attributes, id: &newState->id))
1447 return false;
1448
1449 if (attributes.hasAttribute(QStringLiteral("initial"))) {
1450 const QString initial = attributes.value(QStringLiteral("initial")).toString();
1451 newState->initial += initial.split(sep: QChar::Space, behavior: Qt::SkipEmptyParts);
1452 }
1453 m_currentState = newState;
1454 return true;
1455}
1456
1457bool QScxmlCompilerPrivate::preReadElementParallel()
1458{
1459 const QXmlStreamAttributes attributes = m_reader->attributes();
1460 auto newState = m_doc->newState(parent: m_currentState, type: DocumentModel::State::Parallel, xmlLocation: xmlLocation());
1461 if (!maybeId(attributes, id: &newState->id))
1462 return false;
1463
1464 m_currentState = newState;
1465 return true;
1466}
1467
1468bool QScxmlCompilerPrivate::preReadElementInitial()
1469{
1470 DocumentModel::AbstractState *parent = currentParent();
1471 if (!parent) {
1472 addError(QStringLiteral("<initial> found outside a state"));
1473 return false;
1474 }
1475
1476 DocumentModel::State *parentState = parent->asState();
1477 if (!parentState) {
1478 addError(QStringLiteral("<initial> found outside a state"));
1479 return false;
1480 }
1481
1482 if (parentState->type == DocumentModel::State::Parallel) {
1483 addError(QStringLiteral("Explicit initial state for parallel states not supported (only implicitly through the initial states of its substates)"));
1484 return false;
1485 }
1486 return true;
1487}
1488
1489bool QScxmlCompilerPrivate::preReadElementTransition()
1490{
1491 // Parser stack at this point:
1492 // <transition>
1493 // <initial>
1494 // <state> or <scxml>
1495 //
1496 // Or:
1497 // <transition>
1498 // <state> or <scxml>
1499
1500 DocumentModel::Transition *transition = nullptr;
1501 if (previous().kind == ParserState::Initial) {
1502 transition = m_doc->newTransition(parent: nullptr, xmlLocation: xmlLocation());
1503 const auto &initialParentState = m_stack.at(i: m_stack.size() - 3);
1504 if (initialParentState.kind == ParserState::Scxml) {
1505 m_currentState->asScxml()->initialTransition = transition;
1506 } else if (initialParentState.kind == ParserState::State) {
1507 m_currentState->asState()->initialTransition = transition;
1508 } else {
1509 Q_UNREACHABLE();
1510 }
1511 } else {
1512 transition = m_doc->newTransition(parent: m_currentState, xmlLocation: xmlLocation());
1513 }
1514
1515 const QXmlStreamAttributes attributes = m_reader->attributes();
1516 transition->events = attributes.value(qualifiedName: QLatin1String("event")).toString().split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
1517 transition->targets = attributes.value(qualifiedName: QLatin1String("target")).toString().split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
1518 if (attributes.hasAttribute(QStringLiteral("cond")))
1519 transition->condition.reset(other: new QString(attributes.value(qualifiedName: QLatin1String("cond")).toString()));
1520 QStringView type = attributes.value(qualifiedName: QLatin1String("type"));
1521 if (type.isEmpty() || type == QLatin1String("external")) {
1522 transition->type = DocumentModel::Transition::External;
1523 } else if (type == QLatin1String("internal")) {
1524 transition->type = DocumentModel::Transition::Internal;
1525 } else {
1526 addError(QStringLiteral("invalid transition type '%1', valid values are 'external' and 'internal'").arg(a: type.toString()));
1527 return true; // TODO: verify me
1528 }
1529 current().instructionContainer = &transition->instructionsOnTransition;
1530 return true;
1531}
1532
1533bool QScxmlCompilerPrivate::preReadElementFinal()
1534{
1535 const QXmlStreamAttributes attributes = m_reader->attributes();
1536 auto newState = m_doc->newState(parent: m_currentState, type: DocumentModel::State::Final, xmlLocation: xmlLocation());
1537 if (!maybeId(attributes, id: &newState->id))
1538 return false;
1539 m_currentState = newState;
1540 return true;
1541}
1542
1543bool QScxmlCompilerPrivate::preReadElementHistory()
1544{
1545 const QXmlStreamAttributes attributes = m_reader->attributes();
1546
1547 DocumentModel::AbstractState *parent = currentParent();
1548 if (!parent) {
1549 addError(QStringLiteral("<history> found outside a state"));
1550 return false;
1551 }
1552 auto newState = m_doc->newHistoryState(parent, xmlLocation: xmlLocation());
1553 if (!maybeId(attributes, id: &newState->id))
1554 return false;
1555
1556 const QStringView type = attributes.value(qualifiedName: QLatin1String("type"));
1557 if (type.isEmpty() || type == QLatin1String("shallow")) {
1558 newState->type = DocumentModel::HistoryState::Shallow;
1559 } else if (type == QLatin1String("deep")) {
1560 newState->type = DocumentModel::HistoryState::Deep;
1561 } else {
1562 addError(QStringLiteral("invalid history type %1, valid values are 'shallow' and 'deep'").arg(a: type.toString()));
1563 return false;
1564 }
1565 m_currentState = newState;
1566 return true;
1567}
1568
1569bool QScxmlCompilerPrivate::preReadElementOnEntry()
1570{
1571 const ParserState::Kind previousKind = previous().kind;
1572 switch (previousKind) {
1573 case ParserState::Final:
1574 case ParserState::State:
1575 case ParserState::Parallel:
1576 if (DocumentModel::State *s = m_currentState->asState()) {
1577 current().instructionContainer = m_doc->newSequence(container: &s->onEntry);
1578 break;
1579 }
1580 Q_FALLTHROUGH();
1581 default:
1582 addError(QStringLiteral("unexpected container state for onentry"));
1583 break;
1584 }
1585 return true;
1586}
1587
1588bool QScxmlCompilerPrivate::preReadElementOnExit()
1589{
1590 ParserState::Kind previousKind = previous().kind;
1591 switch (previousKind) {
1592 case ParserState::Final:
1593 case ParserState::State:
1594 case ParserState::Parallel:
1595 if (DocumentModel::State *s = m_currentState->asState()) {
1596 current().instructionContainer = m_doc->newSequence(container: &s->onExit);
1597 break;
1598 }
1599 Q_FALLTHROUGH();
1600 default:
1601 addError(QStringLiteral("unexpected container state for onexit"));
1602 break;
1603 }
1604 return true;
1605}
1606
1607bool QScxmlCompilerPrivate::preReadElementRaise()
1608{
1609 const QXmlStreamAttributes attributes = m_reader->attributes();
1610 auto raise = m_doc->newNode<DocumentModel::Raise>(xmlLocation: xmlLocation());
1611 raise->event = attributes.value(qualifiedName: QLatin1String("event")).toString();
1612 current().instruction = raise;
1613 return true;
1614}
1615
1616bool QScxmlCompilerPrivate::preReadElementIf()
1617{
1618 const QXmlStreamAttributes attributes = m_reader->attributes();
1619 auto *ifI = m_doc->newNode<DocumentModel::If>(xmlLocation: xmlLocation());
1620 current().instruction = ifI;
1621 ifI->conditions.append(t: attributes.value(qualifiedName: QLatin1String("cond")).toString());
1622 current().instructionContainer = m_doc->newSequence(container: &ifI->blocks);
1623 return true;
1624}
1625
1626bool QScxmlCompilerPrivate::preReadElementElseIf()
1627{
1628 const QXmlStreamAttributes attributes = m_reader->attributes();
1629
1630 DocumentModel::If *ifI = lastIf();
1631 if (!ifI)
1632 return false;
1633
1634 ifI->conditions.append(t: attributes.value(qualifiedName: QLatin1String("cond")).toString());
1635 previous().instructionContainer = m_doc->newSequence(container: &ifI->blocks);
1636 return true;
1637}
1638
1639bool QScxmlCompilerPrivate::preReadElementElse()
1640{
1641 DocumentModel::If *ifI = lastIf();
1642 if (!ifI)
1643 return false;
1644
1645 previous().instructionContainer = m_doc->newSequence(container: &ifI->blocks);
1646 return true;
1647}
1648
1649bool QScxmlCompilerPrivate::preReadElementForeach()
1650{
1651 const QXmlStreamAttributes attributes = m_reader->attributes();
1652 auto foreachI = m_doc->newNode<DocumentModel::Foreach>(xmlLocation: xmlLocation());
1653 foreachI->array = attributes.value(qualifiedName: QLatin1String("array")).toString();
1654 foreachI->item = attributes.value(qualifiedName: QLatin1String("item")).toString();
1655 foreachI->index = attributes.value(qualifiedName: QLatin1String("index")).toString();
1656 current().instruction = foreachI;
1657 current().instructionContainer = &foreachI->block;
1658 return true;
1659}
1660
1661bool QScxmlCompilerPrivate::preReadElementLog()
1662{
1663 const QXmlStreamAttributes attributes = m_reader->attributes();
1664 auto logI = m_doc->newNode<DocumentModel::Log>(xmlLocation: xmlLocation());
1665 logI->label = attributes.value(qualifiedName: QLatin1String("label")).toString();
1666 logI->expr = attributes.value(qualifiedName: QLatin1String("expr")).toString();
1667 current().instruction = logI;
1668 return true;
1669}
1670
1671bool QScxmlCompilerPrivate::preReadElementDataModel()
1672{
1673 return true;
1674}
1675
1676bool QScxmlCompilerPrivate::preReadElementData()
1677{
1678 const QXmlStreamAttributes attributes = m_reader->attributes();
1679 auto data = m_doc->newNode<DocumentModel::DataElement>(xmlLocation: xmlLocation());
1680 data->id = attributes.value(qualifiedName: QLatin1String("id")).toString();
1681 data->src = attributes.value(qualifiedName: QLatin1String("src")).toString();
1682 data->expr = attributes.value(qualifiedName: QLatin1String("expr")).toString();
1683 if (DocumentModel::Scxml *scxml = m_currentState->asScxml()) {
1684 scxml->dataElements.append(t: data);
1685 } else if (DocumentModel::State *state = m_currentState->asState()) {
1686 state->dataElements.append(t: data);
1687 } else {
1688 Q_UNREACHABLE();
1689 }
1690 return true;
1691}
1692
1693bool QScxmlCompilerPrivate::preReadElementAssign()
1694{
1695 const QXmlStreamAttributes attributes = m_reader->attributes();
1696 auto assign = m_doc->newNode<DocumentModel::Assign>(xmlLocation: xmlLocation());
1697 assign->location = attributes.value(qualifiedName: QLatin1String("location")).toString();
1698 assign->expr = attributes.value(qualifiedName: QLatin1String("expr")).toString();
1699 current().instruction = assign;
1700 return true;
1701}
1702
1703bool QScxmlCompilerPrivate::preReadElementDoneData()
1704{
1705 DocumentModel::State *s = m_currentState->asState();
1706 if (s && s->type == DocumentModel::State::Final) {
1707 if (s->doneData) {
1708 addError(msg: QLatin1String("state can only have one donedata"));
1709 } else {
1710 s->doneData = m_doc->newNode<DocumentModel::DoneData>(xmlLocation: xmlLocation());
1711 }
1712 } else {
1713 addError(QStringLiteral("donedata can only occur in a final state"));
1714 }
1715 return true;
1716}
1717
1718bool QScxmlCompilerPrivate::preReadElementContent()
1719{
1720 const QXmlStreamAttributes attributes = m_reader->attributes();
1721 ParserState::Kind previousKind = previous().kind;
1722 switch (previousKind) {
1723 case ParserState::DoneData: {
1724 DocumentModel::State *s = m_currentState->asState();
1725 Q_ASSERT(s);
1726 s->doneData->expr = attributes.value(qualifiedName: QLatin1String("expr")).toString();
1727 } break;
1728 case ParserState::Send: {
1729 DocumentModel::Send *s = previous().instruction->asSend();
1730 Q_ASSERT(s);
1731 s->contentexpr = attributes.value(qualifiedName: QLatin1String("expr")).toString();
1732 } break;
1733 case ParserState::Invoke: {
1734 DocumentModel::Invoke *i = previous().instruction->asInvoke();
1735 Q_ASSERT(i);
1736 Q_UNUSED(i);
1737 if (attributes.hasAttribute(QStringLiteral("expr"))) {
1738 addError(QStringLiteral("expr attribute in content of invoke is not supported"));
1739 break;
1740 }
1741 } break;
1742 default:
1743 addError(QStringLiteral("unexpected parent of content %1").arg(a: previous().kind));
1744 }
1745 return true;
1746}
1747
1748bool QScxmlCompilerPrivate::preReadElementParam()
1749{
1750 const QXmlStreamAttributes attributes = m_reader->attributes();
1751 auto param = m_doc->newNode<DocumentModel::Param>(xmlLocation: xmlLocation());
1752 param->name = attributes.value(qualifiedName: QLatin1String("name")).toString();
1753 param->expr = attributes.value(qualifiedName: QLatin1String("expr")).toString();
1754 param->location = attributes.value(qualifiedName: QLatin1String("location")).toString();
1755
1756 ParserState::Kind previousKind = previous().kind;
1757 switch (previousKind) {
1758 case ParserState::DoneData: {
1759 DocumentModel::State *s = m_currentState->asState();
1760 Q_ASSERT(s);
1761 Q_ASSERT(s->doneData);
1762 s->doneData->params.append(t: param);
1763 } break;
1764 case ParserState::Send: {
1765 DocumentModel::Send *s = previous().instruction->asSend();
1766 Q_ASSERT(s);
1767 s->params.append(t: param);
1768 } break;
1769 case ParserState::Invoke: {
1770 DocumentModel::Invoke *i = previous().instruction->asInvoke();
1771 Q_ASSERT(i);
1772 i->params.append(t: param);
1773 } break;
1774 default:
1775 addError(QStringLiteral("unexpected parent of param %1").arg(a: previous().kind));
1776 }
1777 return true;
1778}
1779
1780bool QScxmlCompilerPrivate::preReadElementScript()
1781{
1782 const QXmlStreamAttributes attributes = m_reader->attributes();
1783 auto *script = m_doc->newNode<DocumentModel::Script>(xmlLocation: xmlLocation());
1784 script->src = attributes.value(qualifiedName: QLatin1String("src")).toString();
1785 current().instruction = script;
1786 return true;
1787}
1788
1789bool QScxmlCompilerPrivate::preReadElementSend()
1790{
1791 const QXmlStreamAttributes attributes = m_reader->attributes();
1792 auto *send = m_doc->newNode<DocumentModel::Send>(xmlLocation: xmlLocation());
1793 send->event = attributes.value(qualifiedName: QLatin1String("event")).toString();
1794 send->eventexpr = attributes.value(qualifiedName: QLatin1String("eventexpr")).toString();
1795 send->delay = attributes.value(qualifiedName: QLatin1String("delay")).toString();
1796 send->delayexpr = attributes.value(qualifiedName: QLatin1String("delayexpr")).toString();
1797 send->id = attributes.value(qualifiedName: QLatin1String("id")).toString();
1798 send->idLocation = attributes.value(qualifiedName: QLatin1String("idlocation")).toString();
1799 send->type = attributes.value(qualifiedName: QLatin1String("type")).toString();
1800 send->typeexpr = attributes.value(qualifiedName: QLatin1String("typeexpr")).toString();
1801 send->target = attributes.value(qualifiedName: QLatin1String("target")).toString();
1802 send->targetexpr = attributes.value(qualifiedName: QLatin1String("targetexpr")).toString();
1803 if (attributes.hasAttribute(qualifiedName: QLatin1String("namelist")))
1804 send->namelist = attributes.value(qualifiedName: QLatin1String("namelist")).toString().split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
1805 current().instruction = send;
1806 return true;
1807}
1808
1809bool QScxmlCompilerPrivate::preReadElementCancel()
1810{
1811 const QXmlStreamAttributes attributes = m_reader->attributes();
1812 auto *cancel = m_doc->newNode<DocumentModel::Cancel>(xmlLocation: xmlLocation());
1813 cancel->sendid = attributes.value(qualifiedName: QLatin1String("sendid")).toString();
1814 cancel->sendidexpr = attributes.value(qualifiedName: QLatin1String("sendidexpr")).toString();
1815 current().instruction = cancel;
1816 return true;
1817}
1818
1819bool QScxmlCompilerPrivate::preReadElementInvoke()
1820{
1821 const QXmlStreamAttributes attributes = m_reader->attributes();
1822 DocumentModel::State *parentState = m_currentState->asState();
1823 if (!parentState ||
1824 (parentState->type != DocumentModel::State::Normal && parentState->type != DocumentModel::State::Parallel)) {
1825 addError(QStringLiteral("invoke can only occur in <state> or <parallel>"));
1826 return true; // TODO: verify me
1827 }
1828 auto *invoke = m_doc->newNode<DocumentModel::Invoke>(xmlLocation: xmlLocation());
1829 parentState->invokes.append(t: invoke);
1830 invoke->src = attributes.value(qualifiedName: QLatin1String("src")).toString();
1831 invoke->srcexpr = attributes.value(qualifiedName: QLatin1String("srcexpr")).toString();
1832 invoke->id = attributes.value(qualifiedName: QLatin1String("id")).toString();
1833 invoke->idLocation = attributes.value(qualifiedName: QLatin1String("idlocation")).toString();
1834 invoke->type = attributes.value(qualifiedName: QLatin1String("type")).toString();
1835 invoke->typeexpr = attributes.value(qualifiedName: QLatin1String("typeexpr")).toString();
1836 QStringView autoforwardS = attributes.value(qualifiedName: QLatin1String("autoforward"));
1837 if (autoforwardS.compare(s: QLatin1String("true"), cs: Qt::CaseInsensitive) == 0
1838 || autoforwardS.compare(s: QLatin1String("yes"), cs: Qt::CaseInsensitive) == 0
1839 || autoforwardS.compare(s: QLatin1String("t"), cs: Qt::CaseInsensitive) == 0
1840 || autoforwardS.compare(s: QLatin1String("y"), cs: Qt::CaseInsensitive) == 0
1841 || autoforwardS == QLatin1String("1"))
1842 invoke->autoforward = true;
1843 else
1844 invoke->autoforward = false;
1845 invoke->namelist = attributes.value(qualifiedName: QLatin1String("namelist")).toString().split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
1846 current().instruction = invoke;
1847 return true;
1848}
1849
1850bool QScxmlCompilerPrivate::preReadElementFinalize()
1851{
1852 auto instr = previous().instruction;
1853 if (!instr) {
1854 addError(QStringLiteral("no previous instruction found for <finalize>"));
1855 return false;
1856 }
1857 auto invoke = instr->asInvoke();
1858 if (!invoke) {
1859 addError(QStringLiteral("instruction before <finalize> is not <invoke>"));
1860 return false;
1861 }
1862 current().instructionContainer = &invoke->finalize;
1863 return true;
1864}
1865
1866bool QScxmlCompilerPrivate::postReadElementScxml()
1867{
1868 return true;
1869}
1870
1871bool QScxmlCompilerPrivate::postReadElementState()
1872{
1873 currentStateUp();
1874 return true;
1875}
1876
1877bool QScxmlCompilerPrivate::postReadElementParallel()
1878{
1879 currentStateUp();
1880 return true;
1881}
1882
1883bool QScxmlCompilerPrivate::postReadElementInitial()
1884{
1885 return true;
1886}
1887
1888bool QScxmlCompilerPrivate::postReadElementTransition()
1889{
1890 return true;
1891}
1892
1893bool QScxmlCompilerPrivate::postReadElementFinal()
1894{
1895 currentStateUp();
1896 return true;
1897}
1898
1899bool QScxmlCompilerPrivate::postReadElementHistory()
1900{
1901 currentStateUp();
1902 return true;
1903}
1904
1905bool QScxmlCompilerPrivate::postReadElementOnEntry()
1906{
1907 return true;
1908}
1909
1910bool QScxmlCompilerPrivate::postReadElementOnExit()
1911{
1912 return true;
1913}
1914
1915bool QScxmlCompilerPrivate::postReadElementRaise()
1916{
1917 return flushInstruction();
1918}
1919
1920bool QScxmlCompilerPrivate::postReadElementIf()
1921{
1922 return flushInstruction();
1923}
1924
1925bool QScxmlCompilerPrivate::postReadElementElseIf()
1926{
1927 return true;
1928}
1929
1930bool QScxmlCompilerPrivate::postReadElementElse()
1931{
1932 return true;
1933}
1934
1935bool QScxmlCompilerPrivate::postReadElementForeach()
1936{
1937 return flushInstruction();
1938}
1939
1940bool QScxmlCompilerPrivate::postReadElementLog()
1941{
1942 return flushInstruction();
1943}
1944
1945bool QScxmlCompilerPrivate::postReadElementDataModel()
1946{
1947 return true;
1948}
1949
1950bool QScxmlCompilerPrivate::postReadElementData()
1951{
1952 const ParserState parserState = current();
1953 DocumentModel::DataElement *data = nullptr;
1954 if (auto state = m_currentState->asState()) {
1955 data = state->dataElements.last();
1956 } else if (auto scxml = m_currentState->asScxml()) {
1957 data = scxml->dataElements.last();
1958 } else {
1959 Q_UNREACHABLE();
1960 }
1961 if (!data->src.isEmpty() && !data->expr.isEmpty()) {
1962 addError(QStringLiteral("data element with both 'src' and 'expr' attributes"));
1963 return false;
1964 }
1965 if (!parserState.chars.trimmed().isEmpty()) {
1966 if (!data->src.isEmpty()) {
1967 addError(QStringLiteral("data element with both 'src' attribute and CDATA"));
1968 return false;
1969 } else if (!data->expr.isEmpty()) {
1970 addError(QStringLiteral("data element with both 'expr' attribute and CDATA"));
1971 return false;
1972 } else {
1973 // w3c-ecma/test558 - "if a child element of <data> is not a XML,
1974 // treat it as a string with whitespace normalization"
1975 // We've modified the test, so that a string is enclosed with quotes.
1976 data->expr = parserState.chars;
1977 }
1978 } else if (!data->src.isEmpty()) {
1979 if (!m_loader) {
1980 addError(QStringLiteral("cannot parse a document with external dependencies without a loader"));
1981 } else {
1982 bool ok;
1983 const QByteArray ba = load(name: data->src, ok: &ok);
1984 if (!ok) {
1985 addError(QStringLiteral("failed to load external dependency"));
1986 } else {
1987 // w3c-ecma/test558 - "if XML is loaded via "src" attribute,
1988 // treat it as a string with whitespace normalization"
1989 // We've enclosed the text in file with quotes.
1990 data->expr = QString::fromUtf8(ba);
1991 }
1992 }
1993 }
1994 return true;
1995}
1996
1997bool QScxmlCompilerPrivate::postReadElementAssign()
1998{
1999 return flushInstruction();
2000}
2001
2002bool QScxmlCompilerPrivate::postReadElementDoneData()
2003{
2004 return true;
2005}
2006
2007bool QScxmlCompilerPrivate::postReadElementContent()
2008{
2009 const ParserState parserState = current();
2010 if (!parserState.chars.trimmed().isEmpty()) {
2011
2012 switch (previous().kind) {
2013 case ParserState::DoneData: // see test529
2014 m_currentState->asState()->doneData->contents = parserState.chars.simplified();
2015 break;
2016 case ParserState::Send: // see test179
2017 previous().instruction->asSend()->content = parserState.chars.simplified();
2018 break;
2019 default:
2020 break;
2021 }
2022 }
2023 return true;
2024}
2025
2026bool QScxmlCompilerPrivate::postReadElementParam()
2027{
2028 return true;
2029}
2030
2031bool QScxmlCompilerPrivate::postReadElementScript()
2032{
2033 const ParserState parserState = current();
2034 DocumentModel::Script *scriptI = parserState.instruction->asScript();
2035 if (!parserState.chars.trimmed().isEmpty()) {
2036 scriptI->content = parserState.chars.trimmed();
2037 if (!scriptI->src.isEmpty())
2038 addError(QStringLiteral("both src and source content given to script, will ignore external content"));
2039 } else if (!scriptI->src.isEmpty()) {
2040 if (!m_loader) {
2041 addError(QStringLiteral("cannot parse a document with external dependencies without a loader"));
2042 } else {
2043 bool ok;
2044 const QByteArray data = load(name: scriptI->src, ok: &ok);
2045 if (!ok) {
2046 addError(QStringLiteral("failed to load external dependency"));
2047 } else {
2048 scriptI->content = QString::fromUtf8(ba: data);
2049 }
2050 }
2051 }
2052 return flushInstruction();
2053}
2054
2055bool QScxmlCompilerPrivate::postReadElementSend()
2056{
2057 return flushInstruction();
2058}
2059
2060bool QScxmlCompilerPrivate::postReadElementCancel()
2061{
2062 return flushInstruction();
2063}
2064
2065bool QScxmlCompilerPrivate::postReadElementInvoke()
2066{
2067 DocumentModel::Invoke *i = current().instruction->asInvoke();
2068 const QString fileName = i->src;
2069 if (!i->content.data()) {
2070 if (!fileName.isEmpty()) {
2071 bool ok = true;
2072 const QByteArray data = load(name: fileName, ok: &ok);
2073 if (!ok) {
2074 addError(QStringLiteral("failed to load external dependency"));
2075 } else {
2076 QXmlStreamReader reader(data);
2077 parseSubDocument(parentInvoke: i, reader: &reader, fileName);
2078 }
2079 }
2080 } else if (!fileName.isEmpty()) {
2081 addError(QStringLiteral("both src and content given to invoke"));
2082 }
2083
2084 return true;
2085}
2086
2087bool QScxmlCompilerPrivate::postReadElementFinalize()
2088{
2089 return true;
2090}
2091
2092void QScxmlCompilerPrivate::resetDocument()
2093{
2094 m_doc.reset(p: new DocumentModel::ScxmlDocument(fileName()));
2095}
2096
2097bool QScxmlCompilerPrivate::readDocument()
2098{
2099 resetDocument();
2100 m_currentState = m_doc->root;
2101 for (bool finished = false; !finished && !m_reader->hasError();) {
2102 switch (m_reader->readNext()) {
2103 case QXmlStreamReader::StartElement : {
2104 const QStringView newTag = m_reader->name();
2105 const ParserState::Kind newElementKind = ParserState::nameToParserStateKind(name: newTag);
2106
2107 auto ns = m_reader->namespaceUri();
2108
2109 if (ns != scxmlNamespace) {
2110 m_reader->skipCurrentElement();
2111 } else if (newElementKind == ParserState::None) {
2112 addError(QStringLiteral("Unknown element %1").arg(a: newTag.toString()));
2113 m_reader->skipCurrentElement();
2114 } else if (newElementKind == ParserState::Scxml) {
2115 if (readElement() == false)
2116 return false;
2117 } else {
2118 addError(QStringLiteral("Unexpected element %1").arg(a: newTag.toString()));
2119 m_reader->skipCurrentElement();
2120 }
2121 }
2122 break;
2123 case QXmlStreamReader::EndElement :
2124 finished = true;
2125 break;
2126 default :
2127 break;
2128 }
2129 }
2130 if (!m_doc->root) {
2131 addError(QStringLiteral("Missing root element"));
2132 return false;
2133 }
2134
2135 if (m_reader->hasError() && m_reader->error() != QXmlStreamReader::PrematureEndOfDocumentError) {
2136 addError(QStringLiteral("Error parsing SCXML file: %1").arg(a: m_reader->errorString()));
2137 return false;
2138 }
2139
2140 return true;
2141}
2142
2143bool QScxmlCompilerPrivate::readElement()
2144{
2145 const QStringView currentTag = m_reader->name();
2146 const QXmlStreamAttributes attributes = m_reader->attributes();
2147
2148 const ParserState::Kind elementKind = ParserState::nameToParserStateKind(name: currentTag);
2149
2150 if (!checkAttributes(attributes, kind: elementKind))
2151 return false;
2152
2153 if (elementKind == ParserState::Scxml && m_doc->root) {
2154 if (!hasPrevious()) {
2155 addError(QStringLiteral("misplaced scxml"));
2156 return false;
2157 }
2158
2159 DocumentModel::Invoke *i = previous().instruction->asInvoke();
2160 if (!i) {
2161 addError(QStringLiteral("misplaced scxml"));
2162 return false;
2163 }
2164
2165 return parseSubElement(parentInvoke: i, reader: m_reader, fileName: m_fileName);
2166 }
2167
2168 if (elementKind != ParserState::Scxml && !m_stack.size()) {
2169 addError(QStringLiteral("misplaced %1").arg(a: currentTag.toString()));
2170 return false;
2171 }
2172
2173 ParserState pNew = ParserState(elementKind);
2174
2175 m_stack.append(t: pNew);
2176
2177 switch (elementKind) {
2178 case ParserState::Scxml: if (!preReadElementScxml()) return false; break;
2179 case ParserState::State: if (!preReadElementState()) return false; break;
2180 case ParserState::Parallel: if (!preReadElementParallel()) return false; break;
2181 case ParserState::Initial: if (!preReadElementInitial()) return false; break;
2182 case ParserState::Transition: if (!preReadElementTransition()) return false; break;
2183 case ParserState::Final: if (!preReadElementFinal()) return false; break;
2184 case ParserState::History: if (!preReadElementHistory()) return false; break;
2185 case ParserState::OnEntry: if (!preReadElementOnEntry()) return false; break;
2186 case ParserState::OnExit: if (!preReadElementOnExit()) return false; break;
2187 case ParserState::Raise: if (!preReadElementRaise()) return false; break;
2188 case ParserState::If: if (!preReadElementIf()) return false; break;
2189 case ParserState::ElseIf: if (!preReadElementElseIf()) return false; break;
2190 case ParserState::Else: if (!preReadElementElse()) return false; break;
2191 case ParserState::Foreach: if (!preReadElementForeach()) return false; break;
2192 case ParserState::Log: if (!preReadElementLog()) return false; break;
2193 case ParserState::DataModel: if (!preReadElementDataModel()) return false; break;
2194 case ParserState::Data: if (!preReadElementData()) return false; break;
2195 case ParserState::Assign: if (!preReadElementAssign()) return false; break;
2196 case ParserState::DoneData: if (!preReadElementDoneData()) return false; break;
2197 case ParserState::Content: if (!preReadElementContent()) return false; break;
2198 case ParserState::Param: if (!preReadElementParam()) return false; break;
2199 case ParserState::Script: if (!preReadElementScript()) return false; break;
2200 case ParserState::Send: if (!preReadElementSend()) return false; break;
2201 case ParserState::Cancel: if (!preReadElementCancel()) return false; break;
2202 case ParserState::Invoke: if (!preReadElementInvoke()) return false; break;
2203 case ParserState::Finalize: if (!preReadElementFinalize()) return false; break;
2204 default: addError(QStringLiteral("Unknown element %1").arg(a: currentTag.toString())); return false;
2205 }
2206
2207 for (bool finished = false; !finished && !m_reader->hasError();) {
2208 switch (m_reader->readNext()) {
2209 case QXmlStreamReader::StartElement : {
2210 const QStringView newTag = m_reader->name();
2211 const ParserState::Kind newElementKind = ParserState::nameToParserStateKind(name: newTag);
2212
2213 auto ns = m_reader->namespaceUri();
2214
2215 if (ns != scxmlNamespace) {
2216 m_reader->skipCurrentElement();
2217 } else if (newElementKind == ParserState::None) {
2218 addError(QStringLiteral("Unknown element %1").arg(a: newTag.toString()));
2219 m_reader->skipCurrentElement();
2220 } else if (pNew.validChild(child: newElementKind)) {
2221 if (readElement() == false)
2222 return false;
2223 } else {
2224 addError(QStringLiteral("Unexpected element %1").arg(a: newTag.toString()));
2225 m_reader->skipCurrentElement();
2226 }
2227 }
2228 break;
2229 case QXmlStreamReader::EndElement :
2230 finished = true;
2231 break;
2232 case QXmlStreamReader::Characters :
2233 if (m_stack.isEmpty())
2234 break;
2235 if (current().collectChars())
2236 current().chars.append(v: m_reader->text());
2237 break;
2238 default :
2239 break;
2240 }
2241 }
2242
2243 switch (elementKind) {
2244 case ParserState::Scxml: if (!postReadElementScxml()) return false; break;
2245 case ParserState::State: if (!postReadElementState()) return false; break;
2246 case ParserState::Parallel: if (!postReadElementParallel()) return false; break;
2247 case ParserState::Initial: if (!postReadElementInitial()) return false; break;
2248 case ParserState::Transition: if (!postReadElementTransition()) return false; break;
2249 case ParserState::Final: if (!postReadElementFinal()) return false; break;
2250 case ParserState::History: if (!postReadElementHistory()) return false; break;
2251 case ParserState::OnEntry: if (!postReadElementOnEntry()) return false; break;
2252 case ParserState::OnExit: if (!postReadElementOnExit()) return false; break;
2253 case ParserState::Raise: if (!postReadElementRaise()) return false; break;
2254 case ParserState::If: if (!postReadElementIf()) return false; break;
2255 case ParserState::ElseIf: if (!postReadElementElseIf()) return false; break;
2256 case ParserState::Else: if (!postReadElementElse()) return false; break;
2257 case ParserState::Foreach: if (!postReadElementForeach()) return false; break;
2258 case ParserState::Log: if (!postReadElementLog()) return false; break;
2259 case ParserState::DataModel: if (!postReadElementDataModel()) return false; break;
2260 case ParserState::Data: if (!postReadElementData()) return false; break;
2261 case ParserState::Assign: if (!postReadElementAssign()) return false; break;
2262 case ParserState::DoneData: if (!postReadElementDoneData()) return false; break;
2263 case ParserState::Content: if (!postReadElementContent()) return false; break;
2264 case ParserState::Param: if (!postReadElementParam()) return false; break;
2265 case ParserState::Script: if (!postReadElementScript()) return false; break;
2266 case ParserState::Send: if (!postReadElementSend()) return false; break;
2267 case ParserState::Cancel: if (!postReadElementCancel()) return false; break;
2268 case ParserState::Invoke: if (!postReadElementInvoke()) return false; break;
2269 case ParserState::Finalize: if (!postReadElementFinalize()) return false; break;
2270 default: break;
2271 }
2272
2273 m_stack.removeLast();
2274
2275 if (m_reader->hasError()/* && m_reader->error() != QXmlStreamReader::PrematureEndOfDocumentError*/) {
2276 addError(QStringLiteral("Error parsing SCXML file: %1").arg(a: m_reader->errorString()));
2277 return false;
2278 }
2279
2280 return true;
2281}
2282
2283void QScxmlCompilerPrivate::currentStateUp()
2284{
2285 Q_ASSERT(m_currentState->parent);
2286 m_currentState = m_currentState->parent;
2287}
2288
2289bool QScxmlCompilerPrivate::flushInstruction()
2290{
2291 if (!hasPrevious()) {
2292 addError(QStringLiteral("missing instructionContainer"));
2293 return false;
2294 }
2295 DocumentModel::InstructionSequence *instructions = previous().instructionContainer;
2296 if (!instructions) {
2297 addError(QStringLiteral("got executable content within an element that did not set instructionContainer"));
2298 return false;
2299 }
2300 instructions->append(t: current().instruction);
2301 return true;
2302}
2303
2304
2305QByteArray QScxmlCompilerPrivate::load(const QString &name, bool *ok)
2306{
2307 QStringList errs;
2308 const QByteArray result = m_loader->load(name, baseDir: m_fileName.isEmpty() ?
2309 QString() : QFileInfo(m_fileName).path(), errors: &errs);
2310 for (const QString &err : errs)
2311 addError(msg: err);
2312
2313 *ok = errs.isEmpty();
2314
2315 return result;
2316}
2317
2318QList<QScxmlError> QScxmlCompilerPrivate::errors() const
2319{
2320 return m_errors;
2321}
2322
2323void QScxmlCompilerPrivate::addError(const QString &msg)
2324{
2325 m_errors.append(t: QScxmlError(m_fileName, m_reader->lineNumber(), m_reader->columnNumber(), msg));
2326}
2327
2328void QScxmlCompilerPrivate::addError(const DocumentModel::XmlLocation &location, const QString &msg)
2329{
2330 m_errors.append(t: QScxmlError(m_fileName, location.line, location.column, msg));
2331}
2332
2333DocumentModel::AbstractState *QScxmlCompilerPrivate::currentParent() const
2334{
2335 return m_currentState ? m_currentState->asAbstractState() : nullptr;
2336}
2337
2338DocumentModel::XmlLocation QScxmlCompilerPrivate::xmlLocation() const
2339{
2340 return DocumentModel::XmlLocation(m_reader->lineNumber(), m_reader->columnNumber());
2341}
2342
2343bool QScxmlCompilerPrivate::maybeId(const QXmlStreamAttributes &attributes, QString *id)
2344{
2345 Q_ASSERT(id);
2346 QString idStr = attributes.value(qualifiedName: QLatin1String("id")).toString();
2347 if (!idStr.isEmpty()) {
2348 if (m_allIds.contains(value: idStr)) {
2349 addError(location: xmlLocation(), QStringLiteral("duplicate id '%1'").arg(a: idStr));
2350 } else {
2351 m_allIds.insert(value: idStr);
2352 *id = idStr;
2353 }
2354 }
2355 return true;
2356}
2357
2358DocumentModel::If *QScxmlCompilerPrivate::lastIf()
2359{
2360 if (!hasPrevious()) {
2361 addError(QStringLiteral("No previous instruction found for else block"));
2362 return nullptr;
2363 }
2364
2365 DocumentModel::Instruction *lastI = previous().instruction;
2366 if (!lastI) {
2367 addError(QStringLiteral("No previous instruction found for else block"));
2368 return nullptr;
2369 }
2370 DocumentModel::If *ifI = lastI->asIf();
2371 if (!ifI) {
2372 addError(QStringLiteral("Previous instruction for else block is not an 'if'"));
2373 return nullptr;
2374 }
2375 return ifI;
2376}
2377
2378QScxmlCompilerPrivate::ParserState &QScxmlCompilerPrivate::current()
2379{
2380 return m_stack.last();
2381}
2382
2383QScxmlCompilerPrivate::ParserState &QScxmlCompilerPrivate::previous()
2384{
2385 return m_stack[m_stack.size() - 2];
2386}
2387
2388bool QScxmlCompilerPrivate::hasPrevious() const
2389{
2390 return m_stack.size() > 1;
2391}
2392
2393bool QScxmlCompilerPrivate::checkAttributes(const QXmlStreamAttributes &attributes,
2394 QScxmlCompilerPrivate::ParserState::Kind kind)
2395{
2396 return checkAttributes(attributes,
2397 requiredNames: ParserState::requiredAttributes(kind),
2398 optionalNames: ParserState::optionalAttributes(kind));
2399}
2400
2401bool QScxmlCompilerPrivate::checkAttributes(const QXmlStreamAttributes &attributes,
2402 const QStringList &requiredNames,
2403 const QStringList &optionalNames)
2404{
2405 QStringList required = requiredNames;
2406 for (const QXmlStreamAttribute &attribute : attributes) {
2407 const QStringView ns = attribute.namespaceUri();
2408 if (!ns.isEmpty() && ns != scxmlNamespace && ns != qtScxmlNamespace)
2409 continue;
2410
2411 const QString name = attribute.name().toString();
2412 if (!required.removeOne(t: name) && !optionalNames.contains(str: name)) {
2413 addError(QStringLiteral("Unexpected attribute '%1'").arg(a: name));
2414 return false;
2415 }
2416 }
2417 if (!required.isEmpty()) {
2418 addError(QStringLiteral("Missing required attributes: '%1'")
2419 .arg(a: required.join(sep: QLatin1String("', '"))));
2420 return false;
2421 }
2422 return true;
2423}
2424
2425QScxmlCompilerPrivate::DefaultLoader::DefaultLoader()
2426 : Loader()
2427{}
2428
2429QByteArray QScxmlCompilerPrivate::DefaultLoader::load(const QString &name, const QString &baseDir, QStringList *errors)
2430{
2431 QStringList errs;
2432 QByteArray contents;
2433#ifdef BUILD_QSCXMLC
2434 QString cleanName = name;
2435 if (name.startsWith(QStringLiteral("file:")))
2436 cleanName = name.mid(5);
2437 QFileInfo fInfo(cleanName);
2438#else
2439 const QUrl url(name);
2440 if (!url.isLocalFile() && !url.isRelative())
2441 errs << QStringLiteral("src attribute is not a local file (%1)").arg(a: name);
2442 QFileInfo fInfo(url.isLocalFile() ? url.toLocalFile() : name);
2443#endif // BUILD_QSCXMLC
2444 if (fInfo.isRelative())
2445 fInfo = QFileInfo(QDir(baseDir).filePath(fileName: fInfo.filePath()));
2446
2447 if (!fInfo.exists()) {
2448 errs << QStringLiteral("src attribute resolves to non existing file (%1)").arg(a: fInfo.filePath());
2449 } else {
2450 QFile f(fInfo.filePath());
2451 if (f.open(flags: QFile::ReadOnly))
2452 contents = f.readAll();
2453 else
2454 errs << QStringLiteral("Failure opening file %1: %2")
2455 .arg(args: fInfo.filePath(), args: f.errorString());
2456 }
2457 if (errors)
2458 *errors = errs;
2459
2460 return contents;
2461}
2462
2463QScxmlCompilerPrivate::ParserState::ParserState(QScxmlCompilerPrivate::ParserState::Kind someKind)
2464 : kind(someKind)
2465 , instruction(0)
2466 , instructionContainer(0)
2467{}
2468
2469QT_END_NAMESPACE
2470
2471#ifndef BUILD_QSCXMLC
2472#include "qscxmlcompiler.moc"
2473#endif
2474

source code of qtscxml/src/scxml/qscxmlcompiler.cpp