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

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