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