KWeatherCore

capparser.cpp
1/*
2 * SPDX-FileCopyrightText: 2021 Anjani Kumar <anjanik012@gmail.com>
3 * SPDX-FileCopyrightText: 2021 Han Young <hanyoung@protonmail.com>
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "capparser.h"
8#include "capalertinfo.h"
9#include "capalertmessage.h"
10#include "caparea.h"
11#include "capnamedvalue.h"
12#include "capreference.h"
13#include "kweathercore_p.h"
14#include <KLocalizedString>
15#include <QDateTime>
16#include <QDebug>
17
18#include <optional>
19
20namespace KWeatherCore
21{
22
23template<typename T>
24struct MapEntry {
25 const char *name;
26 T value;
27};
28
29template<typename QStringT, typename EnumT, std::size_t N>
30static std::optional<EnumT> stringToValue(const QStringT &s, const MapEntry<EnumT> (&map)[N])
31{
32 const auto it = std::lower_bound(std::begin(map), std::end(map), s, [](auto lhs, auto rhs) {
33 return QLatin1String(lhs.name) < rhs;
34 });
35 if (it != std::end(map) && QLatin1String((*it).name) == s) {
36 return (*it).value;
37 }
38 return {};
39}
40
41// ### important: keep all the following tables sorted by name!
42static constexpr const MapEntry<CAPAlertInfo::Category> category_map[] = {
43 {"CBRNE", CAPAlertInfo::Category::CBRNE},
44 {"Env", CAPAlertInfo::Category::Environmental},
45 {"Fire", CAPAlertInfo::Category::Fire},
46 {"Geo", CAPAlertInfo::Category::Geophysical},
47 {"Health", CAPAlertInfo::Category::Health},
48 {"Infra", CAPAlertInfo::Category::Infrastructure},
49 {"Met", CAPAlertInfo::Category::Meteorological},
50 {"Other", CAPAlertInfo::Category::Other},
51 {"Rescue", CAPAlertInfo::Category::Rescue},
52 {"Safety", CAPAlertInfo::Category::Safety},
53 {"Security", CAPAlertInfo::Category::Security},
54 {"Transport", CAPAlertInfo::Category::Transport},
55};
56
57enum class Tags { ALERT, IDENTIFIER, SENDER, SENT_TIME, STATUS, MSG_TYPE, SCOPE, NOTE, INFO, REFERENCES };
58
59static constexpr const MapEntry<Tags> tag_map[] = {
60 {"alert", Tags::ALERT},
61 {"identifier", Tags::IDENTIFIER},
62 {"info", Tags::INFO},
63 {"msgType", Tags::MSG_TYPE},
64 {"note", Tags::NOTE},
65 {"references", Tags::REFERENCES},
66 {"scope", Tags::SCOPE},
67 {"sender", Tags::SENDER},
68 {"sent", Tags::SENT_TIME},
69 {"status", Tags::STATUS},
70};
71
72enum class InfoTags {
73 HEADLINE,
74 DESCRIPTION,
75 EVENT,
76 EVENTCODE,
77 EFFECTIVE_TIME,
78 ONSET_TIME,
79 EXPIRE_TIME,
80 CATEGORY,
81 INSTRUCTION,
82 URGENCY,
83 SEVERITY,
84 CERTAINITY,
85 PARAMETER,
86 AREA,
87 SENDERNAME,
88 LANGUAGE,
89 RESPONSETYPE,
90 CONTACT,
91 WEB,
92};
93
94static constexpr const MapEntry<InfoTags> info_tag_map[] = {
95 {"area", InfoTags::AREA},
96 {"category", InfoTags::CATEGORY},
97 {"certainty", InfoTags::CERTAINITY},
98 {"contact", InfoTags::CONTACT},
99 {"description", InfoTags::DESCRIPTION},
100 {"effective", InfoTags::EFFECTIVE_TIME},
101 {"event", InfoTags::EVENT},
102 {"eventCode", InfoTags::EVENTCODE},
103 {"expires", InfoTags::EXPIRE_TIME},
104 {"headline", InfoTags::HEADLINE},
105 {"instruction", InfoTags::INSTRUCTION},
106 {"language", InfoTags::LANGUAGE},
107 {"onset", InfoTags::ONSET_TIME},
108 {"parameter", InfoTags::PARAMETER},
109 {"responseType", InfoTags::RESPONSETYPE},
110 {"senderName", InfoTags::SENDERNAME},
111 {"severity", InfoTags::SEVERITY},
112 {"urgency", InfoTags::URGENCY},
113 {"web", InfoTags::WEB},
114};
115
116static constexpr const MapEntry<CAPAlertMessage::Status> status_map[] = {
120 {"System", CAPAlertMessage::Status::System},
122};
123
124static constexpr const MapEntry<CAPAlertMessage::MessageType> msgtype_map[] = {
130};
131
132static constexpr const MapEntry<CAPAlertMessage::Scope> scope_map[] = {
136};
137
138static constexpr const MapEntry<CAPAlertInfo::ResponseType> response_type_map[] = {
139 {"AllClear", CAPAlertInfo::ResponseType::AllClear},
140 {"Assess", CAPAlertInfo::ResponseType::Assess},
141 {"Avoid", CAPAlertInfo::ResponseType::Avoid},
142 {"Evacuate", CAPAlertInfo::ResponseType::Evacuate},
143 {"Execute", CAPAlertInfo::ResponseType::Execute},
144 {"Monitor", CAPAlertInfo::ResponseType::Monitor},
145 {"None", CAPAlertInfo::ResponseType::None},
146 {"Prepare", CAPAlertInfo::ResponseType::Prepare},
147 {"Shelter", CAPAlertInfo::ResponseType::Shelter},
148};
149
150CAPParser::CAPParser(const QByteArray &data)
151 : m_xml(data)
152{
153 bool flag = false;
154 if (!data.isEmpty()) {
155 while (m_xml.readNextStartElement()) {
156 if (m_xml.name() == QStringLiteral("alert")) {
157 flag = true;
158 break;
159 }
160 }
161 if (!flag) {
162 qWarning() << "Not a CAP XML";
163 }
164 }
165}
166
167CAPAlertMessage CAPParser::parse()
168{
169 CAPAlertMessage entry;
170 while (m_xml.readNextStartElement()) {
171 const auto tag = stringToValue(m_xml.name(), tag_map);
172 if (!tag) {
173 m_xml.skipCurrentElement();
174 continue;
175 }
176 switch (*tag) {
177 case Tags::IDENTIFIER:
178 entry.setIdentifier(m_xml.readElementText());
179 break;
180 case Tags::SENDER:
181 entry.setSender(m_xml.readElementText());
182 break;
183 case Tags::SENT_TIME:
184 entry.setSentTime(QDateTime::fromString(m_xml.readElementText(), Qt::ISODate));
185 break;
186 case Tags::STATUS: {
187 const auto elementText = m_xml.readElementText();
188 const auto status = stringToValue(elementText, status_map);
189 if (status) {
190 entry.setStatus(*status);
191 } else {
192 qWarning() << "Unknown status field" << elementText;
193 }
194 break;
195 }
196 case Tags::MSG_TYPE: {
197 const auto elementText = m_xml.readElementText();
198 const auto msgType = stringToValue(elementText, msgtype_map);
199 if (msgType) {
200 entry.setMessageType(*msgType);
201 } else {
202 qWarning() << "Unknown msgType field" << elementText;
203 }
204 break;
205 }
206 case Tags::SCOPE: {
207 const auto elementText = m_xml.readElementText();
208 const auto scope = stringToValue(elementText, scope_map);
209 if (scope) {
210 entry.setScope(*scope);
211 } else {
212 qWarning() << "Unknown scope field" << elementText;
213 }
214 break;
215 }
216 case Tags::NOTE:
217 entry.setNote(m_xml.readElementText());
218 break;
219 case Tags::INFO: {
220 auto info = parseInfo();
221 entry.addInfo(std::move(info));
222 break;
223 }
224 case Tags::REFERENCES:
225 entry.setReferences(parseReferences(m_xml.readElementText()));
226 break;
227 default:
228 m_xml.skipCurrentElement();
229 }
230 }
231 return entry;
232}
233
234CAPAlertInfo CAPParser::parseInfo()
235{
236 CAPAlertInfo info;
237
238 if (m_xml.name() == QLatin1String("info")) {
239 while (!m_xml.atEnd() && !(m_xml.isEndElement() && m_xml.name() == QLatin1String("info"))) {
240 m_xml.readNext();
241 if (!m_xml.isStartElement()) {
242 continue;
243 }
244 const auto tag = stringToValue(m_xml.name(), info_tag_map);
245 if (tag) {
246 switch (*tag) {
247 case InfoTags::CATEGORY: {
248 const auto s = m_xml.readElementText();
249 const auto category = stringToValue(s, category_map);
250 if (category) {
251 info.addCategory(*category);
252 }
253 break;
254 }
255 case InfoTags::EVENT:
256 info.setEvent(m_xml.readElementText());
257 break;
258 case InfoTags::URGENCY:
259 info.setUrgency(KWeatherCorePrivate::urgencyStringToEnum(m_xml.readElementText()));
260 break;
261 case InfoTags::SEVERITY:
262 info.setSeverity(KWeatherCorePrivate::severityStringToEnum(m_xml.readElementText()));
263 break;
264 case InfoTags::CERTAINITY:
265 info.setCertainty(KWeatherCorePrivate::certaintyStringToEnum(m_xml.readElementText()));
266 break;
267 case InfoTags::EFFECTIVE_TIME:
268 info.setEffectiveTime(QDateTime::fromString(m_xml.readElementText(), Qt::ISODate));
269 break;
270 case InfoTags::ONSET_TIME:
271 info.setOnsetTime(QDateTime::fromString(m_xml.readElementText(), Qt::ISODate));
272 break;
273 case InfoTags::EXPIRE_TIME:
274 info.setExpireTime(QDateTime::fromString(m_xml.readElementText(), Qt::ISODate));
275 break;
276 case InfoTags::HEADLINE:
277 info.setHeadline(m_xml.readElementText());
278 break;
279 case InfoTags::DESCRIPTION:
280 info.setDescription(m_xml.readElementText());
281 break;
282 case InfoTags::INSTRUCTION:
283 info.setInstruction(m_xml.readElementText());
284 break;
285 case InfoTags::PARAMETER: {
286 info.addParameter(parseNamedValue());
287 break;
288 }
289 case InfoTags::AREA: {
290 info.addArea(parseArea());
291 break;
292 }
293 case InfoTags::SENDERNAME: {
294 info.setSender(m_xml.readElementText());
295 break;
296 }
297 case InfoTags::LANGUAGE:
298 info.setLanguage(m_xml.readElementText());
299 break;
300 case InfoTags::RESPONSETYPE: {
301 const auto elementText = m_xml.readElementText();
302 if (const auto respType = stringToValue(elementText, response_type_map)) {
303 info.addResponseType(*respType);
304 } else {
305 qWarning() << "Unknown respone type value" << elementText;
306 }
307 break;
308 }
309 case InfoTags::CONTACT:
310 info.setContact(m_xml.readElementText());
311 break;
312 case InfoTags::WEB:
313 info.setWeb(m_xml.readElementText());
314 break;
315 case InfoTags::EVENTCODE:
316 info.addEventCode(parseNamedValue());
317 break;
318 }
319 } else {
320 if (m_xml.isStartElement()) {
321 qWarning() << "unknown element: " << m_xml.name();
322 }
323 }
324 }
325 }
326 return info;
327}
328
329CAPArea CAPParser::parseArea()
330{
331 CAPArea area;
332 while (!(m_xml.isEndElement() && m_xml.name() == QLatin1String("area"))) {
333 m_xml.readNext();
334 if (m_xml.name() == QLatin1String("areaDesc") && !m_xml.isEndElement()) {
335 area.setDescription(m_xml.readElementText());
336 } else if (m_xml.name() == QLatin1String("geocode") && !m_xml.isEndElement()) {
337 area.addGeoCode(parseNamedValue());
338 } else if (m_xml.name() == QLatin1String("polygon") && !m_xml.isEndElement()) {
339 area.addPolygon(KWeatherCorePrivate::stringToPolygon(m_xml.readElementText()));
340 } else if (m_xml.name() == QLatin1String("circle") && !m_xml.isEndElement()) {
341 const auto t = m_xml.readElementText();
342 const auto commaIdx = t.indexOf(QLatin1Char(','));
343 const auto spaceIdx = t.indexOf(QLatin1Char(' '));
344 if (commaIdx > 0 && spaceIdx > commaIdx && commaIdx < t.size()) {
345 CAPCircle circle;
346 circle.latitude = QStringView(t).left(commaIdx).toFloat();
347 circle.longitude = QStringView(t).mid(commaIdx + 1, spaceIdx - commaIdx - 1).toFloat();
348 circle.radius = QStringView(t).mid(spaceIdx).toFloat();
349 area.addCircle(std::move(circle));
350 }
351 } else if (m_xml.name() == QLatin1String("altitude") && !m_xml.isEndElement()) {
352 area.setAltitude(m_xml.readElementText().toFloat());
353 } else if (m_xml.name() == QLatin1String("ceiling") && !m_xml.isEndElement()) {
354 area.setCeiling(m_xml.readElementText().toFloat());
355 } else if (m_xml.isStartElement()) {
356 qDebug() << "unknown area element:" << m_xml.name();
357 }
358 }
359 return area;
360}
361
362CAPNamedValue CAPParser::parseNamedValue()
363{
364 CAPNamedValue value;
365 const auto elementName = m_xml.name().toString();
366 while (!m_xml.isEndElement() || m_xml.name() != elementName) {
367 m_xml.readNext();
368 if (m_xml.isStartElement() && m_xml.name() == QLatin1String("valueName")) {
369 value.name = m_xml.readElementText();
370 } else if (m_xml.isStartElement() && m_xml.name() == QLatin1String("value")) {
371 value.value = m_xml.readElementText();
372 } else if (m_xml.isStartElement()) {
373 qDebug() << "unknown named value element:" << m_xml.name();
374 }
375 }
376 return value;
377}
378
379std::vector<CAPReference> CAPParser::parseReferences(const QString &refsString)
380{
381 std::vector<CAPReference> refs;
382 // TODO for Qt 6: use QStringTokenizer
383 const auto refsSplit = refsString.split(QLatin1Char(' '), Qt::SkipEmptyParts);
384 refs.reserve(refsSplit.size());
385 for (const auto &refString : refsSplit) {
386 const auto refSplit = refString.split(QLatin1Char(','));
387 if (refSplit.size() != 3) {
388 qDebug() << "failed to parse CAP reference:" << refString;
389 continue;
390 }
391 refs.emplace_back(refSplit.at(0), refSplit.at(1), QDateTime::fromString(refSplit.at(2), Qt::ISODate));
392 }
393
394 return refs;
395}
396}
@ Test
Technical testing only, all recipients disregard.
@ Actual
Actionable by all targeted recipients.
@ Exercise
Actionable only by designated exercise participants.
@ Draft
A preliminary template or draft, not actionable in its current form.
@ Public
For general dissemination to unrestricted audiences.
@ Private
For dissemination only to specified addresses.
@ Restricted
For dissemination only to users with a known operational requirement.
@ Update
Updates and supercedes the earlier message(s) identified in references()
@ Error
Indicates rejection of the message(s) identified in references()
@ Alert
Initial information requiring attention by targeted recipients.
@ Acknowledge
Acknowledges receipt and acceptance of the message(s) identified in references()
@ Cancel
Cancels the earlier message(s) identified in references()
Q_SCRIPTABLE CaptureState status()
Category category(StandardShortcut id)
bool isEmpty() const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
void reserve(qsizetype size)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QStringView left(qsizetype length) const const
QStringView mid(qsizetype start, qsizetype length) const const
float toFloat(bool *ok) const const
SkipEmptyParts
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:42 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.