| 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 |  |