ion_envcan.cpp 69.7 KB
Newer Older
1
2
3
4
5
/*
    SPDX-FileCopyrightText: 2007-2011, 2019 Shawn Starr <shawn.starr@rogers.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
6
7
8
9
10

/* Ion for Environment Canada XML data */

#include "ion_envcan.h"

11
12
#include "ion_envcandebug.h"

13
#include <KIO/Job>
14
#include <KLocalizedString>
Alexander Lohnau's avatar
Alexander Lohnau committed
15
#include <KUnitConversion/Converter>
16

17
#include <QRegularExpression>
18
#include <QTimeZone>
19

20
WeatherData::WeatherData()
Alexander Lohnau's avatar
Alexander Lohnau committed
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    : stationLatitude(qQNaN())
    , stationLongitude(qQNaN())
    , temperature(qQNaN())
    , dewpoint(qQNaN())
    , windchill(qQNaN())
    , pressure(qQNaN())
    , visibility(qQNaN())
    , humidity(qQNaN())
    , windSpeed(qQNaN())
    , windGust(qQNaN())
    , normalHigh(qQNaN())
    , normalLow(qQNaN())
    , prevHigh(qQNaN())
    , prevLow(qQNaN())
    , recordHigh(qQNaN())
    , recordLow(qQNaN())
    , recordRain(qQNaN())
    , recordSnow(qQNaN())
39
40
41
42
{
}

WeatherData::ForecastInfo::ForecastInfo()
Alexander Lohnau's avatar
Alexander Lohnau committed
43
44
45
    : tempHigh(qQNaN())
    , tempLow(qQNaN())
    , popPrecent(qQNaN())
46
47
48
{
}

49
50
// ctor, dtor
EnvCanadaIon::EnvCanadaIon(QObject *parent, const QVariantList &args)
Alexander Lohnau's avatar
Alexander Lohnau committed
51
    : IonInterface(parent, args)
52
{
53
54
    // Get the real city XML URL so we can parse this
    getXMLSetup();
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
}

void EnvCanadaIon::deleteForecasts()
{
    QMutableHashIterator<QString, WeatherData> it(m_weatherData);
    while (it.hasNext()) {
        it.next();
        WeatherData &item = it.value();
        qDeleteAll(item.warnings);
        item.warnings.clear();

        qDeleteAll(item.watches);
        item.watches.clear();

        qDeleteAll(item.forecasts);
        item.forecasts.clear();
    }
}

void EnvCanadaIon::reset()
{
    deleteForecasts();
    emitWhenSetup = true;
    m_sourcesToReset = sources();
    getXMLSetup();
}

EnvCanadaIon::~EnvCanadaIon()
{
    // Destroy each watch/warning stored in a QVector
    deleteForecasts();
}

88
QMap<QString, IonInterface::ConditionIcons> EnvCanadaIon::setupConditionIconMappings() const
89
{
Alexander Lohnau's avatar
Alexander Lohnau committed
90
    return QMap<QString, ConditionIcons>{
91

92
        // Explicit periods
Alexander Lohnau's avatar
Alexander Lohnau committed
93
94
95
96
        {QStringLiteral("mainly sunny"), FewCloudsDay},
        {QStringLiteral("mainly clear"), FewCloudsNight},
        {QStringLiteral("sunny"), ClearDay},
        {QStringLiteral("clear"), ClearNight},
97

98
        // Available conditions
Alexander Lohnau's avatar
Alexander Lohnau committed
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
        {QStringLiteral("blowing snow"), Snow},
        {QStringLiteral("cloudy"), Overcast},
        {QStringLiteral("distant precipitation"), LightRain},
        {QStringLiteral("drifting snow"), Flurries},
        {QStringLiteral("drizzle"), LightRain},
        {QStringLiteral("dust"), NotAvailable},
        {QStringLiteral("dust devils"), NotAvailable},
        {QStringLiteral("fog"), Mist},
        {QStringLiteral("fog bank near station"), Mist},
        {QStringLiteral("fog depositing ice"), Mist},
        {QStringLiteral("fog patches"), Mist},
        {QStringLiteral("freezing drizzle"), FreezingDrizzle},
        {QStringLiteral("freezing rain"), FreezingRain},
        {QStringLiteral("funnel cloud"), NotAvailable},
        {QStringLiteral("hail"), Hail},
        {QStringLiteral("haze"), Haze},
        {QStringLiteral("heavy blowing snow"), Snow},
        {QStringLiteral("heavy drifting snow"), Snow},
        {QStringLiteral("heavy drizzle"), LightRain},
        {QStringLiteral("heavy hail"), Hail},
        {QStringLiteral("heavy mixed rain and drizzle"), LightRain},
        {QStringLiteral("heavy mixed rain and snow shower"), RainSnow},
        {QStringLiteral("heavy rain"), Rain},
        {QStringLiteral("heavy rain and snow"), RainSnow},
        {QStringLiteral("heavy rainshower"), Rain},
        {QStringLiteral("heavy snow"), Snow},
        {QStringLiteral("heavy snow pellets"), Snow},
        {QStringLiteral("heavy snowshower"), Snow},
        {QStringLiteral("heavy thunderstorm with hail"), Thunderstorm},
        {QStringLiteral("heavy thunderstorm with rain"), Thunderstorm},
        {QStringLiteral("ice crystals"), Flurries},
        {QStringLiteral("ice pellets"), Hail},
        {QStringLiteral("increasing cloud"), Overcast},
        {QStringLiteral("light drizzle"), LightRain},
        {QStringLiteral("light freezing drizzle"), FreezingRain},
        {QStringLiteral("light freezing rain"), FreezingRain},
        {QStringLiteral("light rain"), LightRain},
        {QStringLiteral("light rainshower"), LightRain},
        {QStringLiteral("light snow"), LightSnow},
        {QStringLiteral("light snow pellets"), LightSnow},
        {QStringLiteral("light snowshower"), Flurries},
        {QStringLiteral("lightning visible"), Thunderstorm},
        {QStringLiteral("mist"), Mist},
        {QStringLiteral("mixed rain and drizzle"), LightRain},
        {QStringLiteral("mixed rain and snow shower"), RainSnow},
        {QStringLiteral("not reported"), NotAvailable},
        {QStringLiteral("rain"), Rain},
        {QStringLiteral("rain and snow"), RainSnow},
        {QStringLiteral("rainshower"), LightRain},
        {QStringLiteral("recent drizzle"), LightRain},
        {QStringLiteral("recent dust or sand storm"), NotAvailable},
        {QStringLiteral("recent fog"), Mist},
        {QStringLiteral("recent freezing precipitation"), FreezingDrizzle},
        {QStringLiteral("recent hail"), Hail},
        {QStringLiteral("recent rain"), Rain},
        {QStringLiteral("recent rain and snow"), RainSnow},
        {QStringLiteral("recent rainshower"), Rain},
        {QStringLiteral("recent snow"), Snow},
        {QStringLiteral("recent snowshower"), Flurries},
        {QStringLiteral("recent thunderstorm"), Thunderstorm},
        {QStringLiteral("recent thunderstorm with hail"), Thunderstorm},
        {QStringLiteral("recent thunderstorm with heavy hail"), Thunderstorm},
        {QStringLiteral("recent thunderstorm with heavy rain"), Thunderstorm},
        {QStringLiteral("recent thunderstorm with rain"), Thunderstorm},
        {QStringLiteral("sand or dust storm"), NotAvailable},
        {QStringLiteral("severe sand or dust storm"), NotAvailable},
        {QStringLiteral("shallow fog"), Mist},
        {QStringLiteral("smoke"), NotAvailable},
        {QStringLiteral("snow"), Snow},
        {QStringLiteral("snow crystals"), Flurries},
        {QStringLiteral("snow grains"), Flurries},
        {QStringLiteral("squalls"), Snow},
        {QStringLiteral("thunderstorm"), Thunderstorm},
        {QStringLiteral("thunderstorm with hail"), Thunderstorm},
        {QStringLiteral("thunderstorm with rain"), Thunderstorm},
        {QStringLiteral("thunderstorm with light rainshowers"), Thunderstorm},
        {QStringLiteral("thunderstorm with heavy rainshowers"), Thunderstorm},
        {QStringLiteral("thunderstorm with sand or dust storm"), Thunderstorm},
        {QStringLiteral("thunderstorm without precipitation"), Thunderstorm},
        {QStringLiteral("tornado"), NotAvailable},
179
    };
180
181
}

