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

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