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