182
QMap<QString, IonInterface::ConditionIcons> EnvCanadaIon::setupForecastIconMappings() const
183
{
Alexander Lohnau's avatar
Alexander Lohnau committed
184
    return QMap<QString, ConditionIcons>{
185

186
        // Abbreviated forecast descriptions
Alexander Lohnau's avatar
Alexander Lohnau committed
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
        {QStringLiteral("a few flurries"), Flurries},
        {QStringLiteral("a few flurries mixed with ice pellets"), RainSnow},
        {QStringLiteral("a few flurries or rain showers"), RainSnow},
        {QStringLiteral("a few flurries or thundershowers"), RainSnow},
        {QStringLiteral("a few rain showers or flurries"), RainSnow},
        {QStringLiteral("a few rain showers or wet flurries"), RainSnow},
        {QStringLiteral("a few showers"), LightRain},
        {QStringLiteral("a few showers or drizzle"), LightRain},
        {QStringLiteral("a few showers or thundershowers"), Thunderstorm},
        {QStringLiteral("a few showers or thunderstorms"), Thunderstorm},
        {QStringLiteral("a few thundershowers"), Thunderstorm},
        {QStringLiteral("a few thunderstorms"), Thunderstorm},
        {QStringLiteral("a few wet flurries"), RainSnow},
        {QStringLiteral("a few wet flurries or rain showers"), RainSnow},
        {QStringLiteral("a mix of sun and cloud"), PartlyCloudyDay},
        {QStringLiteral("cloudy with sunny periods"), PartlyCloudyDay},
        {QStringLiteral("partly cloudy"), PartlyCloudyDay},
        {QStringLiteral("mainly cloudy"), PartlyCloudyDay},
        {QStringLiteral("mainly sunny"), FewCloudsDay},
        {QStringLiteral("sunny"), ClearDay},
        {QStringLiteral("blizzard"), Snow},
        {QStringLiteral("clear"), ClearNight},
        {QStringLiteral("cloudy"), Overcast},
        {QStringLiteral("drizzle"), LightRain},
        {QStringLiteral("drizzle mixed with freezing drizzle"), FreezingDrizzle},
        {QStringLiteral("drizzle mixed with rain"), LightRain},
        {QStringLiteral("drizzle or freezing drizzle"), LightRain},
        {QStringLiteral("drizzle or rain"), LightRain},
        {QStringLiteral("flurries"), Flurries},
        {QStringLiteral("flurries at times heavy"), Flurries},
        {QStringLiteral("flurries at times heavy or rain snowers"), RainSnow},
        {QStringLiteral("flurries mixed with ice pellets"), FreezingRain},
        {QStringLiteral("flurries or ice pellets"), FreezingRain},
        {QStringLiteral("flurries or rain showers"), RainSnow},
        {QStringLiteral("flurries or thundershowers"), Flurries},
        {QStringLiteral("fog"), Mist},
        {QStringLiteral("fog developing"), Mist},
        {QStringLiteral("fog dissipating"), Mist},
        {QStringLiteral("fog patches"), Mist},
        {QStringLiteral("freezing drizzle"), FreezingDrizzle},
        {QStringLiteral("freezing rain"), FreezingRain},
        {QStringLiteral("freezing rain mixed with rain"), FreezingRain},
        {QStringLiteral("freezing rain mixed with snow"), FreezingRain},
        {QStringLiteral("freezing rain or ice pellets"), FreezingRain},
        {QStringLiteral("freezing rain or rain"), FreezingRain},
        {QStringLiteral("freezing rain or snow"), FreezingRain},
        {QStringLiteral("ice fog"), Mist},
        {QStringLiteral("ice fog developing"), Mist},
        {QStringLiteral("ice fog dissipating"), Mist},
        {QStringLiteral("ice pellets"), Hail},
        {QStringLiteral("ice pellets mixed with freezing rain"), Hail},
        {QStringLiteral("ice pellets mixed with snow"), Hail},
        {QStringLiteral("ice pellets or snow"), RainSnow},
        {QStringLiteral("light snow"), LightSnow},
        {QStringLiteral("light snow and blizzard"), LightSnow},
        {QStringLiteral("light snow and blizzard and blowing snow"), Snow},
        {QStringLiteral("light snow and blowing snow"), LightSnow},
        {QStringLiteral("light snow mixed with freezing drizzle"), FreezingDrizzle},
        {QStringLiteral("light snow mixed with freezing rain"), FreezingRain},
        {QStringLiteral("light snow or ice pellets"), LightSnow},
        {QStringLiteral("light snow or rain"), RainSnow},
        {QStringLiteral("light wet snow"), RainSnow},
        {QStringLiteral("light wet snow or rain"), RainSnow},
        {QStringLiteral("local snow squalls"), Snow},
        {QStringLiteral("near blizzard"), Snow},
        {QStringLiteral("overcast"), Overcast},
        {QStringLiteral("increasing cloudiness"), Overcast},
        {QStringLiteral("increasing clouds"), Overcast},
        {QStringLiteral("periods of drizzle"), LightRain},
        {QStringLiteral("periods of drizzle mixed with freezing drizzle"), FreezingDrizzle},
        {QStringLiteral("periods of drizzle mixed with rain"), LightRain},
        {QStringLiteral("periods of drizzle or freezing drizzle"), FreezingDrizzle},
        {QStringLiteral("periods of drizzle or rain"), LightRain},
        {QStringLiteral("periods of freezing drizzle"), FreezingDrizzle},
        {QStringLiteral("periods of freezing drizzle or drizzle"), FreezingDrizzle},
        {QStringLiteral("periods of freezing drizzle or rain"), FreezingDrizzle},
        {QStringLiteral("periods of freezing rain"), FreezingRain},
        {QStringLiteral("periods of freezing rain mixed with ice pellets"), FreezingRain},
        {QStringLiteral("periods of freezing rain mixed with rain"), FreezingRain},
        {QStringLiteral("periods of freezing rain mixed with snow"), FreezingRain},
        {QStringLiteral("periods of freezing rain mixed with freezing drizzle"), FreezingRain},
        {QStringLiteral("periods of freezing rain or ice pellets"), FreezingRain},
        {QStringLiteral("periods of freezing rain or rain"), FreezingRain},
        {QStringLiteral("periods of freezing rain or snow"), FreezingRain},
        {QStringLiteral("periods of ice pellets"), Hail},
        {QStringLiteral("periods of ice pellets mixed with freezing rain"), Hail},
        {QStringLiteral("periods of ice pellets mixed with snow"), Hail},
        {QStringLiteral("periods of ice pellets or freezing rain"), Hail},
        {QStringLiteral("periods of ice pellets or snow"), Hail},
        {QStringLiteral("periods of light snow"), LightSnow},
        {QStringLiteral("periods of light snow and blizzard"), Snow},
        {QStringLiteral("periods of light snow and blizzard and blowing snow"), Snow},
        {QStringLiteral("periods of light snow and blowing snow"), LightSnow},
        {QStringLiteral("periods of light snow mixed with freezing drizzle"), RainSnow},
        {QStringLiteral("periods of light snow mixed with freezing rain"), RainSnow},
        {QStringLiteral("periods of light snow mixed with ice pellets"), LightSnow},
        {QStringLiteral("periods of light snow mixed with rain"), RainSnow},
        {QStringLiteral("periods of light snow or freezing drizzle"), RainSnow},
        {QStringLiteral("periods of light snow or freezing rain"), RainSnow},
        {QStringLiteral("periods of light snow or ice pellets"), LightSnow},
        {QStringLiteral("periods of light snow or rain"), RainSnow},
        {QStringLiteral("periods of light wet snow"), LightSnow},
        {QStringLiteral("periods of light wet snow mixed with rain"), RainSnow},
        {QStringLiteral("periods of light wet snow or rain"), RainSnow},
        {QStringLiteral("periods of rain"), Rain},
        {QStringLiteral("periods of rain mixed with freezing rain"), Rain},
        {QStringLiteral("periods of rain mixed with snow"), RainSnow},
        {QStringLiteral("periods of rain or drizzle"), Rain},
        {QStringLiteral("periods of rain or freezing rain"), Rain},
        {QStringLiteral("periods of rain or thundershowers"), Showers},
        {QStringLiteral("periods of rain or thunderstorms"), Thunderstorm},
        {QStringLiteral("periods of rain or snow"), RainSnow},
        {QStringLiteral("periods of snow"), Snow},
        {QStringLiteral("periods of snow and blizzard"), Snow},
        {QStringLiteral("periods of snow and blizzard and blowing snow"), Snow},
        {QStringLiteral("periods of snow and blowing snow"), Snow},
        {QStringLiteral("periods of snow mixed with freezing drizzle"), RainSnow},
        {QStringLiteral("periods of snow mixed with freezing rain"), RainSnow},
        {QStringLiteral("periods of snow mixed with ice pellets"), Snow},
        {QStringLiteral("periods of snow mixed with rain"), RainSnow},
        {QStringLiteral("periods of snow or freezing drizzle"), RainSnow},
        {QStringLiteral("periods of snow or freezing rain"), RainSnow},
        {QStringLiteral("periods of snow or ice pellets"), Snow},
        {QStringLiteral("periods of snow or rain"), RainSnow},
        {QStringLiteral("periods of rain or snow"), RainSnow},
        {QStringLiteral("periods of wet snow"), Snow},
        {QStringLiteral("periods of wet snow mixed with rain"), RainSnow},
        {QStringLiteral("periods of wet snow or rain"), RainSnow},
        {QStringLiteral("rain"), Rain},
        {QStringLiteral("rain at times heavy"), Rain},
        {QStringLiteral("rain at times heavy mixed with freezing rain"), FreezingRain},
        {QStringLiteral("rain at times heavy mixed with snow"), RainSnow},
        {QStringLiteral("rain at times heavy or drizzle"), Rain},
        {QStringLiteral("rain at times heavy or freezing rain"), Rain},
        {QStringLiteral("rain at times heavy or snow"), RainSnow},
        {QStringLiteral("rain at times heavy or thundershowers"), Showers},
        {QStringLiteral("rain at times heavy or thunderstorms"), Thunderstorm},
        {QStringLiteral("rain mixed with freezing rain"), FreezingRain},
        {QStringLiteral("rain mixed with snow"), RainSnow},
        {QStringLiteral("rain or drizzle"), Rain},
        {QStringLiteral("rain or freezing rain"), Rain},
        {QStringLiteral("rain or snow"), RainSnow},
        {QStringLiteral("rain or thundershowers"), Showers},
        {QStringLiteral("rain or thunderstorms"), Thunderstorm},
        {QStringLiteral("rain showers or flurries"), RainSnow},
        {QStringLiteral("rain showers or wet flurries"), RainSnow},
        {QStringLiteral("showers"), Showers},
        {QStringLiteral("showers at times heavy"), Showers},
        {QStringLiteral("showers at times heavy or thundershowers"), Showers},
        {QStringLiteral("showers at times heavy or thunderstorms"), Thunderstorm},
        {QStringLiteral("showers or drizzle"), Showers},
        {QStringLiteral("showers or thundershowers"), Thunderstorm},
        {QStringLiteral("showers or thunderstorms"), Thunderstorm},
        {QStringLiteral("smoke"), NotAvailable},
        {QStringLiteral("snow"), Snow},
        {QStringLiteral("snow and blizzard"), Snow},
        {QStringLiteral("snow and blizzard and blowing snow"), Snow},
        {QStringLiteral("snow and blowing snow"), Snow},
        {QStringLiteral("snow at times heavy"), Snow},
        {QStringLiteral("snow at times heavy and blizzard"), Snow},
        {QStringLiteral("snow at times heavy and blowing snow"), Snow},
        {QStringLiteral("snow at times heavy mixed with freezing drizzle"), RainSnow},
        {QStringLiteral("snow at times heavy mixed with freezing rain"), RainSnow},
        {QStringLiteral("snow at times heavy mixed with ice pellets"), Snow},
        {QStringLiteral("snow at times heavy mixed with rain"), RainSnow},
        {QStringLiteral("snow at times heavy or freezing rain"), RainSnow},
        {QStringLiteral("snow at times heavy or ice pellets"), Snow},
        {QStringLiteral("snow at times heavy or rain"), RainSnow},
        {QStringLiteral("snow mixed with freezing drizzle"), RainSnow},
        {QStringLiteral("snow mixed with freezing rain"), RainSnow},
        {QStringLiteral("snow mixed with ice pellets"), Snow},
        {QStringLiteral("snow mixed with rain"), RainSnow},
        {QStringLiteral("snow or freezing drizzle"), RainSnow},
        {QStringLiteral("snow or freezing rain"), RainSnow},
        {QStringLiteral("snow or ice pellets"), Snow},
        {QStringLiteral("snow or rain"), RainSnow},
        {QStringLiteral("snow squalls"), Snow},
        {QStringLiteral("sunny"), ClearDay},
        {QStringLiteral("sunny with cloudy periods"), PartlyCloudyDay},
        {QStringLiteral("thunderstorms"), Thunderstorm},
        {QStringLiteral("thunderstorms and possible hail"), Thunderstorm},
        {QStringLiteral("wet flurries"), Flurries},
        {QStringLiteral("wet flurries at times heavy"), Flurries},
        {QStringLiteral("wet flurries at times heavy or rain snowers"), RainSnow},
        {QStringLiteral("wet flurries or rain showers"), RainSnow},
        {QStringLiteral("wet snow"), Snow},
        {QStringLiteral("wet snow at times heavy"), Snow},
        {QStringLiteral("wet snow at times heavy mixed with rain"), RainSnow},
        {QStringLiteral("wet snow mixed with rain"), RainSnow},
        {QStringLiteral("wet snow or rain"), RainSnow},
        {QStringLiteral("windy"), NotAvailable},

        {QStringLiteral("chance of drizzle mixed with freezing drizzle"), LightRain},
        {QStringLiteral("chance of flurries mixed with ice pellets"), Flurries},
        {QStringLiteral("chance of flurries or ice pellets"), Flurries},
        {QStringLiteral("chance of flurries or rain showers"), RainSnow},
        {QStringLiteral("chance of flurries or thundershowers"), RainSnow},
        {QStringLiteral("chance of freezing drizzle"), FreezingDrizzle},
        {QStringLiteral("chance of freezing rain"), FreezingRain},
        {QStringLiteral("chance of freezing rain mixed with snow"), RainSnow},
        {QStringLiteral("chance of freezing rain or rain"), FreezingRain},
        {QStringLiteral("chance of freezing rain or snow"), RainSnow},
        {QStringLiteral("chance of light snow and blowing snow"), LightSnow},
        {QStringLiteral("chance of light snow mixed with freezing drizzle"), LightSnow},
        {QStringLiteral("chance of light snow mixed with ice pellets"), LightSnow},
        {QStringLiteral("chance of light snow mixed with rain"), RainSnow},
        {QStringLiteral("chance of light snow or freezing rain"), RainSnow},
        {QStringLiteral("chance of light snow or ice pellets"), LightSnow},
        {QStringLiteral("chance of light snow or rain"), RainSnow},
        {QStringLiteral("chance of light wet snow"), Snow},
        {QStringLiteral("chance of rain"), Rain},
        {QStringLiteral("chance of rain at times heavy"), Rain},
        {QStringLiteral("chance of rain mixed with snow"), RainSnow},
        {QStringLiteral("chance of rain or drizzle"), Rain},
        {QStringLiteral("chance of rain or freezing rain"), Rain},
        {QStringLiteral("chance of rain or snow"), RainSnow},
        {QStringLiteral("chance of rain showers or flurries"), RainSnow},
        {QStringLiteral("chance of rain showers or wet flurries"), RainSnow},
        {QStringLiteral("chance of severe thunderstorms"), Thunderstorm},
        {QStringLiteral("chance of showers at times heavy"), Rain},
        {QStringLiteral("chance of showers at times heavy or thundershowers"), Thunderstorm},
        {QStringLiteral("chance of showers at times heavy or thunderstorms"), Thunderstorm},
        {QStringLiteral("chance of showers or thundershowers"), Thunderstorm},
        {QStringLiteral("chance of showers or thunderstorms"), Thunderstorm},
        {QStringLiteral("chance of snow"), Snow},
        {QStringLiteral("chance of snow and blizzard"), Snow},
        {QStringLiteral("chance of snow mixed with freezing drizzle"), Snow},
        {QStringLiteral("chance of snow mixed with freezing rain"), RainSnow},
        {QStringLiteral("chance of snow mixed with rain"), RainSnow},
        {QStringLiteral("chance of snow or rain"), RainSnow},
        {QStringLiteral("chance of snow squalls"), Snow},
        {QStringLiteral("chance of thundershowers"), Showers},
        {QStringLiteral("chance of thunderstorms"), Thunderstorm},
        {QStringLiteral("chance of thunderstorms and possible hail"), Thunderstorm},
        {QStringLiteral("chance of wet flurries"), Flurries},
        {QStringLiteral("chance of wet flurries at times heavy"), Flurries},
        {QStringLiteral("chance of wet flurries or rain showers"), RainSnow},
        {QStringLiteral("chance of wet snow"), Snow},
        {QStringLiteral("chance of wet snow mixed with rain"), RainSnow},
        {QStringLiteral("chance of wet snow or rain"), RainSnow},
427
    };
428
429
}

