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> ¶ms)
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> ¶ms,
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