KDStateMachineEditor API Documentation 2.1
Loading...
Searching...
No Matches
scxmlimporter.cpp
Go to the documentation of this file.
1/*
2 This file is part of the KDAB State Machine Editor Library.
3
4 SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: Kevin Funk <kevin.funk@kdab.com>
6
7 SPDX-License-Identifier: LGPL-2.1-only OR LicenseRef-KDAB-KDStateMachineEditor
8
9 Licensees holding valid commercial KDAB State Machine Editor Library
10 licenses may use this file in accordance with the KDAB State Machine Editor
11 Library License Agreement provided with the Software.
12
13 Contact info@kdab.com if any conditions of this licensing are not clear to you.
14*/
15
16#include "scxmlimporter.h"
17
18#include "debug.h"
19#include "element.h"
20#include "state.h"
21#include "transition.h"
22
23#include <QHash>
24#include <QXmlStreamReader>
25
26#define IF_DEBUG(x)
27
28using namespace KDSME;
29
30struct ScxmlImporter::Private
31{
32 explicit Private(ScxmlImporter *q)
33 : q(q)
34 {
35 }
36
42 StateMachine *visitScxml();
43 void visitTransiton(State *parent);
44 void visitState(State *parent);
45 void visitInitial(State *parent);
46 void visitParallel(State *parent);
47 void visitFinal(State *parent);
48 void visitHistory(State *parent);
49
51 void reset();
52
54 State *tryCreateInitialState(State *parent);
56 void initState(State *state);
57
59 Transition *createTransition(State *parent, const QString &targetStateId);
60
61 void raiseUnexpectedElementError(const QString &context);
62
64 void resolveTargetStates();
65
67
68 QXmlStreamReader m_reader;
69
71 QHash<Transition *, QString> m_unresolvedTargetStateIds;
72 QHash<QString, State *> m_nameToStateMap;
73
74 QByteArray m_data;
75};
76
77ScxmlImporter::ScxmlImporter(const QByteArray &data)
78 : d(new Private(this))
79{
80 d->m_data = data;
81}
82
86
88{
89 setErrorString(QString());
90 d->reset();
91
92 if (d->m_data.isEmpty()) {
93 setErrorString(tr("No data supplied"));
94 return nullptr;
95 }
96
97 d->m_reader.addData(d->m_data);
98
99 StateMachine *stateMachine = nullptr;
100
101 if (d->m_reader.readNextStartElement() && d->m_reader.name() == QStringLiteral("scxml")) {
102 stateMachine = d->visitScxml();
103 } else {
104 d->m_reader.raiseError(tr("This document does not start with an <scxml> element"));
105 }
106
107 if (!d->m_reader.hasError()) {
108 // All states have been created by now, we can now link the transitions to their
109 // resp. target states
110 d->resolveTargetStates();
111 }
112
113 if (d->m_reader.hasError()) {
114 // pass error string to *this
115 setErrorString(d->m_reader.errorString());
116
117 delete stateMachine;
118 stateMachine = nullptr;
119 }
120 return stateMachine;
121}
122
123StateMachine *ScxmlImporter::Private::visitScxml()
124{
125 Q_ASSERT(m_reader.isStartElement() && m_reader.name() == QStringLiteral("scxml"));
126 IF_DEBUG(qCDebug(KDSME_CORE) << Q_FUNC_INFO;)
127
128 const QXmlStreamAttributes attributes = m_reader.attributes();
129
130 auto *state = new StateMachine;
131 state->setLabel(attributes.value(QStringLiteral("name")).toString());
132
133 tryCreateInitialState(state);
134
135 while (m_reader.readNextStartElement()) {
136 if (m_reader.name() == QStringLiteral("state")) {
137 visitState(state);
138 } else if (m_reader.name() == QStringLiteral("parallel")) {
139 visitParallel(state);
140 } else if (m_reader.name() == QStringLiteral("final")) {
141 visitFinal(state);
142 } else if (m_reader.name() == QStringLiteral("datamodel")) {
143 m_reader.skipCurrentElement();
144 } else if (m_reader.name() == QStringLiteral("script")) {
145 m_reader.skipCurrentElement();
146 } else {
147 raiseUnexpectedElementError(QStringLiteral("scxml"));
148 }
149 }
150 return state;
151}
152
153void ScxmlImporter::Private::visitParallel(State *parent)
154{
155 Q_ASSERT(m_reader.isStartElement() && m_reader.name() == QStringLiteral("parallel"));
156 IF_DEBUG(qCDebug(KDSME_CORE) << Q_FUNC_INFO;)
157
158 auto *state = new State(parent);
159 state->setChildMode(State::ParallelStates);
160 initState(state);
161 tryCreateInitialState(state);
162
163 while (m_reader.readNextStartElement()) {
164 if (m_reader.name() == QStringLiteral("onentry") || m_reader.name() == QStringLiteral("onexit")) {
165 m_reader.skipCurrentElement();
166 } else if (m_reader.name() == QStringLiteral("transition")) {
167 visitTransiton(state);
168 } else if (m_reader.name() == QStringLiteral("state")) {
169 visitState(state);
170 } else if (m_reader.name() == QStringLiteral("parallel")) {
171 visitParallel(state);
172 } else if (m_reader.name() == QStringLiteral("datamodel")) {
173 m_reader.skipCurrentElement();
174 } else if (m_reader.name() == QStringLiteral("invoke")) {
175 m_reader.skipCurrentElement();
176 } else if (m_reader.name() == QStringLiteral("history")) {
177 visitHistory(state);
178 } else {
179 raiseUnexpectedElementError(QStringLiteral("parallel"));
180 }
181 }
182}
183
184void ScxmlImporter::Private::visitState(State *parent)
185{
186 Q_ASSERT(m_reader.isStartElement() && m_reader.name() == QStringLiteral("state"));
187 IF_DEBUG(qCDebug(KDSME_CORE) << Q_FUNC_INFO;)
188
189 auto state = new State(parent);
190 initState(state);
191 tryCreateInitialState(state);
192
193 while (m_reader.readNextStartElement()) {
194 if (m_reader.name() == QStringLiteral("onentry") || m_reader.name() == QStringLiteral("onexit")) {
195 m_reader.skipCurrentElement();
196 } else if (m_reader.name() == QStringLiteral("transition")) {
197 visitTransiton(state);
198 } else if (m_reader.name() == QStringLiteral("initial")) {
199 visitInitial(state);
200 } else if (m_reader.name() == QStringLiteral("state")) {
201 visitState(state);
202 } else if (m_reader.name() == QStringLiteral("parallel")) {
203 visitParallel(state);
204 } else if (m_reader.name() == QStringLiteral("final")) {
205 visitFinal(state);
206 } else if (m_reader.name() == QStringLiteral("history")) {
207 visitHistory(state);
208 } else if (m_reader.name() == QStringLiteral("datamodel")) {
209 m_reader.skipCurrentElement();
210 } else if (m_reader.name() == QStringLiteral("invoke")) {
211 m_reader.skipCurrentElement();
212 } else {
213 raiseUnexpectedElementError(QStringLiteral("state"));
214 }
215 }
216}
217
218void ScxmlImporter::Private::visitInitial(State *parent)
219{
220 Q_ASSERT(m_reader.isStartElement() && m_reader.name() == QStringLiteral("initial"));
221 IF_DEBUG(qCDebug(KDSME_CORE) << Q_FUNC_INFO;)
222
223 // Must have exactly one <transition> child
224 const Transition *transition = nullptr;
225 while (m_reader.readNextStartElement()) {
226 if (m_reader.name() == u"transition") {
227 State *initialState = new PseudoState(PseudoState::InitialState, parent);
228 const QXmlStreamAttributes attributes = m_reader.attributes();
229 const QString targetStateId = attributes.value(QStringLiteral("target")).toString();
230 transition = createTransition(initialState, targetStateId);
231 } else {
232 raiseUnexpectedElementError(QStringLiteral("initial"));
233 }
234 }
235 if (!transition) {
236 m_reader.raiseError(QStringLiteral("Encountered <initial> element with invalid <transition> child"));
237 }
238
239 m_reader.skipCurrentElement();
240}
241
242void ScxmlImporter::Private::visitFinal(State *parent)
243{
244 Q_ASSERT(m_reader.isStartElement() && m_reader.name() == QStringLiteral("final"));
245 IF_DEBUG(qCDebug(KDSME_CORE) << Q_FUNC_INFO;)
246
247 auto state = new FinalState(parent);
248 initState(state);
249
250 m_reader.skipCurrentElement();
251}
252
253void ScxmlImporter::Private::visitTransiton(State *parent)
254{
255 Q_ASSERT(m_reader.isStartElement() && m_reader.name() == QStringLiteral("transition"));
256 IF_DEBUG(qCDebug(KDSME_CORE) << Q_FUNC_INFO;)
257
258 const QXmlStreamAttributes attributes = m_reader.attributes();
259 const QString event = attributes.value(QLatin1String("event")).toString();
260 const QString targetStateId = attributes.value(QLatin1String("target")).toString();
261 Transition *transition = createTransition(parent, targetStateId);
262 if (transition) {
263 transition->setLabel(event);
264 }
265
266 m_reader.skipCurrentElement();
267}
268
269void ScxmlImporter::Private::visitHistory(State *parent)
270{
271 Q_UNUSED(parent);
272 Q_ASSERT(m_reader.isStartElement() && m_reader.name() == u"transition");
273 IF_DEBUG(qCDebug(KDSME_CORE) << Q_FUNC_INFO;)
274
275 qCWarning(KDSME_CORE) << "NYI";
276
277 m_reader.skipCurrentElement();
278}
279
280void ScxmlImporter::Private::resolveTargetStates()
281{
282 IF_DEBUG(qCDebug(KDSME_CORE) << m_nameToStateMap;)
283
284 auto it = m_unresolvedTargetStateIds.constBegin();
285 while (it != m_unresolvedTargetStateIds.constEnd()) {
286 const QString targetStateId = it.value();
287 State *targetState = m_nameToStateMap.value(targetStateId);
288 if (!targetState) {
289 m_reader.raiseError(QStringLiteral("Unknown state id: %1").arg(targetStateId));
290 return;
291 }
292
293 Transition *transition = it.key();
294 transition->setTargetState(targetState);
295 ++it;
296 }
297}
298
299State *ScxmlImporter::Private::tryCreateInitialState(State *parent)
300{
301 const QXmlStreamAttributes attributes = m_reader.attributes();
302 if (attributes.hasAttribute(QStringLiteral("initial"))) {
303 State *initialState = new PseudoState(PseudoState::InitialState, parent);
304 const QString initialStateId = attributes.value(QStringLiteral("initial")).toString();
305 createTransition(initialState, initialStateId);
306 }
307 return nullptr;
308}
309
310void ScxmlImporter::Private::initState(State *state)
311{
312 Q_ASSERT(state);
313
314 const QXmlStreamAttributes attributes = m_reader.attributes();
315 const QString id = attributes.value(QStringLiteral("id")).toString();
316 IF_DEBUG(qCDebug(KDSME_CORE) << parent->label() << id;)
317 if (id.isEmpty()) {
318 qCWarning(KDSME_CORE) << "Unnamed state at offset:" << m_reader.characterOffset();
319 }
320 state->setLabel(id);
321 m_nameToStateMap[id] = state;
322}
323
324Transition *ScxmlImporter::Private::createTransition(State *parent, const QString &targetStateId)
325{
326 IF_DEBUG(qCDebug(KDSME_CORE) << parent->label() << targetStateId);
327 if (targetStateId.isEmpty()) {
328 return nullptr;
329 }
330
331 auto *transition = new Transition(parent);
332 m_unresolvedTargetStateIds[transition] = targetStateId;
333 return transition;
334}
335
336void ScxmlImporter::Private::reset()
337{
338 m_nameToStateMap.clear();
339 m_unresolvedTargetStateIds.clear();
340 m_reader.clear();
341}
342
343void ScxmlImporter::Private::raiseUnexpectedElementError(const QString &context)
344{
345 m_reader.raiseError(QStringLiteral("Unexpected element found while parsing '%1': %2")
346 .arg(context, m_reader.name().toString()));
347}
void setErrorString(const QString &errorString)
void setLabel(const QString &label)
Definition element.cpp:63
QString label
Definition element.h:40
ScxmlImporter(const QByteArray &data)
StateMachine * import() override
@ ParallelStates
Definition state.h:41
void setTargetState(State *targetState)
#define IF_DEBUG(x)

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDStateMachineEditor
Create Qt State Machine metacode using a graphical user interface
https://github.com/KDAB/KDStateMachineEditor
Generated on Tue Jul 15 2025 15:21:47 for KDStateMachineEditor API Documentation by doxygen 1.9.8