Alexander Lohnau's avatar
Alexander Lohnau committed
430
QMap<QString, IonInterface::ConditionIcons> const &EnvCanadaIon::conditionIcons() const
431
432
433
434
435
{
    static QMap<QString, ConditionIcons> const condval = setupConditionIconMappings();
    return condval;
}

Alexander Lohnau's avatar
Alexander Lohnau committed
436
QMap<QString, IonInterface::ConditionIcons> const &EnvCanadaIon::forecastIcons() const
437
438
439
440
441
{
    static QMap<QString, ConditionIcons> const foreval = setupForecastIconMappings();
    return foreval;
}

Alexander Lohnau's avatar
Alexander Lohnau committed
442
QStringList EnvCanadaIon::validate(const QString &source) const
443
444
{
    QStringList placeList;
445

446
447
448
449
    QString sourceNormalized = source.toUpper();
    QHash<QString, EnvCanadaIon::XMLMapInfo>::const_iterator it = m_places.constBegin();
    while (it != m_places.constEnd()) {
        if (it.key().toUpper().contains(sourceNormalized)) {
450
            placeList.append(QStringLiteral("place|") + it.key());
451
452
453
454
455
456
457
458
459
        }
        ++it;
    }

    placeList.sort();
    return placeList;
}

// Get a specific Ion's data
Alexander Lohnau's avatar
Alexander Lohnau committed
460
bool EnvCanadaIon::updateIonSource(const QString &source)
461
{
Alexander Lohnau's avatar
Alexander Lohnau committed
462
    // qCDebug(IONENGINE_ENVCAN) << "updateIonSource()" << source;
463
464
465
466
467

    // We expect the applet to send the source in the following tokenization:
    // ionname|validate|place_name - Triggers validation of place
    // ionname|weather|place_name - Triggers receiving weather of place

468
    const QStringList sourceAction = source.split(QLatin1Char('|'));
469
470
471

    // Guard: if the size of array is not 2 then we have bad data, return an error
    if (sourceAction.size() < 2) {
472
        setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed"));
473
474
475
        return true;
    }

476
477
    if (sourceAction[1] == QLatin1String("validate") && sourceAction.size() > 2) {
        const QStringList result = validate(sourceAction[2]);
478

479
480
481
        const QString reply = (result.size() == 1        ? QStringLiteral("envcan|valid|single|") + result[0]
                                   : (result.size() > 1) ? QStringLiteral("envcan|valid|multiple|") + result.join(QLatin1Char('|'))
                                                         : QStringLiteral("envcan|invalid|single|") + sourceAction[2]);
482
483
484
485
486
        setData(source, QStringLiteral("validate"), reply);

        return true;
    }
    if (sourceAction[1] == QLatin1String("weather") && sourceAction.size() > 2) {
487
488
489
        getXMLData(source);
        return true;
    }
490
    setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed"));
491
    return true;
492
493
494
495
496
}

