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