1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qcandbcfileparser.h" |
5 | #include "qcandbcfileparser_p.h" |
6 | #include "qcanmessagedescription.h" |
7 | #include "qcansignaldescription.h" |
8 | #include "qcanuniqueiddescription.h" |
9 | #include "private/qcanmessagedescription_p.h" |
10 | #include "private/qcansignaldescription_p.h" |
11 | |
12 | #include <QtCore/QFile> |
13 | #include <QtCore/QRegularExpression> |
14 | |
15 | #include <optional> |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | /*! |
20 | \class QCanDbcFileParser |
21 | \inmodule QtSerialBus |
22 | \since 6.5 |
23 | \preliminary |
24 | |
25 | \brief The QCanDbcFileParser class can be used to parse DBC files. |
26 | |
27 | A CAN database or CAN DBC file is an ASCII text file that contains |
28 | information on how to decode and interpret raw CAN bus data. Some more |
29 | details about the format can be found \l {CSSElectronics DBC Intro}{here} |
30 | or \l {OpenVehicles DBC Intro}{here}. |
31 | |
32 | The QCanDbcFileParser class takes the input DBC file, parses it, and |
33 | provides a list of \l {QCanMessageDescription}s as an output. These message |
34 | descriptions can be forwarded to \l QCanFrameProcessor, and later used |
35 | as rules to encode or decode \l {QCanBusFrame}s. |
36 | |
37 | Use one of \l parse() overloads to specify a file or a list of files that |
38 | will be processed. Both overloads return \c true if the parsing completes |
39 | successfully and \c false otherwise. |
40 | |
41 | Call the \l error() method to get the error which occurred during the |
42 | parsing. If the parsing completes successfully, this method will return |
43 | \l {QCanDbcFileParser::}{None}. Otherwise, you can use an |
44 | \l errorString() method to get the string representation of an error. |
45 | |
46 | During the parsing some non-critical problems may occur as well. Such |
47 | problems will be logged, but the parsing process will not be aborted. You |
48 | can use the \l warnings() method to get the full list of such problems |
49 | after the parsing is completed. |
50 | |
51 | If the parsing completes successfully, call \l messageDescriptions() to get |
52 | a list of the message descriptions that were extracted during the last |
53 | \l parse() call. Call \l messageValueDescriptions() to get the textual |
54 | descriptions of signal raw values, if they are available. |
55 | |
56 | Use the static \l uniqueIdDescription() function to get a |
57 | \l QCanUniqueIdDescription for the DBC format. |
58 | |
59 | \code |
60 | QCanDbcFileParser fileParser; |
61 | const bool result = fileParser.parse(u"path/to/file.dbc"_s); |
62 | // Check result, call error() and warnings() if needed |
63 | |
64 | // Prepare a QCanFrameProcessor to decode or encode DBC frames |
65 | QCanFrameProcessor frameProcessor; |
66 | frameProcessor.setUniqueIdDescription(QCanDbcFileParser::uniqueIdDescription()); |
67 | frameProcessor.setMessageDescriptions(fileParser.messageDescriptions()); |
68 | \endcode |
69 | |
70 | \note The parser is stateful, which means that all the results (like |
71 | extracted message descriptions, error code, or warnings) are reset once the |
72 | next parsing starts. |
73 | |
74 | \section2 Supported Keywords |
75 | |
76 | The current implementation supports only a subset of keywords that you can |
77 | find in a DBC file: |
78 | |
79 | \list |
80 | \li \c {BO_} - message description. |
81 | \li \c {SG_} - signal description. |
82 | \li \c {SIG_VALTYPE_} - signal type description. |
83 | \li \c {SG_MUL_VAL_} - extended multiplexing description. |
84 | \li \c {CM_} - comments (only for message and signal descriptions). |
85 | \li \c {VAL_} - textual descriptions for raw signal values. |
86 | \endlist |
87 | |
88 | Lines starting from other keywords are simply ignored. |
89 | |
90 | \sa QCanMessageDescription, QCanFrameProcessor |
91 | */ |
92 | |
93 | /*! |
94 | \typealias QCanDbcFileParser::ValueDescriptions |
95 | |
96 | This is a type alias for \c {QHash<quint32, QString>}. |
97 | |
98 | The keys of the hash represent raw signal values, and the values of the |
99 | hash represent corresponding string descriptions. |
100 | */ |
101 | |
102 | /*! |
103 | \typealias QCanDbcFileParser::SignalValueDescriptions |
104 | |
105 | This is a type alias for \c {QHash<QString, ValueDescriptions>}. |
106 | |
107 | The keys of the hash represent signal names, and the values of the |
108 | hash contain the corresponding \l QCanDbcFileParser::ValueDescriptions |
109 | entries. |
110 | |
111 | \sa QCanDbcFileParser::ValueDescriptions |
112 | */ |
113 | |
114 | /*! |
115 | \typealias QCanDbcFileParser::MessageValueDescriptions |
116 | |
117 | This is a type alias for |
118 | \c {QHash<QtCanBus::UniqueId, SignalValueDescriptions>}. |
119 | |
120 | The keys of the hash represent message unique ids, and the values of the |
121 | hash contain the corresponding \l QCanDbcFileParser::SignalValueDescriptions |
122 | entries. |
123 | |
124 | \sa QCanDbcFileParser::SignalValueDescriptions |
125 | */ |
126 | |
127 | /*! |
128 | \enum QCanDbcFileParser::Error |
129 | |
130 | This enum represents the possible errors that can happen during the parsing |
131 | of a DBC file. |
132 | |
133 | \value None No error occurred. |
134 | \value FileReading An error occurred while opening or reading the file. |
135 | \value Parsing An error occurred while parsing the content of the file. |
136 | */ |
137 | |
138 | /*! |
139 | Constructs a DBC file parser. |
140 | */ |
141 | QCanDbcFileParser::QCanDbcFileParser() |
142 | : d(std::make_unique<QCanDbcFileParserPrivate>()) |
143 | { |
144 | } |
145 | |
146 | /*! |
147 | Destroys this DBC file parser. |
148 | */ |
149 | QCanDbcFileParser::~QCanDbcFileParser() = default; |
150 | |
151 | /*! |
152 | Parses the file \a fileName. Returns \c true if the parsing completed |
153 | successfully or \c false otherwise. |
154 | |
155 | If the parsing completed successfully, call the \l messageDescriptions() |
156 | method to get the list of all extracted message descriptions. |
157 | |
158 | If the parsing failed, call the \l error() and \l errorString() methods |
159 | to get the information about the error. |
160 | |
161 | Call the \l warnings() method to get the list of warnings that were |
162 | logged during the parsing. |
163 | |
164 | \sa messageDescriptions(), error(), warnings() |
165 | */ |
166 | bool QCanDbcFileParser::parse(const QString &fileName) |
167 | { |
168 | d->reset(); |
169 | return d->parseFile(fileName); |
170 | } |
171 | |
172 | /*! |
173 | \overload |
174 | |
175 | Parses a list of files \a fileNames. Returns \c true if the parsing |
176 | completed successfully or \c false otherwise. |
177 | |
178 | If the parsing completed successfully, call the \l messageDescriptions() |
179 | method to get the list of all extracted message descriptions. |
180 | |
181 | The parsing stops at the first error. Call the \l error() and |
182 | \l errorString() methods to get the information about the error. |
183 | |
184 | Call the \l warnings() method to get the list of warnings that were |
185 | logged during the parsing. |
186 | |
187 | \sa messageDescriptions(), error(), warnings() |
188 | */ |
189 | bool QCanDbcFileParser::parse(const QStringList &fileNames) |
190 | { |
191 | d->reset(); |
192 | for (const auto &fileName : fileNames) { |
193 | if (!d->parseFile(fileName)) |
194 | return false; |
195 | } |
196 | return true; |
197 | } |
198 | |
199 | /*! |
200 | Returns the list of message descriptions that were extracted during the |
201 | last \l parse() call. |
202 | |
203 | \sa parse(), error() |
204 | */ |
205 | QList<QCanMessageDescription> QCanDbcFileParser::messageDescriptions() const |
206 | { |
207 | return d->getMessages(); |
208 | } |
209 | |
210 | /*! |
211 | Returns the textual descriptions for signal raw values. |
212 | |
213 | DBC supports the possibility to provide textual descriptions to signal raw |
214 | values. If such data exists in the parsed DBC file(s), it can be accessed |
215 | using this function. |
216 | |
217 | The textual descriptions are unique for a certain signal within a specific |
218 | message, so the returned structure contains the information about the |
219 | message unique id and the signal name, as well as the actual value |
220 | descriptions. |
221 | |
222 | \sa QCanDbcFileParser::MessageValueDescriptions, |
223 | QCanDbcFileParser::SignalValueDescriptions, |
224 | QCanDbcFileParser::ValueDescriptions |
225 | */ |
226 | QCanDbcFileParser::MessageValueDescriptions QCanDbcFileParser::messageValueDescriptions() const |
227 | { |
228 | return d->m_valueDescriptions; |
229 | } |
230 | |
231 | /*! |
232 | Returns the last error which occurred during the parsing. |
233 | |
234 | \sa errorString(), parse() |
235 | */ |
236 | QCanDbcFileParser::Error QCanDbcFileParser::error() const |
237 | { |
238 | return d->m_error; |
239 | } |
240 | |
241 | /*! |
242 | Returns the text representation of the last error which occurred during the |
243 | parsing or an empty string if there was no error. |
244 | |
245 | \sa error() |
246 | */ |
247 | QString QCanDbcFileParser::errorString() const |
248 | { |
249 | return d->m_errorString; |
250 | } |
251 | |
252 | /*! |
253 | Returns the list of non-critical problems which occurred during the parsing. |
254 | |
255 | A typical problem can be a malformed message or signal description. In such |
256 | cases the malformed message or signal is skipped, but the rest of the file |
257 | can be processed as usual. |
258 | |
259 | \sa error(), parse() |
260 | */ |
261 | QStringList QCanDbcFileParser::warnings() const |
262 | { |
263 | return d->m_warnings; |
264 | } |
265 | |
266 | /*! |
267 | Returns a unique identifier description. DBC protocol always uses the |
268 | Frame Id as an identifier, and therefore the unique identifier description |
269 | is always the same. |
270 | |
271 | Use this method to get an instance of \l QCanUniqueIdDescription and pass |
272 | it to \l QCanFrameProcessor. |
273 | |
274 | \sa QCanFrameProcessor::setUniqueIdDescription() |
275 | */ |
276 | QCanUniqueIdDescription QCanDbcFileParser::uniqueIdDescription() |
277 | { |
278 | QCanUniqueIdDescription desc; |
279 | desc.setSource(QtCanBus::DataSource::FrameId); |
280 | desc.setEndian(QSysInfo::Endian::LittleEndian); |
281 | desc.setStartBit(0); |
282 | desc.setBitLength(29); // for both extended and normal frame id |
283 | return desc; |
284 | } |
285 | |
286 | /* QCanDbcFileParserPrivate implementation */ |
287 | |
288 | using namespace Qt::StringLiterals; |
289 | |
290 | // signal name with whitespaces is invalid in DBC, so we can safely use it |
291 | // for internal purposes |
292 | static const auto kQtDummySignal = u"Qt Dummy Signal"_s ; |
293 | |
294 | static constexpr auto kMessageDef = "BO_ "_L1 ; |
295 | static constexpr auto kSignalDef = "SG_ "_L1 ; |
296 | static constexpr auto kSigValTypeDef = "SIG_VALTYPE_ "_L1 ; |
297 | static constexpr auto = "CM_ "_L1 ; |
298 | static constexpr auto kExtendedMuxDef = "SG_MUL_VAL_ "_L1 ; |
299 | static constexpr auto kValDef = "VAL_ "_L1 ; |
300 | |
301 | static constexpr auto kUnsignedIntRegExp = "\\d+"_L1 ; |
302 | static constexpr auto kDoubleRegExp = "[+-]?\\d+(.\\d+([eE][+-]?\\d+)?)?"_L1 ; |
303 | static constexpr auto kDbcIdentRegExp = "[_[:alpha:]][_[:alnum:]]+"_L1 ; |
304 | static constexpr auto kOneOrMoreSpaceRegExp = "[ ]+"_L1 ; |
305 | static constexpr auto kMaybeSpaceRegExp = "[ ]*"_L1 ; |
306 | static constexpr auto kMuxIndicatorRegExp = "M|m\\d+M?"_L1 ; |
307 | static constexpr auto kByteOrderRegExp = "0|1"_L1 ; |
308 | static constexpr auto kValueTypeRegExp = "\\+|\\-"_L1 ; |
309 | // The pattern matches all printable characters, except double-quote (") and backslash (\). |
310 | static constexpr auto kCharStrRegExp = "((?![\\\"\\\\])\\P{Cc})*"_L1 ; |
311 | |
312 | void QCanDbcFileParserPrivate::reset() |
313 | { |
314 | m_fileName.clear(); |
315 | m_error = QCanDbcFileParser::Error::None; |
316 | m_errorString.clear(); |
317 | m_warnings.clear(); |
318 | m_lineOffset = 0; |
319 | m_isProcessingMessage = false; |
320 | m_seenExtraData = false; |
321 | m_currentMessage = {}; |
322 | m_messageDescriptions.clear(); |
323 | m_valueDescriptions.clear(); |
324 | } |
325 | |
326 | /*! |
327 | \internal |
328 | Returns \c false only in case of hard error. Returns \c true even if some |
329 | warnings occurred during parsing. |
330 | */ |
331 | bool QCanDbcFileParserPrivate::parseFile(const QString &fileName) |
332 | { |
333 | QFile f(fileName); |
334 | if (!f.open(flags: QIODevice::ReadOnly)) { |
335 | m_error = QCanDbcFileParser::Error::FileReading; |
336 | m_errorString = f.errorString(); |
337 | return false; |
338 | } |
339 | m_fileName = fileName; |
340 | m_seenExtraData = false; |
341 | |
342 | while (!f.atEnd()) { |
343 | const QString str = QString::fromUtf8(ba: f.readLine().trimmed()); |
344 | if (!processLine(line: {str.constData(), str.size()})) // also sets the error properly |
345 | return false; |
346 | } |
347 | addCurrentMessage(); // check if we need to add the message |
348 | // now when we parsed the whole file, we can verify the signal multiplexing |
349 | postProcessSignalMultiplexing(); |
350 | |
351 | return true; |
352 | } |
353 | |
354 | /*! |
355 | \internal |
356 | Returns \c false only in case of hard error. Returns \c true even if some |
357 | warnings occurred during parsing. |
358 | */ |
359 | bool QCanDbcFileParserPrivate::processLine(const QStringView line) |
360 | { |
361 | QStringView data = line; |
362 | m_lineOffset = 0; |
363 | if (data.startsWith(s: kMessageDef)) { |
364 | if (m_seenExtraData) { |
365 | // Unexpected position of message description |
366 | m_error = QCanDbcFileParser::Error::Parsing; |
367 | m_errorString = QObject::tr(s: "Failed to parse file %1. Unexpected position " |
368 | "of %2 section." ).arg(args&: m_fileName, args: kMessageDef); |
369 | return false; |
370 | } |
371 | addCurrentMessage(); |
372 | if (!parseMessage(data)) |
373 | return false; |
374 | } |
375 | // signal definitions can be on the same line as message definition, |
376 | // or on a separate line |
377 | data = data.sliced(pos: m_lineOffset).trimmed(); |
378 | while (data.startsWith(s: kSignalDef)) { |
379 | if (!m_isProcessingMessage || m_seenExtraData) { |
380 | // Unexpected position of signal description |
381 | m_error = QCanDbcFileParser::Error::Parsing; |
382 | m_errorString = QObject::tr(s: "Failed to parse file %1. Unexpected position " |
383 | "of %2 section." ).arg(args&: m_fileName, args: kSignalDef); |
384 | return false; |
385 | } |
386 | if (!parseSignal(data)) |
387 | return false; |
388 | data = data.sliced(pos: m_lineOffset).trimmed(); |
389 | } |
390 | // If we detect one of the following lines, then message description is |
391 | // finished. We also assume that we can have only one key at each line. |
392 | if (data.startsWith(s: kSigValTypeDef)) { |
393 | m_seenExtraData = true; |
394 | addCurrentMessage(); |
395 | parseSignalType(data); |
396 | } else if (data.startsWith(s: kCommentDef)) { |
397 | m_seenExtraData = true; |
398 | addCurrentMessage(); |
399 | parseComment(data); |
400 | } else if (data.startsWith(s: kExtendedMuxDef)) { |
401 | m_seenExtraData = true; |
402 | addCurrentMessage(); |
403 | parseExtendedMux(data); |
404 | } else if (data.startsWith(s: kValDef)) { |
405 | m_seenExtraData = true; |
406 | addCurrentMessage(); |
407 | parseValueDescriptions(data); |
408 | } |
409 | return true; |
410 | } |
411 | |
412 | static std::optional<QtCanBus::UniqueId> (QStringView view) |
413 | { |
414 | bool ok = false; |
415 | const uint value = view.toUInt(ok: &ok); |
416 | if (ok) |
417 | return QtCanBus::UniqueId{value & 0x1FFFFFFF}; |
418 | return std::nullopt; |
419 | } |
420 | |
421 | /*! |
422 | \internal |
423 | Returns \c false only in case of hard error. Returns \c true even if some |
424 | warnings occurred during parsing. |
425 | */ |
426 | bool QCanDbcFileParserPrivate::parseMessage(const QStringView data) |
427 | { |
428 | // The regexp matches the following definition: |
429 | // BO_ message_id message_name ':' message_size transmitter |
430 | // also considering the fact that spaces around ':' seem to be optional, and |
431 | // allowing more than one space between parts. |
432 | |
433 | // %1 - messageDef |
434 | // %2 - maybeSpace |
435 | // %3 - unsignedInt |
436 | // %4 - oneOrMoreSpace |
437 | // %5 - DbcIdentifier |
438 | static const QString regExStr = |
439 | "%1%2(?<messageId>%3)%4(?<name>%5)%2:%2(?<size>%3)%4(?<transmitter>%5)"_L1 . |
440 | arg(args: kMessageDef, args: kMaybeSpaceRegExp, args: kUnsignedIntRegExp, args: kOneOrMoreSpaceRegExp, |
441 | args: kDbcIdentRegExp); |
442 | static const QRegularExpression messageRegExp(regExStr); |
443 | |
444 | m_isProcessingMessage = false; |
445 | const auto match = messageRegExp.matchView(subjectView: data); |
446 | if (match.hasMatch()) { |
447 | m_currentMessage = extractMessage(match); |
448 | // can't check for isValid() here, because demands signal descriptions |
449 | if (!m_currentMessage.name().isEmpty()) { |
450 | m_isProcessingMessage = true; |
451 | } else { |
452 | addWarning(warning: QObject::tr(s: "Failed to parse message description from " |
453 | "string %1" ).arg(a: data)); |
454 | } |
455 | m_lineOffset = match.capturedEnd(nth: 0); |
456 | } else { |
457 | addWarning(warning: QObject::tr(s: "Failed to find message description in string %1" ).arg(a: data)); |
458 | m_lineOffset = data.size(); // skip this string |
459 | } |
460 | return true; |
461 | } |
462 | |
463 | QCanMessageDescription |
464 | QCanDbcFileParserPrivate::extractMessage(const QRegularExpressionMatch &match) |
465 | { |
466 | Q_ASSERT(match.hasMatch()); |
467 | QCanMessageDescription desc; |
468 | desc.setName(match.captured(name: u"name"_s )); |
469 | |
470 | const auto id = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
471 | if (id.has_value()) { |
472 | desc.setUniqueId(id.value()); |
473 | } else { |
474 | addWarning(warning: QObject::tr(s: "Failed to parse frame id for message %1" ).arg(a: desc.name())); |
475 | return {}; |
476 | } |
477 | |
478 | bool ok = false; |
479 | const auto size = match.capturedView(name: u"size"_s ).toUInt(ok: &ok); |
480 | if (ok) { |
481 | desc.setSize(size); |
482 | } else { |
483 | addWarning(warning: QObject::tr(s: "Failed to parse size for message %1" ).arg(a: desc.name())); |
484 | return {}; |
485 | } |
486 | |
487 | desc.setTransmitter(match.captured(name: u"transmitter"_s )); |
488 | |
489 | return desc; |
490 | } |
491 | |
492 | /*! |
493 | \internal |
494 | Returns \c false only in case of hard error. Returns \c true even if some |
495 | warnings occurred during parsing. |
496 | */ |
497 | bool QCanDbcFileParserPrivate::parseSignal(const QStringView data) |
498 | { |
499 | // The regexp should match the following pattern: |
500 | // SG_ signal_name multiplexer_indicator : start_bit | |
501 | // signal_size @ byte_order value_type ( factor , offset ) |
502 | // [ minimum | maximum ] unit receiver {, receiver} |
503 | // We also need to consider the fact that some of the spaces might be |
504 | // optional, and we can potentially allow more spaces between parts. |
505 | // Note that the end of the signal description can contain multiple |
506 | // receivers. The regexp is supposed to extract all of them, but we use |
507 | // only the first one for now. |
508 | |
509 | // %1 - SignalDef |
510 | // %2 - MaybeSpace |
511 | // %3 - DbcIdentifier |
512 | // %4 - OneOrMoreSpace |
513 | // %5 - MuxIndicator |
514 | // %6 - unsignedInt |
515 | // %7 - byteOrder |
516 | // %8 - valueType |
517 | // %9 - double |
518 | // %10 - charStr |
519 | static const QString regExStr = |
520 | "%1%2(?<name>%3)(%4(?<mux>%5))?%2:%2(?<startBit>%6)%2\\|%2(?<sigSize>%6)%2@%2" |
521 | "(?<byteOrder>%7)%2(?<valueType>%8)%4\\(%2(?<factor>%9)%2,%2(?<offset>%9)%2\\)" |
522 | "%4\\[%2(?<min>%9)%2\\|%2(?<max>%9)%2\\]%4\"(?<unit>%10)\"" |
523 | "%4(?<receiver>%3)(%2,%2%3)*"_L1 . |
524 | arg(args: kSignalDef, args: kMaybeSpaceRegExp, args: kDbcIdentRegExp, args: kOneOrMoreSpaceRegExp, |
525 | args: kMuxIndicatorRegExp, args: kUnsignedIntRegExp, args: kByteOrderRegExp, args: kValueTypeRegExp, |
526 | args: kDoubleRegExp, args: kCharStrRegExp); |
527 | static const QRegularExpression signalRegExp(regExStr); |
528 | |
529 | const auto match = signalRegExp.matchView(subjectView: data); |
530 | if (match.hasMatch()) { |
531 | QCanSignalDescription desc = extractSignal(match); |
532 | |
533 | if (desc.isValid()) |
534 | m_currentMessage.addSignalDescription(description: desc); |
535 | else |
536 | addWarning(warning: QObject::tr(s: "Failed to parse signal description from string %1" ).arg(a: data)); |
537 | |
538 | m_lineOffset = match.capturedEnd(nth: 0); |
539 | } else { |
540 | addWarning(warning: QObject::tr(s: "Failed to find signal description in string %1" ).arg(a: data)); |
541 | m_lineOffset = data.size(); // skip this string |
542 | } |
543 | return true; |
544 | } |
545 | |
546 | QCanSignalDescription QCanDbcFileParserPrivate::extractSignal(const QRegularExpressionMatch &match) |
547 | { |
548 | Q_ASSERT(match.hasMatch()); |
549 | QCanSignalDescription desc; |
550 | desc.setName(match.captured(name: u"name"_s )); |
551 | |
552 | bool ok = false; |
553 | |
554 | if (match.hasCaptured(name: u"mux"_s )) { |
555 | const auto muxStr = match.capturedView(name: u"mux"_s ); |
556 | if (muxStr == u"M"_s ) { |
557 | desc.setMultiplexState(QtCanBus::MultiplexState::MultiplexorSwitch); |
558 | } else if (muxStr.endsWith(s: u"M"_s , cs: Qt::CaseSensitive)) { |
559 | desc.setMultiplexState(QtCanBus::MultiplexState::SwitchAndSignal); |
560 | const auto val = muxStr.sliced(pos: 1, n: muxStr.size() - 2).toUInt(ok: &ok); |
561 | if (!ok) { |
562 | addWarning(warning: QObject::tr(s: "Failed to parse multiplexor value for signal %1" ). |
563 | arg(a: desc.name())); |
564 | return {}; |
565 | } |
566 | // We have the value, but we do not really know the multiplexor |
567 | // switch name. To know it, we potentially need to parse all signals |
568 | // for the message. So for now we just create a dummy entry, and |
569 | // the actual signal name will be updated later; |
570 | desc.addMultiplexSignal(name: kQtDummySignal, value: val); |
571 | } else { |
572 | desc.setMultiplexState(QtCanBus::MultiplexState::MultiplexedSignal); |
573 | const auto val = muxStr.sliced(pos: 1).toUInt(ok: &ok); |
574 | if (!ok) { |
575 | addWarning(warning: QObject::tr(s: "Failed to parse multiplexor value for signal %1" ). |
576 | arg(a: desc.name())); |
577 | return {}; |
578 | } |
579 | // Same as above |
580 | desc.addMultiplexSignal(name: kQtDummySignal, value: val); |
581 | } |
582 | } |
583 | |
584 | const uint startBit = match.capturedView(name: u"startBit"_s ).toUInt(ok: &ok); |
585 | if (ok) { |
586 | desc.setStartBit(startBit); |
587 | } else { |
588 | addWarning(warning: QObject::tr(s: "Failed to parse start bit for signal %1" ).arg(a: desc.name())); |
589 | return {}; |
590 | } |
591 | |
592 | const uint bitLength = match.capturedView(name: u"sigSize"_s ).toUInt(ok: &ok); |
593 | if (ok) { |
594 | desc.setBitLength(bitLength); |
595 | } else { |
596 | addWarning(warning: QObject::tr(s: "Failed to parse bit length for signal %1" ).arg(a: desc.name())); |
597 | return {}; |
598 | } |
599 | |
600 | // 0 = BE; 1 = LE |
601 | const auto endian = match.capturedView(name: u"byteOrder"_s ) == u"0"_s |
602 | ? QSysInfo::Endian::BigEndian : QSysInfo::Endian::LittleEndian; |
603 | desc.setDataEndian(endian); |
604 | |
605 | // + = unsigned; - = signed |
606 | const auto dataFormat = match.capturedView(name: u"valueType"_s ) == u"+"_s |
607 | ? QtCanBus::DataFormat::UnsignedInteger : QtCanBus::DataFormat::SignedInteger; |
608 | desc.setDataFormat(dataFormat); |
609 | |
610 | const double factor = match.capturedView(name: u"factor"_s ).toDouble(ok: &ok); |
611 | if (ok) { |
612 | desc.setFactor(factor); |
613 | } else { |
614 | addWarning(warning: QObject::tr(s: "Failed to parse factor for signal %1" ).arg(a: desc.name())); |
615 | return {}; |
616 | } |
617 | |
618 | const double offset = match.capturedView(name: u"offset"_s ).toDouble(ok: &ok); |
619 | if (ok) { |
620 | desc.setOffset(offset); |
621 | } else { |
622 | addWarning(warning: QObject::tr(s: "Failed to parse offset for signal %1" ).arg(a: desc.name())); |
623 | return {}; |
624 | } |
625 | |
626 | const double min = match.capturedView(name: u"min"_s ).toDouble(ok: &ok); |
627 | if (ok) { |
628 | const double max = match.capturedView(name: u"max"_s ).toDouble(ok: &ok); |
629 | if (ok) |
630 | desc.setRange(minimum: min, maximum: max); |
631 | } |
632 | if (!ok) { |
633 | addWarning(warning: QObject::tr(s: "Failed to parse value range from signal %1" ).arg(a: desc.name())); |
634 | return {}; |
635 | } |
636 | |
637 | desc.setPhysicalUnit(match.captured(name: u"unit"_s )); |
638 | desc.setReceiver(match.captured(name: u"receiver"_s )); |
639 | |
640 | return desc; |
641 | } |
642 | |
643 | void QCanDbcFileParserPrivate::parseSignalType(const QStringView data) |
644 | { |
645 | // The regexp should match the following pattern: |
646 | // SIG_VALTYPE_ message_id signal_name signal_extended_value_type ; |
647 | // We also need to consider the fact that we can potentially allow more |
648 | // spaces between parts. |
649 | |
650 | // %1 sigValTypeDef |
651 | // %2 maybeSpace |
652 | // %3 unsignedInt |
653 | // %4 oneOrMoreSpace |
654 | // %5 DbcIdentifier |
655 | const QString regExStr = |
656 | "%1%2(?<messageId>%3)%4(?<sigName>%5)%2:%2(?<type>%3)%2;"_L1 . |
657 | arg(args: kSigValTypeDef, args: kMaybeSpaceRegExp, args: kUnsignedIntRegExp, |
658 | args: kOneOrMoreSpaceRegExp, args: kDbcIdentRegExp); |
659 | const QRegularExpression sigValTypeRegEx(regExStr); |
660 | |
661 | const auto match = sigValTypeRegEx.matchView(subjectView: data); |
662 | if (!match.hasMatch()) { |
663 | m_lineOffset = data.size(); |
664 | addWarning(warning: QObject::tr(s: "Failed to find signal value type description in string %1" ). |
665 | arg(a: data)); |
666 | return; |
667 | } |
668 | |
669 | m_lineOffset = match.capturedEnd(nth: 0); |
670 | |
671 | const auto uidOptional = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
672 | if (!uidOptional) { |
673 | addWarning(warning: QObject::tr(s: "Failed to parse frame id from string %1" ).arg(a: data)); |
674 | return; |
675 | } |
676 | |
677 | const QtCanBus::UniqueId uid = uidOptional.value(); |
678 | auto msgDesc = m_messageDescriptions.value(key: uid); |
679 | if (msgDesc.isValid()) { |
680 | const QString sigName = match.captured(name: u"sigName"_s ); |
681 | auto sigDesc = msgDesc.signalDescriptionForName(name: sigName); |
682 | if (sigDesc.isValid()) { |
683 | bool ok = false; |
684 | const auto type = match.capturedView(name: u"type" ).toUInt(ok: &ok); |
685 | if (ok) { |
686 | bool sigDescChanged = false; |
687 | switch (type) { |
688 | case 0: /* signed or unsigned integer */ |
689 | // do nothing, as we already have signed/unsinged integer |
690 | // based on "SG_ " string |
691 | break; |
692 | case 1: /* 32-bit IEEE-float */ |
693 | sigDesc.setDataFormat(QtCanBus::DataFormat::Float); |
694 | sigDesc.setBitLength(32); |
695 | sigDescChanged = true; |
696 | break; |
697 | case 2: /* 64-bit IEEE-double */ |
698 | sigDesc.setDataFormat(QtCanBus::DataFormat::Double); |
699 | sigDesc.setBitLength(64); |
700 | sigDescChanged = true; |
701 | break; |
702 | default: |
703 | // invalid value |
704 | break; |
705 | } |
706 | if (sigDescChanged) { |
707 | msgDesc.addSignalDescription(description: sigDesc); |
708 | m_messageDescriptions.insert(key: msgDesc.uniqueId(), value: msgDesc); |
709 | } |
710 | } else { |
711 | addWarning(warning: QObject::tr(s: "Failed to parse data type from string %1" ).arg(a: data)); |
712 | } |
713 | } else { |
714 | addWarning(warning: QObject::tr(s: "Failed to find signal description for signal %1. " |
715 | "Skipping string %2" ).arg(args: sigName, args: data)); |
716 | } |
717 | } else { |
718 | addWarning(warning: QObject::tr(s: "Failed to find message description for unique id %1. " |
719 | "Skipping string %2" ).arg(a: qToUnderlying(e: uid)).arg(a: data)); |
720 | } |
721 | } |
722 | |
723 | void QCanDbcFileParserPrivate::parseComment(const QStringView data) |
724 | { |
725 | // The comment for message or signal description is represented by the |
726 | // following pattern: |
727 | // CM_ (BO_ message_id char_string | SG_ message_id signal_name char_string); |
728 | |
729 | // %1 commentDef |
730 | // %2 maybeSpace |
731 | // %3 messageDef |
732 | // %4 signalDef |
733 | // %5 oneOrMoreSpace |
734 | // %6 unsignedInt |
735 | // %7 DbcIdentifier |
736 | // %8 charStr |
737 | const QString regExStr = |
738 | "%1%2(?<type>(%3|%4))%2(?<messageId>%6)%5((?<sigName>%7)%5)?\"(?<comment>%8)\"%2;"_L1 . |
739 | arg(args: kCommentDef, args: kMaybeSpaceRegExp, args: kMessageDef, args: kSignalDef, args: kOneOrMoreSpaceRegExp, |
740 | args: kUnsignedIntRegExp, args: kDbcIdentRegExp, args: kCharStrRegExp); |
741 | const QRegularExpression (regExStr); |
742 | |
743 | const auto match = commentRegExp.matchView(subjectView: data); |
744 | if (!match.hasMatch()) { |
745 | // no warning here, as we ignore some "general" comments, and parse only |
746 | // comments related to messages and signals |
747 | m_lineOffset = data.size(); |
748 | return; |
749 | } |
750 | |
751 | m_lineOffset = match.capturedEnd(nth: 0); |
752 | |
753 | const auto type = match.capturedView(name: u"type"_s ); |
754 | |
755 | const auto uidOptional = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
756 | if (!uidOptional) { |
757 | addWarning(warning: QObject::tr(s: "Failed to parse frame id from string %1" ).arg(a: data)); |
758 | return; |
759 | } |
760 | |
761 | const QtCanBus::UniqueId uid = uidOptional.value(); |
762 | auto messageDesc = m_messageDescriptions.value(key: uid); |
763 | if (!messageDesc.isValid()) { |
764 | addWarning(warning: QObject::tr(s: "Failed to find message description for unique id %1. " |
765 | "Skipping string %2" ).arg(a: qToUnderlying(e: uid)).arg(a: data)); |
766 | return; |
767 | } |
768 | |
769 | if (type == kMessageDef) { |
770 | const QString = match.captured(name: u"comment"_s ); |
771 | messageDesc.setComment(comment); |
772 | m_messageDescriptions.insert(key: uid, value: messageDesc); |
773 | } else if (type == kSignalDef) { |
774 | const QString sigName = match.captured(name: u"sigName"_s ); |
775 | auto signalDesc = messageDesc.signalDescriptionForName(name: sigName); |
776 | if (signalDesc.isValid()) { |
777 | const QString = match.captured(name: u"comment"_s ); |
778 | signalDesc.setComment(comment); |
779 | messageDesc.addSignalDescription(description: signalDesc); |
780 | m_messageDescriptions.insert(key: uid, value: messageDesc); |
781 | } else { |
782 | addWarning(warning: QObject::tr(s: "Failed to find signal description for signal %1. " |
783 | "Skipping string %2" ).arg(args: sigName, args: data)); |
784 | } |
785 | } |
786 | } |
787 | |
788 | void QCanDbcFileParserPrivate::parseExtendedMux(const QStringView data) |
789 | { |
790 | // The extended multiplexing is defined by the following pattern: |
791 | // SG_MUL_VAL_ message_id multiplexed_signal_name |
792 | // multiplexor_switch_name multiplexor_value_ranges ; |
793 | // Here multiplexor_value_ranges consists of multiple ranges, separated |
794 | // by a whitespace, and one range is defined as follows: |
795 | // multiplexor_value_range = unsigned_integer - unsigned_integer |
796 | |
797 | // %1 extendedMuxDef |
798 | // %2 maybeSpace |
799 | // %3 unsignedInt |
800 | // %4 oneOrMoreSpace |
801 | // %5 DbcIdentifier |
802 | const QString regExStr = |
803 | "%1%2(?<messageId>%3)%4(?<multiplexedSignal>%5)%4(?<multiplexorSwitch>%5)%4" |
804 | "(?<firstRange>%3%2-%2%3)(%2,%2%3%2-%2%3)*%2;"_L1 . |
805 | arg(args: kExtendedMuxDef, args: kMaybeSpaceRegExp, args: kUnsignedIntRegExp, args: kOneOrMoreSpaceRegExp, |
806 | args: kDbcIdentRegExp); |
807 | const QRegularExpression extendedMuxRegExp(regExStr); |
808 | |
809 | const auto match = extendedMuxRegExp.matchView(subjectView: data); |
810 | if (!match.hasMatch()) { |
811 | m_lineOffset = data.size(); |
812 | addWarning(warning: QObject::tr(s: "Failed to find extended multiplexing description in string %1" ). |
813 | arg(a: data)); |
814 | return; |
815 | } |
816 | |
817 | m_lineOffset = match.capturedEnd(nth: 0); |
818 | |
819 | const auto uidOptional = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
820 | if (!uidOptional) { |
821 | addWarning(warning: QObject::tr(s: "Failed to parse frame id from string %1" ).arg(a: data)); |
822 | return; |
823 | } |
824 | |
825 | const QtCanBus::UniqueId uid = uidOptional.value(); |
826 | auto messageDesc = m_messageDescriptions.value(key: uid); |
827 | if (!messageDesc.isValid()) { |
828 | addWarning(warning: QObject::tr(s: "Failed to find message description for unique id %1. " |
829 | "Skipping string %2" ).arg(a: qToUnderlying(e: uid)).arg(a: data)); |
830 | return; |
831 | } |
832 | |
833 | const QString multiplexedSignalName = match.captured(name: u"multiplexedSignal"_s ); |
834 | const QString multiplexorSwitchName = match.captured(name: u"multiplexorSwitch"_s ); |
835 | |
836 | auto multiplexedSignal = messageDesc.signalDescriptionForName(name: multiplexedSignalName); |
837 | auto multiplexorSwitch = messageDesc.signalDescriptionForName(name: multiplexorSwitchName); |
838 | |
839 | if (!multiplexedSignal.isValid() || !multiplexorSwitch.isValid()) { |
840 | const QString invalidName = multiplexedSignal.isValid() ? multiplexorSwitchName |
841 | : multiplexedSignalName; |
842 | addWarning(warning: QObject::tr(s: "Failed to find signal description for signal %1. " |
843 | "Skipping string %2" ).arg(args: invalidName, args: data)); |
844 | return; |
845 | } |
846 | |
847 | auto signalRanges = multiplexedSignal.multiplexSignals(); |
848 | signalRanges.remove(key: kQtDummySignal); // dummy signal not needed anymore |
849 | |
850 | QCanSignalDescription::MultiplexValues rangeValues; |
851 | auto rangeView = match.capturedView(name: u"firstRange"_s ); |
852 | const auto sepIdx = rangeView.indexOf(c: u'-'); |
853 | if (sepIdx != -1) { |
854 | const auto min = rangeView.first(n: sepIdx).trimmed().toUInt(); |
855 | const auto max = rangeView.sliced(pos: sepIdx + 1).trimmed().toUInt(); |
856 | rangeValues.push_back(t: {.minimum: min, .maximum: max}); |
857 | } |
858 | |
859 | // We can have an arbitrary amount of ranges, so we can't use capture groups |
860 | // to capture them. But we know that they follow a specific pattern (because |
861 | // the full string matched the regexp). So we need to parse the rest of the |
862 | // matched string manually |
863 | const auto totalEnd = match.capturedEnd(nth: 0); // including the ';' |
864 | const auto firstRangeEnd = match.capturedEnd(name: u"firstRange"_s ); |
865 | const auto len = totalEnd - firstRangeEnd - 1; |
866 | if (len > 0) { |
867 | const auto otherRangesView = data.sliced(pos: firstRangeEnd, n: len).trimmed(); |
868 | const QStringTokenizer parts = otherRangesView.tokenize(needle: u',', flags: Qt::SkipEmptyParts); |
869 | for (const QStringView range : parts) { |
870 | const auto sepIdx = range.indexOf(c: u'-'); |
871 | if (sepIdx != -1) { |
872 | const auto min = range.first(n: sepIdx).trimmed().toUInt(); |
873 | const auto max = range.sliced(pos: sepIdx + 1).trimmed().toUInt(); |
874 | rangeValues.push_back(t: {.minimum: min, .maximum: max}); |
875 | } |
876 | } |
877 | } |
878 | |
879 | if (!rangeValues.isEmpty()) |
880 | signalRanges.insert(key: multiplexorSwitchName, value: rangeValues); |
881 | else |
882 | signalRanges.remove(key: multiplexorSwitchName); |
883 | |
884 | // update the value |
885 | multiplexedSignal.setMultiplexSignals(signalRanges); |
886 | messageDesc.addSignalDescription(description: multiplexedSignal); |
887 | m_messageDescriptions.insert(key: uid, value: messageDesc); |
888 | } |
889 | |
890 | void QCanDbcFileParserPrivate::parseValueDescriptions(const QStringView data) |
891 | { |
892 | // The regexp should match the following pattern: |
893 | // VAL_ message_id signal_name { value_description }; |
894 | // Here the value_description is defined as follows |
895 | // value_description = unsigned_int char_string |
896 | |
897 | // %1 valDef |
898 | // %2 maybeSpace |
899 | // %3 unsignedInt |
900 | // %4 oneOrMoreSpace |
901 | // %5 DbcIdentifier |
902 | // %6 charStr |
903 | const QString regExStr = |
904 | "%1%2(?<messageId>%3)%4(?<signalName>%5)(%4%3%4\"(%6)\")+%2;"_L1 . |
905 | arg(args: kValDef, args: kMaybeSpaceRegExp, args: kUnsignedIntRegExp, args: kOneOrMoreSpaceRegExp, |
906 | args: kDbcIdentRegExp, args: kCharStrRegExp); |
907 | |
908 | const QRegularExpression valueDescRegExp(regExStr); |
909 | |
910 | const auto match = valueDescRegExp.matchView(subjectView: data); |
911 | if (!match.hasMatch()) { |
912 | m_lineOffset = data.size(); |
913 | addWarning(warning: QObject::tr(s: "Failed to parse value description from string %1" ).arg(a: data)); |
914 | return; |
915 | } |
916 | |
917 | m_lineOffset = match.capturedEnd(nth: 0); |
918 | |
919 | const auto uidOptional = extractUniqueId(view: match.capturedView(name: u"messageId"_s )); |
920 | if (!uidOptional) { |
921 | addWarning(warning: QObject::tr(s: "Failed to parse value description from string %1" ).arg(a: data)); |
922 | return; |
923 | } |
924 | |
925 | const QtCanBus::UniqueId uid = uidOptional.value(); |
926 | // Check if the message exists |
927 | const auto messageDesc = m_messageDescriptions.value(key: uid); |
928 | if (!messageDesc.isValid()) { |
929 | addWarning(warning: QObject::tr(s: "Failed to find message description for unique id %1. " |
930 | "Skipping string %2" ).arg(a: qToUnderlying(e: uid)).arg(a: data)); |
931 | return; |
932 | } |
933 | |
934 | // Check if the signal exists within the message |
935 | const QString signalName = match.captured(name: u"signalName"_s ); |
936 | if (!messageDesc.signalDescriptionForName(name: signalName).isValid()) { |
937 | addWarning(warning: QObject::tr(s: "Failed to find signal description for signal %1. " |
938 | "Skipping string %2" ).arg(args: signalName, args: data)); |
939 | return; |
940 | } |
941 | |
942 | // We can have an arbitrary amount of value descriptions, so we can't use |
943 | // capture groups to capture them. But we know that they follow a specific |
944 | // pattern (because the full string matched the regexp). So we need to parse |
945 | // the rest of the matched string manually |
946 | const auto totalEnd = match.capturedEnd(nth: 0); // including the ';' |
947 | const auto signalNameEnd = match.capturedEnd(name: u"signalName"_s ); |
948 | const auto len = totalEnd - signalNameEnd - 1; |
949 | if (len > 0) { |
950 | auto signalDescriptionsView = data.sliced(pos: signalNameEnd, n: len).trimmed(); |
951 | while (signalDescriptionsView.size()) { |
952 | const auto spacePos = signalDescriptionsView.indexOf(c: u' '); |
953 | if (spacePos == -1) |
954 | break; |
955 | bool ok = false; |
956 | const auto value = signalDescriptionsView.sliced(pos: 0, n: spacePos).toUInt(ok: &ok); |
957 | if (!ok) |
958 | break; |
959 | const auto firstQuotePos = signalDescriptionsView.indexOf(c: u'"', from: spacePos + 1); |
960 | if (firstQuotePos == -1) |
961 | break; |
962 | const auto nextQuotePos = signalDescriptionsView.indexOf(c: u'"', from: firstQuotePos + 1); |
963 | if (nextQuotePos == -1) |
964 | break; |
965 | const auto description = signalDescriptionsView.sliced( |
966 | pos: firstQuotePos + 1, n: nextQuotePos - firstQuotePos - 1); |
967 | |
968 | m_valueDescriptions[uid][signalName].insert(key: value, value: description.toString()); |
969 | signalDescriptionsView = signalDescriptionsView.sliced(pos: nextQuotePos + 1).trimmed(); |
970 | } |
971 | } |
972 | } |
973 | |
974 | void QCanDbcFileParserPrivate::postProcessSignalMultiplexing() |
975 | { |
976 | // For the case of simple multiplexing we need to do the following for |
977 | // every message description: |
978 | // 1. Find the multiplexor signal |
979 | // 2. Replace all kQtDummySignal entries with the name of the multiplexor |
980 | // 3. While doing so, check if we have any signals with type |
981 | // SwitchAndSignal. This will mean that extended multiplexing is used |
982 | // 4. Also detect conditions when we have more than one multiplexor signal. |
983 | // This is an error as well, and message description should be discarded. |
984 | |
985 | QList<QtCanBus::UniqueId> uidsToRemove; |
986 | |
987 | for (auto &messageDesc : m_messageDescriptions) { |
988 | bool useExtendedMux = false; |
989 | QString multiplexorSignal; |
990 | auto &signalDescriptions = QCanMessageDescriptionPrivate::get(desc: messageDesc)->messageSignals; |
991 | for (const auto &signalDesc : std::as_const(t&: signalDescriptions)) { |
992 | if (signalDesc.multiplexState() == QtCanBus::MultiplexState::MultiplexorSwitch) { |
993 | if (multiplexorSignal.isEmpty()) { |
994 | multiplexorSignal = signalDesc.name(); |
995 | } else { |
996 | // invalid config |
997 | multiplexorSignal.clear(); |
998 | uidsToRemove.push_back(t: messageDesc.uniqueId()); |
999 | break; |
1000 | } |
1001 | } else if (signalDesc.multiplexState() == QtCanBus::MultiplexState::SwitchAndSignal) { |
1002 | // extended multiplexing |
1003 | useExtendedMux = true; |
1004 | } |
1005 | } |
1006 | if (!useExtendedMux && !multiplexorSignal.isEmpty()) { |
1007 | // iterate through all signal descriptions and update kQtDummySignal |
1008 | for (auto &signalDesc : signalDescriptions) { |
1009 | if (signalDesc.multiplexState() == QtCanBus::MultiplexState::MultiplexedSignal) { |
1010 | auto &muxValues = QCanSignalDescriptionPrivate::get(desc: signalDesc)->muxSignals; |
1011 | auto val = muxValues.value(key: kQtDummySignal); |
1012 | muxValues.remove(key: kQtDummySignal); |
1013 | muxValues.insert(key: multiplexorSignal, value: val); |
1014 | } |
1015 | } |
1016 | } else if (useExtendedMux) { |
1017 | // Iterate through all signal descriptions and check that we do |
1018 | // not have any kQtDummySignal entries. It such entry exists, this |
1019 | // means that there were errors while parsing extended multiplexing |
1020 | // table, and this message description is invalid |
1021 | for (const auto &signalDesc : std::as_const(t&: signalDescriptions)) { |
1022 | const auto muxSignals = signalDesc.multiplexSignals(); |
1023 | if (muxSignals.contains(key: kQtDummySignal)) { |
1024 | uidsToRemove.push_back(t: messageDesc.uniqueId()); |
1025 | break; |
1026 | } |
1027 | } |
1028 | } |
1029 | } |
1030 | |
1031 | for (const auto &uid : std::as_const(t&: uidsToRemove)) { |
1032 | m_messageDescriptions.remove(key: uid); |
1033 | addWarning(warning: QObject::tr(s: "Message description with unique id %1 is skipped because " |
1034 | "it has invalid multiplexing description." ). |
1035 | arg(a: qToUnderlying(e: uid))); |
1036 | } |
1037 | } |
1038 | |
1039 | void QCanDbcFileParserPrivate::addWarning(QString &&warning) |
1040 | { |
1041 | m_warnings.emplace_back(args&: warning); |
1042 | } |
1043 | |
1044 | void QCanDbcFileParserPrivate::addCurrentMessage() |
1045 | { |
1046 | if (m_isProcessingMessage) { |
1047 | auto uid = m_currentMessage.uniqueId(); |
1048 | if (!m_currentMessage.isValid()) { |
1049 | addWarning(warning: QObject::tr(s: "Message description with unique id %1 is skipped " |
1050 | "because it's not valid." ).arg(a: qToUnderlying(e: uid))); |
1051 | } else if (m_messageDescriptions.contains(key: uid)) { |
1052 | addWarning(warning: QObject::tr(s: "Message description with unique id %1 is skipped " |
1053 | "because such unique id is already used." ). |
1054 | arg(a: qToUnderlying(e: uid))); |
1055 | } else { |
1056 | m_messageDescriptions.insert(key: uid, value: m_currentMessage); |
1057 | } |
1058 | m_currentMessage = {}; |
1059 | m_isProcessingMessage = false; |
1060 | } |
1061 | } |
1062 | |
1063 | QList<QCanMessageDescription> QCanDbcFileParserPrivate::getMessages() const |
1064 | { |
1065 | return QList<QCanMessageDescription>(m_messageDescriptions.cbegin(), |
1066 | m_messageDescriptions.cend()); |
1067 | } |
1068 | |
1069 | QT_END_NAMESPACE |
1070 | |