// Parses city list and gets the correct city based on ID number
void EnvCanadaIon::getXMLSetup()
{
Alexander Lohnau's avatar
Alexander Lohnau committed
497
    // qCDebug(IONENGINE_ENVCAN) << "getXMLSetup()";
498
499
500

    // If network is down, we need to spin and wait

501
    const QUrl url(QStringLiteral("http://dd.weather.gc.ca/citypage_weather/xml/siteList.xml"));
502

Alexander Lohnau's avatar
Alexander Lohnau committed
503
    KIO::TransferJob *getJob = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
504
505

    m_xmlSetup.clear();
Alexander Lohnau's avatar
Alexander Lohnau committed
506
507
    connect(getJob, &KIO::TransferJob::data, this, &EnvCanadaIon::setup_slotDataArrived);
    connect(getJob, &KJob::result, this, &EnvCanadaIon::setup_slotJobFinished);
508
509
510
}

// Gets specific city XML data
Alexander Lohnau's avatar
Alexander Lohnau committed
511
void EnvCanadaIon::getXMLData(const QString &source)
512
{
Alexander Lohnau's avatar
Alexander Lohnau committed
513
    for (const QString &fetching : qAsConst(m_jobList)) {
514
515
516
517
518
519
        if (fetching == source) {
            // already getting this source and awaiting the data
            return;
        }
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
520
    // qCDebug(IONENGINE_ENVCAN) << source;
521
522
523

    // Demunge source name for key only.
    QString dataKey = source;
524
    dataKey.remove(QStringLiteral("envcan|weather|"));
Alexander Lohnau's avatar
Alexander Lohnau committed
525
    const XMLMapInfo &place = m_places[dataKey];
526

Alexander Lohnau's avatar
Alexander Lohnau committed
527
528
529
530
    const QUrl url(QLatin1String("http://dd.weather.gc.ca/citypage_weather/xml/") + place.territoryName + QLatin1Char('/') + place.cityCode
                   + QStringLiteral("_e.xml"));
    // url="file:///home/spstarr/Desktop/s0000649_e.xml";
    // qCDebug(IONENGINE_ENVCAN) << "Will Try URL: " << url;
531

532
    if (place.territoryName.isEmpty() && place.cityCode.isEmpty()) {
533
        setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed"));
534
535
536
        return;
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
537
    KIO::TransferJob *getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo);
538

539
540
    m_jobXml.insert(getJob, new QXmlStreamReader);
    m_jobList.insert(getJob, source);
541

Alexander Lohnau's avatar
Alexander Lohnau committed
542
543
    connect(getJob, &KIO::TransferJob::data, this, &EnvCanadaIon::slotDataArrived);
    connect(getJob, &KJob::result, this, &EnvCanadaIon::slotJobFinished);
544
545
546
547
548
549
550
}

void EnvCanadaIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data)
{
    Q_UNUSED(job)

    if (data.isEmpty()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
551
        // qCDebug(IONENGINE_ENVCAN) << "done!";
552
553
554
555
        return;
    }

    // Send to xml.
Alexander Lohnau's avatar
Alexander Lohnau committed
556
    // qCDebug(IONENGINE_ENVCAN) << data;
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
    m_xmlSetup.addData(data);
}

void EnvCanadaIon::slotDataArrived(KIO::Job *job, const QByteArray &data)
{
    if (data.isEmpty() || !m_jobXml.contains(job)) {
        return;
    }

    // Send to xml.
    m_jobXml[job]->addData(data);
}

void EnvCanadaIon::slotJobFinished(KJob *job)
{
    // Dual use method, if we're fetching location data to parse we need to do this first
    const QString source = m_jobList.value(job);
Alexander Lohnau's avatar
Alexander Lohnau committed
574
    // qCDebug(IONENGINE_ENVCAN) << source << m_sourcesToReset.contains(source);
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
    setData(source, Data());
    QXmlStreamReader *reader = m_jobXml.value(job);
    if (reader) {
        readXMLData(m_jobList[job], *reader);
    }

    m_jobList.remove(job);
    delete m_jobXml[job];
    m_jobXml.remove(job);

    if (m_sourcesToReset.contains(source)) {
        m_sourcesToReset.removeAll(source);

        // so the weather engine updates it's data
        forceImmediateUpdateOfAllVisualizations();

        // update the clients of our engine
        emit forceUpdate(this, source);
    }
}

void EnvCanadaIon::setup_slotJobFinished(KJob *job)
{
    Q_UNUSED(job)
    const bool success = readXMLSetup();
    m_xmlSetup.clear();
Alexander Lohnau's avatar
Alexander Lohnau committed
601
    // qCDebug(IONENGINE_ENVCAN) << success << m_sourcesToReset;
602
603
604
605
606
607
608
609
610
611
612
    setInitialized(success);
}

// Parse the city list and store into a QMap
bool EnvCanadaIon::readXMLSetup()
{
    bool success = false;
    QString territory;
    QString code;
    QString cityName;

Alexander Lohnau's avatar
Alexander Lohnau committed
613
    // qCDebug(IONENGINE_ENVCAN) << "readXMLSetup()";
614
615
616
617

    while (!m_xmlSetup.atEnd()) {
        m_xmlSetup.readNext();

618
619
        const QStringRef elementName = m_xmlSetup.name();

620
621
        if (m_xmlSetup.isStartElement()) {
            // XML ID code to match filename
622
            if (elementName == QLatin1String("site")) {
623
                code = m_xmlSetup.attributes().value(QStringLiteral("code")).toString();
624
625
            }

626
            if (elementName == QLatin1String("nameEn")) {
627
628
629
                cityName = m_xmlSetup.readElementText(); // Name of cities
            }

630
            if (elementName == QLatin1String("provinceCode")) {
631
632
633
634
                territory = m_xmlSetup.readElementText(); // Provinces/Territory list
            }
        }

635
        if (m_xmlSetup.isEndElement() && elementName == QLatin1String("site")) {
636
            EnvCanadaIon::XMLMapInfo info;
637
            QString tmp = cityName + QStringLiteral(", ") + territory; // Build the key name.
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652

            // Set the mappings
            info.cityCode = code;
            info.territoryName = territory;
            info.cityName = cityName;

            // Set the string list, we will use for the applet to display the available cities.
            m_places[tmp] = info;
            success = true;
        }
    }

    return (success && !m_xmlSetup.error());
}

Alexander Lohnau's avatar
Alexander Lohnau committed
653
void EnvCanadaIon::parseWeatherSite(WeatherData &data, QXmlStreamReader &xml)
654
655
656
657
{
    while (!xml.atEnd()) {
        xml.readNext();

658
659
        const QStringRef elementName = xml.name();

660
        if (xml.isStartElement()) {
661
            if (elementName == QLatin1String("license")) {
662
                data.creditUrl = xml.readElementText();
663
            } else if (elementName == QLatin1String("location")) {
664
                parseLocations(data, xml);
665
            } else if (elementName == QLatin1String("warnings")) {
666
667
668
669
                // Cleanup warning list on update
                data.warnings.clear();
                data.watches.clear();
                parseWarnings(data, xml);
670
            } else if (elementName == QLatin1String("currentConditions")) {
671
                parseConditions(data, xml);
672
            } else if (elementName == QLatin1String("forecastGroup")) {
673
674
675
                // Clean up forecast list on update
                data.forecasts.clear();
                parseWeatherForecast(data, xml);
676
            } else if (elementName == QLatin1String("yesterdayConditions")) {
677
                parseYesterdayWeather(data, xml);
678
            } else if (elementName == QLatin1String("riseSet")) {
679
                parseAstronomicals(data, xml);
680
            } else if (elementName == QLatin1String("almanac")) {
681
682
683
684
685
686
687
688
                parseWeatherRecords(data, xml);
            } else {
                parseUnknownElement(xml);
            }
        }
    }
}

689
// Parse Weather data main loop, from here we have to descend into each tag pair
Alexander Lohnau's avatar
Alexander Lohnau committed
690
bool EnvCanadaIon::readXMLData(const QString &source, QXmlStreamReader &xml)
691
692
693
{
    WeatherData data;

Alexander Lohnau's avatar
Alexander Lohnau committed
694
    // qCDebug(IONENGINE_ENVCAN) << "readXMLData()";
695
696

    QString dataKey = source;
697
    dataKey.remove(QStringLiteral("envcan|weather|"));
698
699
700
701
702
703
704
705
706
    data.shortTerritoryName = m_places[dataKey].territoryName;
    while (!xml.atEnd()) {
        xml.readNext();

        if (xml.isEndElement()) {
            break;
        }

        if (xml.isStartElement()) {
707
            if (xml.name() == QLatin1String("siteData")) {
708
709
710
711
712
713
714
                parseWeatherSite(data, xml);
            } else {
                parseUnknownElement(xml);
            }
        }
    }

715
    bool solarDataSourceNeedsConnect = false;
Alexander Lohnau's avatar
Alexander Lohnau committed
716
    Plasma::DataEngine *timeEngine = dataEngine(QStringLiteral("time"));
717
    if (timeEngine) {
Alexander Lohnau's avatar
Alexander Lohnau committed
718
        const bool canCalculateElevation = (data.observationDateTime.isValid() && (!qIsNaN(data.stationLatitude) && !qIsNaN(data.stationLongitude)));
719
        if (canCalculateElevation) {
720
            data.solarDataTimeEngineSourceName = QStringLiteral("%1|Solar|Latitude=%2|Longitude=%3|DateTime=%4")
Alexander Lohnau's avatar
Alexander Lohnau committed
721
722
723
724
                                                     .arg(QString::fromUtf8(data.observationDateTime.timeZone().id()))
                                                     .arg(data.stationLatitude)
                                                     .arg(data.stationLongitude)
                                                     .arg(data.observationDateTime.toString(Qt::ISODate));
725
726
727
728
729
730
            solarDataSourceNeedsConnect = true;
        }

        // check any previous data
        const auto it = m_weatherData.constFind(source);
        if (it != m_weatherData.constEnd()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
731
            const QString &oldSolarDataTimeEngineSource = it.value().solarDataTimeEngineSourceName;
732
733
734
735
736
737
738
739
740
741
742
743

            if (oldSolarDataTimeEngineSource == data.solarDataTimeEngineSourceName) {
                // can reuse elevation source (if any), copy over data
                data.isNight = it.value().isNight;
                solarDataSourceNeedsConnect = false;
            } else if (!oldSolarDataTimeEngineSource.isEmpty()) {
                // drop old elevation source
                timeEngine->disconnectSource(oldSolarDataTimeEngineSource, this);
            }
        }
    }

744
    m_weatherData[source] = data;
745
746
747
748
749
750
751
752

    // connect only after m_weatherData has the data, so the instant data push handling can see it
    if (solarDataSourceNeedsConnect) {
        timeEngine->connectSource(data.solarDataTimeEngineSourceName, this);
    } else {
        updateWeather(source);
    }

753
754
755
    return !xml.error();
}

Alexander Lohnau's avatar
Alexander Lohnau committed
756
void EnvCanadaIon::parseFloat(float &value, QXmlStreamReader &xml)
757
758
759
760
761
762
763
764
{
    bool ok = false;
    const float result = xml.readElementText().toFloat(&ok);
    if (ok) {
        value = result;
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
765
void EnvCanadaIon::parseDateTime(WeatherData &data, QXmlStreamReader &xml, WeatherData::WeatherEvent *event)
766
{
767
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("dateTime"));
768
769

    // What kind of date info is this?
770
771
    const QString dateType = xml.attributes().value(QStringLiteral("name")).toString();
    const QString dateZone = xml.attributes().value(QStringLiteral("zone")).toString();
772
    const QString dateUtcOffset = xml.attributes().value(QStringLiteral("UTCOffset")).toString();
773
774
775
776
777
778
779
780
781
782

    QString selectTimeStamp;

    while (!xml.atEnd()) {
        xml.readNext();

        if (xml.isEndElement()) {
            break;
        }

783
784
        const QStringRef elementName = xml.name();

785
        if (xml.isStartElement()) {
786
            if (dateType == QLatin1String("xmlCreation")) {
787
788
                return;
            }
789
            if (dateZone == QLatin1String("UTC")) {
790
791
                return;
            }
792
            if (elementName == QLatin1String("year")) {
793
                xml.readElementText();
794
            } else if (elementName == QLatin1String("month")) {
795
                xml.readElementText();
796
            } else if (elementName == QLatin1String("day")) {
797
                xml.readElementText();
798
            } else if (elementName == QLatin1String("hour"))
799
                xml.readElementText();
800
            else if (elementName == QLatin1String("minute"))
801
                xml.readElementText();
802
            else if (elementName == QLatin1String("timeStamp"))
803
                selectTimeStamp = xml.readElementText();
804
            else if (elementName == QLatin1String("textSummary")) {
805
                if (dateType == QLatin1String("eventIssue")) {
806
807
808
                    if (event) {
                        event->timestamp = xml.readElementText();
                    }
809
                } else if (dateType == QLatin1String("observation")) {
810
                    xml.readElementText();
811
                    QDateTime observationDateTime = QDateTime::fromString(selectTimeStamp, QStringLiteral("yyyyMMddHHmmss"));
812
813
814
815
816
                    QTimeZone timeZone = QTimeZone(dateZone.toUtf8());
                    // if timezone id not recognized, fallback to utcoffset
                    if (!timeZone.isValid()) {
                        timeZone = QTimeZone(dateUtcOffset.toInt() * 3600);
                    }
817
818
819
                    if (observationDateTime.isValid() && timeZone.isValid()) {
                        data.observationDateTime = observationDateTime;
                        data.observationDateTime.setTimeZone(timeZone);
820
                    }
821
                    data.obsTimestamp = observationDateTime.toString(QStringLiteral("dd.MM.yyyy @ hh:mm"));
822
                } else if (dateType == QLatin1String("forecastIssue")) {
823
                    data.forecastTimestamp = xml.readElementText();
824
                } else if (dateType == QLatin1String("sunrise")) {
825
                    data.sunriseTimestamp = xml.readElementText();
826
                } else if (dateType == QLatin1String("sunset")) {
827
                    data.sunsetTimestamp = xml.readElementText();
828
                } else if (dateType == QLatin1String("moonrise")) {
829
                    data.moonriseTimestamp = xml.readElementText();
830
                } else if (dateType == QLatin1String("moonset")) {
831
832
833
834
835
836
837
                    data.moonsetTimestamp = xml.readElementText();
                }
            }
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
838
void EnvCanadaIon::parseLocations(WeatherData &data, QXmlStreamReader &xml)
839
{
840
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("location"));
841
842
843
844
845
846
847
848

    while (!xml.atEnd()) {
        xml.readNext();

        if (xml.isEndElement()) {
            break;
        }

849
850
        const QStringRef elementName = xml.name();

851
        if (xml.isStartElement()) {
852
            if (elementName == QLatin1String("country")) {
853
                data.countryName = xml.readElementText();
854
            } else if (elementName == QLatin1String("province") || elementName == QLatin1String("territory")) {
855
                data.longTerritoryName = xml.readElementText();
856
            } else if (elementName == QLatin1String("name")) {
857
                data.cityName = xml.readElementText();
858
            } else if (elementName == QLatin1String("region")) {
859
860
861
862
863
864
865
866
                data.regionName = xml.readElementText();
            } else {
                parseUnknownElement(xml);
            }
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
867
void EnvCanadaIon::parseWindInfo(WeatherData &data, QXmlStreamReader &xml)
868
{
869
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("wind"));
870
871
872
873
874
875
876
877

    while (!xml.atEnd()) {
        xml.readNext();

        if (xml.isEndElement()) {
            break;
        }

878
879
        const QStringRef elementName = xml.name();

880
        if (xml.isStartElement()) {
881
            if (elementName == QLatin1String("speed")) {
882
                parseFloat(data.windSpeed, xml);
883
            } else if (elementName == QLatin1String("gust")) {
884
                parseFloat(data.windGust, xml);
885
            } else if (elementName == QLatin1String("direction")) {
886
                data.windDirection = xml.readElementText();
887
            } else if (elementName == QLatin1String("bearing")) {
888
                data.windDegrees = xml.attributes().value(QStringLiteral("degrees")).toString();
889
890
891
892
893
894
895
            } else {
                parseUnknownElement(xml);
            }
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
896
void EnvCanadaIon::parseConditions(WeatherData &data, QXmlStreamReader &xml)
897
{
898
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("currentConditions"));
899
900
    data.temperature = qQNaN();
    data.dewpoint = qQNaN();
901
    data.condition = i18n("N/A");
902
    data.humidex.clear();
903
    data.stationID = i18n("N/A");
904
905
    data.stationLatitude = qQNaN();
    data.stationLongitude = qQNaN();
906
907
908
    data.pressure = qQNaN();
    data.visibility = qQNaN();
    data.humidity = qQNaN();
909
910
911
912

    while (!xml.atEnd()) {
        xml.readNext();

913
914
915
        const QStringRef elementName = xml.name();

        if (xml.isEndElement() && elementName == QLatin1String("currentConditions"))
916
917
918
            break;

        if (xml.isStartElement()) {
919
            if (elementName == QLatin1String("station")) {
920
                data.stationID = xml.attributes().value(QStringLiteral("code")).toString();
921
                QRegularExpression dumpDirection(QStringLiteral("[^0-9.]"));
922
923
                data.stationLatitude = xml.attributes().value(QStringLiteral("lat")).toString().remove(dumpDirection).toDouble();
                data.stationLongitude = xml.attributes().value(QStringLiteral("lon")).toString().remove(dumpDirection).toDouble();
924
            } else if (elementName == QLatin1String("dateTime")) {
925
                parseDateTime(data, xml);
926
            } else if (elementName == QLatin1String("condition")) {
927
                data.condition = xml.readElementText().trimmed();
928
            } else if (elementName == QLatin1String("temperature")) {
929
930
                // prevent N/A text to result in 0.0 value
                parseFloat(data.temperature, xml);
931
            } else if (elementName == QLatin1String("dewpoint")) {
932
933
934
935
936
937
938
                // prevent N/A text to result in 0.0 value
                parseFloat(data.dewpoint, xml);
            } else if (elementName == QLatin1String("humidex")) {
                data.humidex = xml.readElementText();
            } else if (elementName == QLatin1String("windChill")) {
                // prevent N/A text to result in 0.0 value
                parseFloat(data.windchill, xml);
939
            } else if (elementName == QLatin1String("pressure")) {
940
                data.pressureTendency = xml.attributes().value(QStringLiteral("tendency")).toString();
941
                if (data.pressureTendency.isEmpty()) {
942
                    data.pressureTendency = QStringLiteral("steady");
943
                }
944
                parseFloat(data.pressure, xml);
945
            } else if (elementName == QLatin1String("visibility")) {
946
                parseFloat(data.visibility, xml);
947
            } else if (elementName == QLatin1String("relativeHumidity")) {
948
                parseFloat(data.humidity, xml);
949
            } else if (elementName == QLatin1String("wind")) {
950
951
952
953
954
955
956
957
958
                parseWindInfo(data, xml);
            }
            //} else {
            //    parseUnknownElement(xml);
            //}
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
959
void EnvCanadaIon::parseWarnings(WeatherData &data, QXmlStreamReader &xml)
960
961
962
963
{
    WeatherData::WeatherEvent *watch = new WeatherData::WeatherEvent;
    WeatherData::WeatherEvent *warning = new WeatherData::WeatherEvent;

964
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("warnings"));
965
    QString eventURL = xml.attributes().value(QStringLiteral("url")).toString();
966
967
968
969
970
    int flag = 0;

    while (!xml.atEnd()) {
        xml.readNext();

971
972
973
        const QStringRef elementName = xml.name();

        if (xml.isEndElement() && elementName == QLatin1String("warnings")) {
974
975
976
977
            break;
        }

        if (xml.isStartElement()) {
978
            if (elementName == QLatin1String("dateTime")) {
979
980
981
982
983
984
985
                if (flag == 1) {
                    parseDateTime(data, xml, watch);
                }
                if (flag == 2) {
                    parseDateTime(data, xml, warning);
                }

Alexander Lohnau's avatar
Alexander Lohnau committed
986
                if (!warning->timestamp.isEmpty() && !warning->url.isEmpty()) {
987
988
989
990
991
992
993
994
                    data.warnings.append(warning);
                    warning = new WeatherData::WeatherEvent;
                }
                if (!watch->timestamp.isEmpty() && !watch->url.isEmpty()) {
                    data.watches.append(watch);
                    watch = new WeatherData::WeatherEvent;
                }

995
            } else if (elementName == QLatin1String("event")) {
996
                // Append new event to list.
997
998
                QString eventType = xml.attributes().value(QStringLiteral("type")).toString();
                if (eventType == QLatin1String("watch")) {
999
1000
                    watch->url = eventURL;
                    watch->type = eventType;
1001
1002
                    watch->priority = xml.attributes().value(QStringLiteral("priority")).toString();
                    watch->description = xml.attributes().value(QStringLiteral("description")).toString();
1003
1004
1005
                    flag = 1;
                }

1006
                if (eventType == QLatin1String("warning")) {
1007
1008
                    warning->url = eventURL;
                    warning->type = eventType;
1009
1010
                    warning->priority = xml.attributes().value(QStringLiteral("priority")).toString();
                    warning->description = xml.attributes().value(QStringLiteral("description")).toString();
1011
1012
1013
                    flag = 2;
                }
            } else {
1014
                if (xml.name() != QLatin1String("dateTime")) {
1015
1016
1017
1018
1019
1020
1021
1022
1023
                    parseUnknownElement(xml);
                }
            }
        }
    }
    delete watch;
    delete warning;
}

Alexander Lohnau's avatar
Alexander Lohnau committed
1024
void EnvCanadaIon::parseWeatherForecast(WeatherData &data, QXmlStreamReader &xml)
1025
{
Alexander Lohnau's avatar
Alexander Lohnau committed
1026
    WeatherData::ForecastInfo *forecast = new WeatherData::ForecastInfo;
1027
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("forecastGroup"));
1028
1029
1030
1031

    while (!xml.atEnd()) {
        xml.readNext();

1032
1033
1034
        const QStringRef elementName = xml.name();

        if (xml.isEndElement() && elementName == QLatin1String("forecastGroup")) {
1035
1036
1037
1038
            break;
        }

        if (xml.isStartElement()) {
1039
            if (elementName == QLatin1String("dateTime")) {
1040
                parseDateTime(data, xml);
1041
            } else if (elementName == QLatin1String("regionalNormals")) {
1042
                parseRegionalNormals(data, xml);
1043
            } else if (elementName == QLatin1String("forecast")) {
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
                parseForecast(data, xml, forecast);
                forecast = new WeatherData::ForecastInfo;
            } else {
                parseUnknownElement(xml);
            }
        }
    }
    delete forecast;
}

Alexander Lohnau's avatar
Alexander Lohnau committed
1054
void EnvCanadaIon::parseRegionalNormals(WeatherData &data, QXmlStreamReader &xml)
1055
{
1056
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("regionalNormals"));
1057
1058
1059
1060
1061
1062
1063
1064

    while (!xml.atEnd()) {
        xml.readNext();

        if (xml.isEndElement()) {
            break;
        }

1065
1066
        const QStringRef elementName = xml.name();

1067
        if (xml.isStartElement()) {
1068
            if (elementName == QLatin1String("textSummary")) {
1069
                xml.readElementText();
1070
            } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("high")) {
1071
1072
                // prevent N/A text to result in 0.0 value
                parseFloat(data.normalHigh, xml);
1073
            } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("low")) {
1074
1075
                // prevent N/A text to result in 0.0 value
                parseFloat(data.normalLow, xml);
1076
1077
1078
1079
1080
            }
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
1081
void EnvCanadaIon::parseForecast(WeatherData &data, QXmlStreamReader &xml, WeatherData::ForecastInfo *forecast)
1082
{
1083
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("forecast"));
1084
1085
1086
1087

    while (!xml.atEnd()) {
        xml.readNext();

1088
1089
1090
        const QStringRef elementName = xml.name();

        if (xml.isEndElement() && elementName == QLatin1String("forecast")) {
1091
1092
1093
1094
1095
            data.forecasts.append(forecast);
            break;
        }

        if (xml.isStartElement()) {
1096
            if (elementName == QLatin1String("period")) {
1097
                forecast->forecastPeriod = xml.attributes().value(QStringLiteral("textForecastName")).toString();
1098
            } else if (elementName == QLatin1String("textSummary")) {
1099
                forecast->forecastSummary = xml.readElementText();
1100
            } else if (elementName == QLatin1String("abbreviatedForecast")) {
1101
                parseShortForecast(forecast, xml);
1102
            } else if (elementName == QLatin1String("temperatures")) {
1103
                parseForecastTemperatures(forecast, xml);
1104
            } else if (elementName == QLatin1String("winds")) {
1105
                parseWindForecast(forecast, xml);
1106
            } else if (elementName == QLatin1String("precipitation")) {
1107
                parsePrecipitationForecast(forecast, xml);
1108
            } else if (elementName == QLatin1String("uv")) {
1109
                data.UVRating = xml.attributes().value(QStringLiteral("category")).toString();
1110
                parseUVIndex(data, xml);
1111
                // else if (elementName == QLatin1String("frost")) { FIXME: Wait until winter to see what this looks like.
1112
1113
                //  parseFrost(xml, forecast);
            } else {
1114
                if (elementName != QLatin1String("forecast")) {
1115
1116
1117
1118
1119
1120
1121
                    parseUnknownElement(xml);
                }
            }
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
1122
void EnvCanadaIon::parseShortForecast(WeatherData::ForecastInfo *forecast, QXmlStreamReader &xml)
1123
{
1124
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("abbreviatedForecast"));
1125
1126
1127
1128
1129
1130

    QString shortText;

    while (!xml.atEnd()) {
        xml.readNext();

1131
1132
1133
        const QStringRef elementName = xml.name();

        if (xml.isEndElement() && elementName == QLatin1String("abbreviatedForecast")) {
1134
1135
1136
1137
            break;
        }

        if (xml.isStartElement()) {
1138
            if (elementName == QLatin1String("pop")) {
1139
                parseFloat(forecast->popPrecent, xml);
1140
            }
1141
            if (elementName == QLatin1String("textSummary")) {
1142
                shortText = xml.readElementText();
1143
                QMap<QString, ConditionIcons> forecastList = forecastIcons();
Alexander Lohnau's avatar
Alexander Lohnau committed
1144
                if ((forecast->forecastPeriod == QLatin1String("tonight")) || (forecast->forecastPeriod.contains(QLatin1String("night")))) {
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
                    forecastList.insert(QStringLiteral("a few clouds"), FewCloudsNight);
                    forecastList.insert(QStringLiteral("cloudy periods"), PartlyCloudyNight);
                    forecastList.insert(QStringLiteral("chance of drizzle mixed with rain"), ChanceShowersNight);
                    forecastList.insert(QStringLiteral("chance of drizzle"), ChanceShowersNight);
                    forecastList.insert(QStringLiteral("chance of drizzle or rain"), ChanceShowersNight);
                    forecastList.insert(QStringLiteral("chance of flurries"), ChanceSnowNight);
                    forecastList.insert(QStringLiteral("chance of light snow"), ChanceSnowNight);
                    forecastList.insert(QStringLiteral("chance of flurries at times heavy"), ChanceSnowNight);
                    forecastList.insert(QStringLiteral("chance of showers or drizzle"), ChanceShowersNight);
                    forecastList.insert(QStringLiteral("chance of showers"), ChanceShowersNight);
                    forecastList.insert(QStringLiteral("clearing"), ClearNight);
1156
                } else {
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
                    forecastList.insert(QStringLiteral("a few clouds"), FewCloudsDay);
                    forecastList.insert(QStringLiteral("cloudy periods"), PartlyCloudyDay);
                    forecastList.insert(QStringLiteral("chance of drizzle mixed with rain"), ChanceShowersDay);
                    forecastList.insert(QStringLiteral("chance of drizzle"), ChanceShowersDay);
                    forecastList.insert(QStringLiteral("chance of drizzle or rain"), ChanceShowersDay);
                    forecastList.insert(QStringLiteral("chance of flurries"), ChanceSnowDay);
                    forecastList.insert(QStringLiteral("chance of light snow"), ChanceSnowDay);
                    forecastList.insert(QStringLiteral("chance of flurries at times heavy"), ChanceSnowDay);
                    forecastList.insert(QStringLiteral("chance of showers or drizzle"), ChanceShowersDay);
                    forecastList.insert(QStringLiteral("chance of showers"), ChanceShowersDay);
                    forecastList.insert(QStringLiteral("clearing"), ClearDay);
1168
1169
1170
1171
1172
1173
1174
1175
                }
                forecast->shortForecast = shortText;
                forecast->iconName = getWeatherIcon(forecastList, shortText.toLower());
            }
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
1176
void EnvCanadaIon::parseUVIndex(WeatherData &data, QXmlStreamReader &xml)
1177
{
1178
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("uv"));
1179
1180
1181
1182

    while (!xml.atEnd()) {
        xml.readNext();

1183
1184
1185
        const QStringRef elementName = xml.name();

        if (xml.isEndElement() && elementName == QLatin1String("uv")) {
1186
1187
1188
1189
            break;
        }

        if (xml.isStartElement()) {
1190
            if (elementName == QLatin1String("index")) {
1191
1192
                data.UVIndex = xml.readElementText();
            }
1193
            if (elementName == QLatin1String("textSummary")) {
1194
1195
1196
1197
1198
1199
                xml.readElementText();
            }
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
1200
void EnvCanadaIon::parseForecastTemperatures(WeatherData::ForecastInfo *forecast, QXmlStreamReader &xml)
1201
{
1202
    Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("temperatures"));
1203
1204
1205
1206

    while (!xml.atEnd()) {
        xml.readNext();

1207
1208
1209
        const QStringRef elementName = xml.name();

        if (xml.isEndElement() && elementName == QLatin1String("temperatures")) {
1210
1211
1212
1213
            break;
        }

        if (xml.isStartElement()) {
1214
            if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("low")) {
1215
                parseFloat(forecast->tempLow, xml);
1216
            } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("high")) {
1217
                parseFloat(forecast->tempHigh, xml);
1218
            } else if (elementName == QLatin1String("textSummary")) {
1219
1220
1221
1222
1223
1224
                xml.readElementText();
            }
        }
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
1225
<