File indexing completed on 2025-01-12 05:01:50

0001 /*
0002     SPDX-FileCopyrightText: 2007-2011, 2019 Shawn Starr <shawn.starr@rogers.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 /* Ion for Environment Canada XML data */
0008 
0009 #include "ion_envcan.h"
0010 
0011 // #include "ion_envcandebug.h"
0012 
0013 #include <KIO/TransferJob>
0014 #include <KLocalizedString>
0015 #include <KUnitConversion/Converter>
0016 
0017 #include <QRegularExpression>
0018 #include <QTimeZone>
0019 
0020 WeatherData::WeatherData()
0021     : stationLatitude(qQNaN())
0022     , stationLongitude(qQNaN())
0023     , temperature(qQNaN())
0024     , dewpoint(qQNaN())
0025     , windchill(qQNaN())
0026     , pressure(qQNaN())
0027     , visibility(qQNaN())
0028     , humidity(qQNaN())
0029     , windSpeed(qQNaN())
0030     , windGust(qQNaN())
0031     , normalHigh(qQNaN())
0032     , normalLow(qQNaN())
0033     , prevHigh(qQNaN())
0034     , prevLow(qQNaN())
0035     , recordHigh(qQNaN())
0036     , recordLow(qQNaN())
0037     , recordRain(qQNaN())
0038     , recordSnow(qQNaN())
0039 {
0040 }
0041 
0042 WeatherData::ForecastInfo::ForecastInfo()
0043     : tempHigh(qQNaN())
0044     , tempLow(qQNaN())
0045     , popPrecent(qQNaN())
0046 {
0047 }
0048 
0049 // ctor, dtor
0050 EnvCanadaIon::EnvCanadaIon(QObject *parent)
0051     : IonInterface(parent)
0052 {
0053     // Get the real city XML URL so we can parse this
0054     getXMLSetup();
0055 }
0056 
0057 void EnvCanadaIon::deleteForecasts()
0058 {
0059     QMutableHashIterator<QString, WeatherData> it(m_weatherData);
0060     while (it.hasNext()) {
0061         it.next();
0062         WeatherData &item = it.value();
0063         qDeleteAll(item.warnings);
0064         item.warnings.clear();
0065 
0066         qDeleteAll(item.forecasts);
0067         item.forecasts.clear();
0068     }
0069 }
0070 
0071 void EnvCanadaIon::reset()
0072 {
0073     deleteForecasts();
0074     emitWhenSetup = true;
0075     m_sourcesToReset = sources();
0076     getXMLSetup();
0077 }
0078 
0079 EnvCanadaIon::~EnvCanadaIon()
0080 {
0081     // Destroy each warning stored in a QList
0082     deleteForecasts();
0083 }
0084 
0085 QMap<QString, IonInterface::ConditionIcons> EnvCanadaIon::setupConditionIconMappings() const
0086 {
0087     return QMap<QString, ConditionIcons>{
0088 
0089         // Explicit periods
0090         {QStringLiteral("mainly sunny"), FewCloudsDay},
0091         {QStringLiteral("mainly clear"), FewCloudsNight},
0092         {QStringLiteral("sunny"), ClearDay},
0093         {QStringLiteral("clear"), ClearNight},
0094 
0095         // Available conditions
0096         {QStringLiteral("blowing snow"), Snow},
0097         {QStringLiteral("cloudy"), Overcast},
0098         {QStringLiteral("distant precipitation"), LightRain},
0099         {QStringLiteral("drifting snow"), Flurries},
0100         {QStringLiteral("drizzle"), LightRain},
0101         {QStringLiteral("dust"), NotAvailable},
0102         {QStringLiteral("dust devils"), NotAvailable},
0103         {QStringLiteral("fog"), Mist},
0104         {QStringLiteral("fog bank near station"), Mist},
0105         {QStringLiteral("fog depositing ice"), Mist},
0106         {QStringLiteral("fog patches"), Mist},
0107         {QStringLiteral("freezing drizzle"), FreezingDrizzle},
0108         {QStringLiteral("freezing rain"), FreezingRain},
0109         {QStringLiteral("funnel cloud"), NotAvailable},
0110         {QStringLiteral("hail"), Hail},
0111         {QStringLiteral("haze"), Haze},
0112         {QStringLiteral("heavy blowing snow"), Snow},
0113         {QStringLiteral("heavy drifting snow"), Snow},
0114         {QStringLiteral("heavy drizzle"), LightRain},
0115         {QStringLiteral("heavy hail"), Hail},
0116         {QStringLiteral("heavy mixed rain and drizzle"), LightRain},
0117         {QStringLiteral("heavy mixed rain and snow shower"), RainSnow},
0118         {QStringLiteral("heavy rain"), Rain},
0119         {QStringLiteral("heavy rain and snow"), RainSnow},
0120         {QStringLiteral("heavy rainshower"), Rain},
0121         {QStringLiteral("heavy snow"), Snow},
0122         {QStringLiteral("heavy snow pellets"), Snow},
0123         {QStringLiteral("heavy snowshower"), Snow},
0124         {QStringLiteral("heavy thunderstorm with hail"), Thunderstorm},
0125         {QStringLiteral("heavy thunderstorm with rain"), Thunderstorm},
0126         {QStringLiteral("ice crystals"), Flurries},
0127         {QStringLiteral("ice pellets"), Hail},
0128         {QStringLiteral("increasing cloud"), Overcast},
0129         {QStringLiteral("light drizzle"), LightRain},
0130         {QStringLiteral("light freezing drizzle"), FreezingRain},
0131         {QStringLiteral("light freezing rain"), FreezingRain},
0132         {QStringLiteral("light rain"), LightRain},
0133         {QStringLiteral("light rainshower"), LightRain},
0134         {QStringLiteral("light snow"), LightSnow},
0135         {QStringLiteral("light snow pellets"), LightSnow},
0136         {QStringLiteral("light snowshower"), Flurries},
0137         {QStringLiteral("lightning visible"), Thunderstorm},
0138         {QStringLiteral("mist"), Mist},
0139         {QStringLiteral("mixed rain and drizzle"), LightRain},
0140         {QStringLiteral("mixed rain and snow shower"), RainSnow},
0141         {QStringLiteral("not reported"), NotAvailable},
0142         {QStringLiteral("rain"), Rain},
0143         {QStringLiteral("rain and snow"), RainSnow},
0144         {QStringLiteral("rainshower"), LightRain},
0145         {QStringLiteral("recent drizzle"), LightRain},
0146         {QStringLiteral("recent dust or sand storm"), NotAvailable},
0147         {QStringLiteral("recent fog"), Mist},
0148         {QStringLiteral("recent freezing precipitation"), FreezingDrizzle},
0149         {QStringLiteral("recent hail"), Hail},
0150         {QStringLiteral("recent rain"), Rain},
0151         {QStringLiteral("recent rain and snow"), RainSnow},
0152         {QStringLiteral("recent rainshower"), Rain},
0153         {QStringLiteral("recent snow"), Snow},
0154         {QStringLiteral("recent snowshower"), Flurries},
0155         {QStringLiteral("recent thunderstorm"), Thunderstorm},
0156         {QStringLiteral("recent thunderstorm with hail"), Thunderstorm},
0157         {QStringLiteral("recent thunderstorm with heavy hail"), Thunderstorm},
0158         {QStringLiteral("recent thunderstorm with heavy rain"), Thunderstorm},
0159         {QStringLiteral("recent thunderstorm with rain"), Thunderstorm},
0160         {QStringLiteral("sand or dust storm"), NotAvailable},
0161         {QStringLiteral("severe sand or dust storm"), NotAvailable},
0162         {QStringLiteral("shallow fog"), Mist},
0163         {QStringLiteral("smoke"), NotAvailable},
0164         {QStringLiteral("snow"), Snow},
0165         {QStringLiteral("snow crystals"), Flurries},
0166         {QStringLiteral("snow grains"), Flurries},
0167         {QStringLiteral("squalls"), Snow},
0168         {QStringLiteral("thunderstorm"), Thunderstorm},
0169         {QStringLiteral("thunderstorm with hail"), Thunderstorm},
0170         {QStringLiteral("thunderstorm with rain"), Thunderstorm},
0171         {QStringLiteral("thunderstorm with light rainshowers"), Thunderstorm},
0172         {QStringLiteral("thunderstorm with heavy rainshowers"), Thunderstorm},
0173         {QStringLiteral("thunderstorm with sand or dust storm"), Thunderstorm},
0174         {QStringLiteral("thunderstorm without precipitation"), Thunderstorm},
0175         {QStringLiteral("tornado"), NotAvailable},
0176     };
0177 }
0178 
0179 QMap<QString, IonInterface::ConditionIcons> EnvCanadaIon::setupForecastIconMappings() const
0180 {
0181     return QMap<QString, ConditionIcons>{
0182 
0183         // Abbreviated forecast descriptions
0184         {QStringLiteral("a few flurries"), Flurries},
0185         {QStringLiteral("a few flurries mixed with ice pellets"), RainSnow},
0186         {QStringLiteral("a few flurries or rain showers"), RainSnow},
0187         {QStringLiteral("a few flurries or thundershowers"), RainSnow},
0188         {QStringLiteral("a few rain showers or flurries"), RainSnow},
0189         {QStringLiteral("a few rain showers or wet flurries"), RainSnow},
0190         {QStringLiteral("a few showers"), LightRain},
0191         {QStringLiteral("a few showers or drizzle"), LightRain},
0192         {QStringLiteral("a few showers or thundershowers"), Thunderstorm},
0193         {QStringLiteral("a few showers or thunderstorms"), Thunderstorm},
0194         {QStringLiteral("a few thundershowers"), Thunderstorm},
0195         {QStringLiteral("a few thunderstorms"), Thunderstorm},
0196         {QStringLiteral("a few wet flurries"), RainSnow},
0197         {QStringLiteral("a few wet flurries or rain showers"), RainSnow},
0198         {QStringLiteral("a mix of sun and cloud"), PartlyCloudyDay},
0199         {QStringLiteral("cloudy with sunny periods"), PartlyCloudyDay},
0200         {QStringLiteral("partly cloudy"), PartlyCloudyDay},
0201         {QStringLiteral("mainly cloudy"), PartlyCloudyDay},
0202         {QStringLiteral("mainly sunny"), FewCloudsDay},
0203         {QStringLiteral("sunny"), ClearDay},
0204         {QStringLiteral("blizzard"), Snow},
0205         {QStringLiteral("clear"), ClearNight},
0206         {QStringLiteral("cloudy"), Overcast},
0207         {QStringLiteral("drizzle"), LightRain},
0208         {QStringLiteral("drizzle mixed with freezing drizzle"), FreezingDrizzle},
0209         {QStringLiteral("drizzle mixed with rain"), LightRain},
0210         {QStringLiteral("drizzle or freezing drizzle"), LightRain},
0211         {QStringLiteral("drizzle or rain"), LightRain},
0212         {QStringLiteral("flurries"), Flurries},
0213         {QStringLiteral("flurries at times heavy"), Flurries},
0214         {QStringLiteral("flurries at times heavy or rain snowers"), RainSnow},
0215         {QStringLiteral("flurries mixed with ice pellets"), FreezingRain},
0216         {QStringLiteral("flurries or ice pellets"), FreezingRain},
0217         {QStringLiteral("flurries or rain showers"), RainSnow},
0218         {QStringLiteral("flurries or thundershowers"), Flurries},
0219         {QStringLiteral("fog"), Mist},
0220         {QStringLiteral("fog developing"), Mist},
0221         {QStringLiteral("fog dissipating"), Mist},
0222         {QStringLiteral("fog patches"), Mist},
0223         {QStringLiteral("freezing drizzle"), FreezingDrizzle},
0224         {QStringLiteral("freezing rain"), FreezingRain},
0225         {QStringLiteral("freezing rain mixed with rain"), FreezingRain},
0226         {QStringLiteral("freezing rain mixed with snow"), FreezingRain},
0227         {QStringLiteral("freezing rain or ice pellets"), FreezingRain},
0228         {QStringLiteral("freezing rain or rain"), FreezingRain},
0229         {QStringLiteral("freezing rain or snow"), FreezingRain},
0230         {QStringLiteral("ice fog"), Mist},
0231         {QStringLiteral("ice fog developing"), Mist},
0232         {QStringLiteral("ice fog dissipating"), Mist},
0233         {QStringLiteral("ice pellets"), Hail},
0234         {QStringLiteral("ice pellets mixed with freezing rain"), Hail},
0235         {QStringLiteral("ice pellets mixed with snow"), Hail},
0236         {QStringLiteral("ice pellets or snow"), RainSnow},
0237         {QStringLiteral("light snow"), LightSnow},
0238         {QStringLiteral("light snow and blizzard"), LightSnow},
0239         {QStringLiteral("light snow and blizzard and blowing snow"), Snow},
0240         {QStringLiteral("light snow and blowing snow"), LightSnow},
0241         {QStringLiteral("light snow mixed with freezing drizzle"), FreezingDrizzle},
0242         {QStringLiteral("light snow mixed with freezing rain"), FreezingRain},
0243         {QStringLiteral("light snow or ice pellets"), LightSnow},
0244         {QStringLiteral("light snow or rain"), RainSnow},
0245         {QStringLiteral("light wet snow"), RainSnow},
0246         {QStringLiteral("light wet snow or rain"), RainSnow},
0247         {QStringLiteral("local snow squalls"), Snow},
0248         {QStringLiteral("near blizzard"), Snow},
0249         {QStringLiteral("overcast"), Overcast},
0250         {QStringLiteral("increasing cloudiness"), Overcast},
0251         {QStringLiteral("increasing clouds"), Overcast},
0252         {QStringLiteral("periods of drizzle"), LightRain},
0253         {QStringLiteral("periods of drizzle mixed with freezing drizzle"), FreezingDrizzle},
0254         {QStringLiteral("periods of drizzle mixed with rain"), LightRain},
0255         {QStringLiteral("periods of drizzle or freezing drizzle"), FreezingDrizzle},
0256         {QStringLiteral("periods of drizzle or rain"), LightRain},
0257         {QStringLiteral("periods of freezing drizzle"), FreezingDrizzle},
0258         {QStringLiteral("periods of freezing drizzle or drizzle"), FreezingDrizzle},
0259         {QStringLiteral("periods of freezing drizzle or rain"), FreezingDrizzle},
0260         {QStringLiteral("periods of freezing rain"), FreezingRain},
0261         {QStringLiteral("periods of freezing rain mixed with ice pellets"), FreezingRain},
0262         {QStringLiteral("periods of freezing rain mixed with rain"), FreezingRain},
0263         {QStringLiteral("periods of freezing rain mixed with snow"), FreezingRain},
0264         {QStringLiteral("periods of freezing rain mixed with freezing drizzle"), FreezingRain},
0265         {QStringLiteral("periods of freezing rain or ice pellets"), FreezingRain},
0266         {QStringLiteral("periods of freezing rain or rain"), FreezingRain},
0267         {QStringLiteral("periods of freezing rain or snow"), FreezingRain},
0268         {QStringLiteral("periods of ice pellets"), Hail},
0269         {QStringLiteral("periods of ice pellets mixed with freezing rain"), Hail},
0270         {QStringLiteral("periods of ice pellets mixed with snow"), Hail},
0271         {QStringLiteral("periods of ice pellets or freezing rain"), Hail},
0272         {QStringLiteral("periods of ice pellets or snow"), Hail},
0273         {QStringLiteral("periods of light snow"), LightSnow},
0274         {QStringLiteral("periods of light snow and blizzard"), Snow},
0275         {QStringLiteral("periods of light snow and blizzard and blowing snow"), Snow},
0276         {QStringLiteral("periods of light snow and blowing snow"), LightSnow},
0277         {QStringLiteral("periods of light snow mixed with freezing drizzle"), RainSnow},
0278         {QStringLiteral("periods of light snow mixed with freezing rain"), RainSnow},
0279         {QStringLiteral("periods of light snow mixed with ice pellets"), LightSnow},
0280         {QStringLiteral("periods of light snow mixed with rain"), RainSnow},
0281         {QStringLiteral("periods of light snow or freezing drizzle"), RainSnow},
0282         {QStringLiteral("periods of light snow or freezing rain"), RainSnow},
0283         {QStringLiteral("periods of light snow or ice pellets"), LightSnow},
0284         {QStringLiteral("periods of light snow or rain"), RainSnow},
0285         {QStringLiteral("periods of light wet snow"), LightSnow},
0286         {QStringLiteral("periods of light wet snow mixed with rain"), RainSnow},
0287         {QStringLiteral("periods of light wet snow or rain"), RainSnow},
0288         {QStringLiteral("periods of rain"), Rain},
0289         {QStringLiteral("periods of rain mixed with freezing rain"), Rain},
0290         {QStringLiteral("periods of rain mixed with snow"), RainSnow},
0291         {QStringLiteral("periods of rain or drizzle"), Rain},
0292         {QStringLiteral("periods of rain or freezing rain"), Rain},
0293         {QStringLiteral("periods of rain or thundershowers"), Showers},
0294         {QStringLiteral("periods of rain or thunderstorms"), Thunderstorm},
0295         {QStringLiteral("periods of rain or snow"), RainSnow},
0296         {QStringLiteral("periods of snow"), Snow},
0297         {QStringLiteral("periods of snow and blizzard"), Snow},
0298         {QStringLiteral("periods of snow and blizzard and blowing snow"), Snow},
0299         {QStringLiteral("periods of snow and blowing snow"), Snow},
0300         {QStringLiteral("periods of snow mixed with freezing drizzle"), RainSnow},
0301         {QStringLiteral("periods of snow mixed with freezing rain"), RainSnow},
0302         {QStringLiteral("periods of snow mixed with ice pellets"), Snow},
0303         {QStringLiteral("periods of snow mixed with rain"), RainSnow},
0304         {QStringLiteral("periods of snow or freezing drizzle"), RainSnow},
0305         {QStringLiteral("periods of snow or freezing rain"), RainSnow},
0306         {QStringLiteral("periods of snow or ice pellets"), Snow},
0307         {QStringLiteral("periods of snow or rain"), RainSnow},
0308         {QStringLiteral("periods of rain or snow"), RainSnow},
0309         {QStringLiteral("periods of wet snow"), Snow},
0310         {QStringLiteral("periods of wet snow mixed with rain"), RainSnow},
0311         {QStringLiteral("periods of wet snow or rain"), RainSnow},
0312         {QStringLiteral("rain"), Rain},
0313         {QStringLiteral("rain at times heavy"), Rain},
0314         {QStringLiteral("rain at times heavy mixed with freezing rain"), FreezingRain},
0315         {QStringLiteral("rain at times heavy mixed with snow"), RainSnow},
0316         {QStringLiteral("rain at times heavy or drizzle"), Rain},
0317         {QStringLiteral("rain at times heavy or freezing rain"), Rain},
0318         {QStringLiteral("rain at times heavy or snow"), RainSnow},
0319         {QStringLiteral("rain at times heavy or thundershowers"), Showers},
0320         {QStringLiteral("rain at times heavy or thunderstorms"), Thunderstorm},
0321         {QStringLiteral("rain mixed with freezing rain"), FreezingRain},
0322         {QStringLiteral("rain mixed with snow"), RainSnow},
0323         {QStringLiteral("rain or drizzle"), Rain},
0324         {QStringLiteral("rain or freezing rain"), Rain},
0325         {QStringLiteral("rain or snow"), RainSnow},
0326         {QStringLiteral("rain or thundershowers"), Showers},
0327         {QStringLiteral("rain or thunderstorms"), Thunderstorm},
0328         {QStringLiteral("rain showers or flurries"), RainSnow},
0329         {QStringLiteral("rain showers or wet flurries"), RainSnow},
0330         {QStringLiteral("showers"), Showers},
0331         {QStringLiteral("showers at times heavy"), Showers},
0332         {QStringLiteral("showers at times heavy or thundershowers"), Showers},
0333         {QStringLiteral("showers at times heavy or thunderstorms"), Thunderstorm},
0334         {QStringLiteral("showers or drizzle"), Showers},
0335         {QStringLiteral("showers or thundershowers"), Thunderstorm},
0336         {QStringLiteral("showers or thunderstorms"), Thunderstorm},
0337         {QStringLiteral("smoke"), NotAvailable},
0338         {QStringLiteral("snow"), Snow},
0339         {QStringLiteral("snow and blizzard"), Snow},
0340         {QStringLiteral("snow and blizzard and blowing snow"), Snow},
0341         {QStringLiteral("snow and blowing snow"), Snow},
0342         {QStringLiteral("snow at times heavy"), Snow},
0343         {QStringLiteral("snow at times heavy and blizzard"), Snow},
0344         {QStringLiteral("snow at times heavy and blowing snow"), Snow},
0345         {QStringLiteral("snow at times heavy mixed with freezing drizzle"), RainSnow},
0346         {QStringLiteral("snow at times heavy mixed with freezing rain"), RainSnow},
0347         {QStringLiteral("snow at times heavy mixed with ice pellets"), Snow},
0348         {QStringLiteral("snow at times heavy mixed with rain"), RainSnow},
0349         {QStringLiteral("snow at times heavy or freezing rain"), RainSnow},
0350         {QStringLiteral("snow at times heavy or ice pellets"), Snow},
0351         {QStringLiteral("snow at times heavy or rain"), RainSnow},
0352         {QStringLiteral("snow mixed with freezing drizzle"), RainSnow},
0353         {QStringLiteral("snow mixed with freezing rain"), RainSnow},
0354         {QStringLiteral("snow mixed with ice pellets"), Snow},
0355         {QStringLiteral("snow mixed with rain"), RainSnow},
0356         {QStringLiteral("snow or freezing drizzle"), RainSnow},
0357         {QStringLiteral("snow or freezing rain"), RainSnow},
0358         {QStringLiteral("snow or ice pellets"), Snow},
0359         {QStringLiteral("snow or rain"), RainSnow},
0360         {QStringLiteral("snow squalls"), Snow},
0361         {QStringLiteral("sunny"), ClearDay},
0362         {QStringLiteral("sunny with cloudy periods"), PartlyCloudyDay},
0363         {QStringLiteral("thunderstorms"), Thunderstorm},
0364         {QStringLiteral("thunderstorms and possible hail"), Thunderstorm},
0365         {QStringLiteral("wet flurries"), Flurries},
0366         {QStringLiteral("wet flurries at times heavy"), Flurries},
0367         {QStringLiteral("wet flurries at times heavy or rain snowers"), RainSnow},
0368         {QStringLiteral("wet flurries or rain showers"), RainSnow},
0369         {QStringLiteral("wet snow"), Snow},
0370         {QStringLiteral("wet snow at times heavy"), Snow},
0371         {QStringLiteral("wet snow at times heavy mixed with rain"), RainSnow},
0372         {QStringLiteral("wet snow mixed with rain"), RainSnow},
0373         {QStringLiteral("wet snow or rain"), RainSnow},
0374         {QStringLiteral("windy"), NotAvailable},
0375 
0376         {QStringLiteral("chance of drizzle mixed with freezing drizzle"), LightRain},
0377         {QStringLiteral("chance of flurries mixed with ice pellets"), Flurries},
0378         {QStringLiteral("chance of flurries or ice pellets"), Flurries},
0379         {QStringLiteral("chance of flurries or rain showers"), RainSnow},
0380         {QStringLiteral("chance of flurries or thundershowers"), RainSnow},
0381         {QStringLiteral("chance of freezing drizzle"), FreezingDrizzle},
0382         {QStringLiteral("chance of freezing rain"), FreezingRain},
0383         {QStringLiteral("chance of freezing rain mixed with snow"), RainSnow},
0384         {QStringLiteral("chance of freezing rain or rain"), FreezingRain},
0385         {QStringLiteral("chance of freezing rain or snow"), RainSnow},
0386         {QStringLiteral("chance of light snow and blowing snow"), LightSnow},
0387         {QStringLiteral("chance of light snow mixed with freezing drizzle"), LightSnow},
0388         {QStringLiteral("chance of light snow mixed with ice pellets"), LightSnow},
0389         {QStringLiteral("chance of light snow mixed with rain"), RainSnow},
0390         {QStringLiteral("chance of light snow or freezing rain"), RainSnow},
0391         {QStringLiteral("chance of light snow or ice pellets"), LightSnow},
0392         {QStringLiteral("chance of light snow or rain"), RainSnow},
0393         {QStringLiteral("chance of light wet snow"), Snow},
0394         {QStringLiteral("chance of rain"), Rain},
0395         {QStringLiteral("chance of rain at times heavy"), Rain},
0396         {QStringLiteral("chance of rain mixed with snow"), RainSnow},
0397         {QStringLiteral("chance of rain or drizzle"), Rain},
0398         {QStringLiteral("chance of rain or freezing rain"), Rain},
0399         {QStringLiteral("chance of rain or snow"), RainSnow},
0400         {QStringLiteral("chance of rain showers or flurries"), RainSnow},
0401         {QStringLiteral("chance of rain showers or wet flurries"), RainSnow},
0402         {QStringLiteral("chance of severe thunderstorms"), Thunderstorm},
0403         {QStringLiteral("chance of showers at times heavy"), Rain},
0404         {QStringLiteral("chance of showers at times heavy or thundershowers"), Thunderstorm},
0405         {QStringLiteral("chance of showers at times heavy or thunderstorms"), Thunderstorm},
0406         {QStringLiteral("chance of showers or thundershowers"), Thunderstorm},
0407         {QStringLiteral("chance of showers or thunderstorms"), Thunderstorm},
0408         {QStringLiteral("chance of snow"), Snow},
0409         {QStringLiteral("chance of snow and blizzard"), Snow},
0410         {QStringLiteral("chance of snow mixed with freezing drizzle"), Snow},
0411         {QStringLiteral("chance of snow mixed with freezing rain"), RainSnow},
0412         {QStringLiteral("chance of snow mixed with rain"), RainSnow},
0413         {QStringLiteral("chance of snow or rain"), RainSnow},
0414         {QStringLiteral("chance of snow squalls"), Snow},
0415         {QStringLiteral("chance of thundershowers"), Showers},
0416         {QStringLiteral("chance of thunderstorms"), Thunderstorm},
0417         {QStringLiteral("chance of thunderstorms and possible hail"), Thunderstorm},
0418         {QStringLiteral("chance of wet flurries"), Flurries},
0419         {QStringLiteral("chance of wet flurries at times heavy"), Flurries},
0420         {QStringLiteral("chance of wet flurries or rain showers"), RainSnow},
0421         {QStringLiteral("chance of wet snow"), Snow},
0422         {QStringLiteral("chance of wet snow mixed with rain"), RainSnow},
0423         {QStringLiteral("chance of wet snow or rain"), RainSnow},
0424     };
0425 }
0426 
0427 QMap<QString, IonInterface::ConditionIcons> const &EnvCanadaIon::conditionIcons() const
0428 {
0429     static QMap<QString, ConditionIcons> const condval = setupConditionIconMappings();
0430     return condval;
0431 }
0432 
0433 QMap<QString, IonInterface::ConditionIcons> const &EnvCanadaIon::forecastIcons() const
0434 {
0435     static QMap<QString, ConditionIcons> const foreval = setupForecastIconMappings();
0436     return foreval;
0437 }
0438 
0439 QStringList EnvCanadaIon::validate(const QString &source) const
0440 {
0441     QStringList placeList;
0442 
0443     QString sourceNormalized = source.toUpper();
0444     QHash<QString, EnvCanadaIon::XMLMapInfo>::const_iterator it = m_places.constBegin();
0445     while (it != m_places.constEnd()) {
0446         if (it.key().toUpper().contains(sourceNormalized)) {
0447             placeList.append(QStringLiteral("place|") + it.key());
0448         }
0449         ++it;
0450     }
0451 
0452     placeList.sort();
0453     return placeList;
0454 }
0455 
0456 // Get a specific Ion's data
0457 bool EnvCanadaIon::updateIonSource(const QString &source)
0458 {
0459     // qCDebug(IONENGINE_ENVCAN) << "updateIonSource()" << source;
0460 
0461     // We expect the applet to send the source in the following tokenization:
0462     // ionname|validate|place_name - Triggers validation of place
0463     // ionname|weather|place_name - Triggers receiving weather of place
0464 
0465     const QStringList sourceAction = source.split(QLatin1Char('|'));
0466 
0467     // Guard: if the size of array is not 2 then we have bad data, return an error
0468     if (sourceAction.size() < 2) {
0469         setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed"));
0470         return true;
0471     }
0472 
0473     if (sourceAction[1] == QLatin1String("validate") && sourceAction.size() > 2) {
0474         const QStringList result = validate(sourceAction[2]);
0475 
0476         const QString reply = (result.size() == 1        ? QString(QStringLiteral("envcan|valid|single|") + result[0])
0477                                    : (result.size() > 1) ? QString(QStringLiteral("envcan|valid|multiple|") + result.join(QLatin1Char('|')))
0478                                                          : QString(QStringLiteral("envcan|invalid|single|") + sourceAction[2]));
0479         setData(source, QStringLiteral("validate"), reply);
0480 
0481         return true;
0482     }
0483     if (sourceAction[1] == QLatin1String("weather") && sourceAction.size() > 2) {
0484         getXMLData(source);
0485         return true;
0486     }
0487     setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed"));
0488     return true;
0489 }
0490 
0491 // Parses city list and gets the correct city based on ID number
0492 void EnvCanadaIon::getXMLSetup()
0493 {
0494     // qCDebug(IONENGINE_ENVCAN) << "getXMLSetup()";
0495 
0496     // If network is down, we need to spin and wait
0497 
0498     const QUrl url(QStringLiteral("http://dd.weather.gc.ca/citypage_weather/xml/siteList.xml"));
0499 
0500     KIO::TransferJob *getJob = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
0501 
0502     m_xmlSetup.clear();
0503     connect(getJob, &KIO::TransferJob::data, this, &EnvCanadaIon::setup_slotDataArrived);
0504     connect(getJob, &KJob::result, this, &EnvCanadaIon::setup_slotJobFinished);
0505 }
0506 
0507 // Gets specific city XML data
0508 void EnvCanadaIon::getXMLData(const QString &source)
0509 {
0510     for (const QString &fetching : std::as_const(m_jobList)) {
0511         if (fetching == source) {
0512             // already getting this source and awaiting the data
0513             return;
0514         }
0515     }
0516 
0517     // qCDebug(IONENGINE_ENVCAN) << source;
0518 
0519     // Demunge source name for key only.
0520     QString dataKey = source;
0521     dataKey.remove(QStringLiteral("envcan|weather|"));
0522     const XMLMapInfo &place = m_places[dataKey];
0523 
0524     const QUrl url(QLatin1String("http://dd.weather.gc.ca/citypage_weather/xml/") + place.territoryName + QLatin1Char('/') + place.cityCode
0525                    + QStringLiteral("_e.xml"));
0526     // url="file:///home/spstarr/Desktop/s0000649_e.xml";
0527     // qCDebug(IONENGINE_ENVCAN) << "Will Try URL: " << url;
0528 
0529     if (place.territoryName.isEmpty() && place.cityCode.isEmpty()) {
0530         setData(source, QStringLiteral("validate"), QStringLiteral("envcan|malformed"));
0531         return;
0532     }
0533 
0534     KIO::TransferJob *getJob = KIO::get(url, KIO::Reload, KIO::HideProgressInfo);
0535 
0536     m_jobXml.insert(getJob, new QXmlStreamReader);
0537     m_jobList.insert(getJob, source);
0538 
0539     connect(getJob, &KIO::TransferJob::data, this, &EnvCanadaIon::slotDataArrived);
0540     connect(getJob, &KJob::result, this, &EnvCanadaIon::slotJobFinished);
0541 }
0542 
0543 void EnvCanadaIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data)
0544 {
0545     Q_UNUSED(job)
0546 
0547     if (data.isEmpty()) {
0548         // qCDebug(IONENGINE_ENVCAN) << "done!";
0549         return;
0550     }
0551 
0552     // Send to xml.
0553     // qCDebug(IONENGINE_ENVCAN) << data;
0554     m_xmlSetup.addData(data);
0555 }
0556 
0557 void EnvCanadaIon::slotDataArrived(KIO::Job *job, const QByteArray &data)
0558 {
0559     if (data.isEmpty() || !m_jobXml.contains(job)) {
0560         return;
0561     }
0562 
0563     // Send to xml.
0564     m_jobXml[job]->addData(data);
0565 }
0566 
0567 void EnvCanadaIon::slotJobFinished(KJob *job)
0568 {
0569     // Dual use method, if we're fetching location data to parse we need to do this first
0570     const QString source = m_jobList.value(job);
0571     // qCDebug(IONENGINE_ENVCAN) << source << m_sourcesToReset.contains(source);
0572     setData(source, Data());
0573     QXmlStreamReader *reader = m_jobXml.value(job);
0574     if (reader) {
0575         readXMLData(m_jobList[job], *reader);
0576     }
0577 
0578     m_jobList.remove(job);
0579     delete m_jobXml[job];
0580     m_jobXml.remove(job);
0581 
0582     if (m_sourcesToReset.contains(source)) {
0583         m_sourcesToReset.removeAll(source);
0584 
0585         // so the weather engine updates it's data
0586         forceImmediateUpdateOfAllVisualizations();
0587 
0588         // update the clients of our engine
0589         Q_EMIT forceUpdate(this, source);
0590     }
0591 }
0592 
0593 void EnvCanadaIon::setup_slotJobFinished(KJob *job)
0594 {
0595     Q_UNUSED(job)
0596     const bool success = readXMLSetup();
0597     m_xmlSetup.clear();
0598     // qCDebug(IONENGINE_ENVCAN) << success << m_sourcesToReset;
0599     setInitialized(success);
0600 }
0601 
0602 // Parse the city list and store into a QMap
0603 bool EnvCanadaIon::readXMLSetup()
0604 {
0605     bool success = false;
0606     QString territory;
0607     QString code;
0608     QString cityName;
0609 
0610     // qCDebug(IONENGINE_ENVCAN) << "readXMLSetup()";
0611 
0612     while (!m_xmlSetup.atEnd()) {
0613         m_xmlSetup.readNext();
0614 
0615         const auto elementName = m_xmlSetup.name();
0616 
0617         if (m_xmlSetup.isStartElement()) {
0618             // XML ID code to match filename
0619             if (elementName == QLatin1String("site")) {
0620                 code = m_xmlSetup.attributes().value(QStringLiteral("code")).toString();
0621             }
0622 
0623             if (elementName == QLatin1String("nameEn")) {
0624                 cityName = m_xmlSetup.readElementText(); // Name of cities
0625             }
0626 
0627             if (elementName == QLatin1String("provinceCode")) {
0628                 territory = m_xmlSetup.readElementText(); // Provinces/Territory list
0629             }
0630         }
0631 
0632         if (m_xmlSetup.isEndElement() && elementName == QLatin1String("site")) {
0633             EnvCanadaIon::XMLMapInfo info;
0634             QString tmp = cityName + QStringLiteral(", ") + territory; // Build the key name.
0635 
0636             // Set the mappings
0637             info.cityCode = code;
0638             info.territoryName = territory;
0639             info.cityName = cityName;
0640 
0641             // Set the string list, we will use for the applet to display the available cities.
0642             m_places[tmp] = info;
0643             success = true;
0644         }
0645     }
0646 
0647     return (success && !m_xmlSetup.error());
0648 }
0649 
0650 void EnvCanadaIon::parseWeatherSite(WeatherData &data, QXmlStreamReader &xml)
0651 {
0652     while (!xml.atEnd()) {
0653         xml.readNext();
0654 
0655         const auto elementName = xml.name();
0656 
0657         if (xml.isStartElement()) {
0658             if (elementName == QLatin1String("license")) {
0659                 data.creditUrl = xml.readElementText();
0660             } else if (elementName == QLatin1String("location")) {
0661                 parseLocations(data, xml);
0662             } else if (elementName == QLatin1String("warnings")) {
0663                 // Cleanup warning list on update
0664                 data.warnings.clear();
0665                 parseWarnings(data, xml);
0666             } else if (elementName == QLatin1String("currentConditions")) {
0667                 parseConditions(data, xml);
0668             } else if (elementName == QLatin1String("forecastGroup")) {
0669                 // Clean up forecast list on update
0670                 data.forecasts.clear();
0671                 parseWeatherForecast(data, xml);
0672             } else if (elementName == QLatin1String("yesterdayConditions")) {
0673                 parseYesterdayWeather(data, xml);
0674             } else if (elementName == QLatin1String("riseSet")) {
0675                 parseAstronomicals(data, xml);
0676             } else if (elementName == QLatin1String("almanac")) {
0677                 parseWeatherRecords(data, xml);
0678             } else {
0679                 parseUnknownElement(xml);
0680             }
0681         }
0682     }
0683 }
0684 
0685 // Parse Weather data main loop, from here we have to descend into each tag pair
0686 bool EnvCanadaIon::readXMLData(const QString &source, QXmlStreamReader &xml)
0687 {
0688     WeatherData data;
0689 
0690     // qCDebug(IONENGINE_ENVCAN) << "readXMLData()";
0691 
0692     QString dataKey = source;
0693     dataKey.remove(QStringLiteral("envcan|weather|"));
0694     data.shortTerritoryName = m_places[dataKey].territoryName;
0695     while (!xml.atEnd()) {
0696         xml.readNext();
0697 
0698         if (xml.isEndElement()) {
0699             break;
0700         }
0701 
0702         if (xml.isStartElement()) {
0703             if (xml.name() == QLatin1String("siteData")) {
0704                 parseWeatherSite(data, xml);
0705             } else {
0706                 parseUnknownElement(xml);
0707             }
0708         }
0709     }
0710 
0711     bool solarDataSourceNeedsConnect = false;
0712     Plasma5Support::DataEngine *timeEngine = dataEngine(QStringLiteral("time"));
0713     if (timeEngine) {
0714         const bool canCalculateElevation = (data.observationDateTime.isValid() && (!qIsNaN(data.stationLatitude) && !qIsNaN(data.stationLongitude)));
0715         if (canCalculateElevation) {
0716             data.solarDataTimeEngineSourceName = QStringLiteral("%1|Solar|Latitude=%2|Longitude=%3|DateTime=%4")
0717                                                      .arg(QString::fromUtf8(data.observationDateTime.timeZone().id()))
0718                                                      .arg(data.stationLatitude)
0719                                                      .arg(data.stationLongitude)
0720                                                      .arg(data.observationDateTime.toString(Qt::ISODate));
0721             solarDataSourceNeedsConnect = true;
0722         }
0723 
0724         // check any previous data
0725         const auto it = m_weatherData.constFind(source);
0726         if (it != m_weatherData.constEnd()) {
0727             const QString &oldSolarDataTimeEngineSource = it.value().solarDataTimeEngineSourceName;
0728 
0729             if (oldSolarDataTimeEngineSource == data.solarDataTimeEngineSourceName) {
0730                 // can reuse elevation source (if any), copy over data
0731                 data.isNight = it.value().isNight;
0732                 solarDataSourceNeedsConnect = false;
0733             } else if (!oldSolarDataTimeEngineSource.isEmpty()) {
0734                 // drop old elevation source
0735                 timeEngine->disconnectSource(oldSolarDataTimeEngineSource, this);
0736             }
0737         }
0738     }
0739 
0740     m_weatherData[source] = data;
0741 
0742     // connect only after m_weatherData has the data, so the instant data push handling can see it
0743     if (solarDataSourceNeedsConnect) {
0744         timeEngine->connectSource(data.solarDataTimeEngineSourceName, this);
0745     } else {
0746         updateWeather(source);
0747     }
0748 
0749     return !xml.error();
0750 }
0751 
0752 void EnvCanadaIon::parseFloat(float &value, QXmlStreamReader &xml)
0753 {
0754     bool ok = false;
0755     const float result = xml.readElementText().toFloat(&ok);
0756     if (ok) {
0757         value = result;
0758     }
0759 }
0760 
0761 void EnvCanadaIon::parseDateTime(WeatherData &data, QXmlStreamReader &xml, WeatherData::WeatherEvent *event)
0762 {
0763     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("dateTime"));
0764 
0765     // What kind of date info is this?
0766     const QString dateType = xml.attributes().value(QStringLiteral("name")).toString();
0767     const QString dateZone = xml.attributes().value(QStringLiteral("zone")).toString();
0768     const QString dateUtcOffset = xml.attributes().value(QStringLiteral("UTCOffset")).toString();
0769 
0770     QString selectTimeStamp;
0771 
0772     while (!xml.atEnd()) {
0773         xml.readNext();
0774 
0775         if (xml.isEndElement()) {
0776             break;
0777         }
0778 
0779         const auto elementName = xml.name();
0780 
0781         if (xml.isStartElement()) {
0782             if (dateType == QLatin1String("xmlCreation")) {
0783                 return;
0784             }
0785             if (dateZone == QLatin1String("UTC")) {
0786                 return;
0787             }
0788             if (elementName == QLatin1String("year")) {
0789                 xml.readElementText();
0790             } else if (elementName == QLatin1String("month")) {
0791                 xml.readElementText();
0792             } else if (elementName == QLatin1String("day")) {
0793                 xml.readElementText();
0794             } else if (elementName == QLatin1String("hour"))
0795                 xml.readElementText();
0796             else if (elementName == QLatin1String("minute"))
0797                 xml.readElementText();
0798             else if (elementName == QLatin1String("timeStamp"))
0799                 selectTimeStamp = xml.readElementText();
0800             else if (elementName == QLatin1String("textSummary")) {
0801                 if (dateType == QLatin1String("eventIssue")) {
0802                     if (event) {
0803                         event->timestamp = xml.readElementText();
0804                     }
0805                 } else if (dateType == QLatin1String("observation")) {
0806                     xml.readElementText();
0807                     QDateTime observationDateTime = QDateTime::fromString(selectTimeStamp, QStringLiteral("yyyyMMddHHmmss"));
0808                     QTimeZone timeZone = QTimeZone(dateZone.toUtf8());
0809                     // if timezone id not recognized, fallback to utcoffset
0810                     if (!timeZone.isValid()) {
0811                         timeZone = QTimeZone(dateUtcOffset.toInt() * 3600);
0812                     }
0813                     if (observationDateTime.isValid() && timeZone.isValid()) {
0814                         data.observationDateTime = observationDateTime;
0815                         data.observationDateTime.setTimeZone(timeZone);
0816                     }
0817                     data.obsTimestamp = observationDateTime.toString(QStringLiteral("dd.MM.yyyy @ hh:mm"));
0818                 } else if (dateType == QLatin1String("forecastIssue")) {
0819                     data.forecastTimestamp = xml.readElementText();
0820                 } else if (dateType == QLatin1String("sunrise")) {
0821                     data.sunriseTimestamp = xml.readElementText();
0822                 } else if (dateType == QLatin1String("sunset")) {
0823                     data.sunsetTimestamp = xml.readElementText();
0824                 } else if (dateType == QLatin1String("moonrise")) {
0825                     data.moonriseTimestamp = xml.readElementText();
0826                 } else if (dateType == QLatin1String("moonset")) {
0827                     data.moonsetTimestamp = xml.readElementText();
0828                 }
0829             }
0830         }
0831     }
0832 }
0833 
0834 void EnvCanadaIon::parseLocations(WeatherData &data, QXmlStreamReader &xml)
0835 {
0836     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("location"));
0837 
0838     while (!xml.atEnd()) {
0839         xml.readNext();
0840 
0841         if (xml.isEndElement()) {
0842             break;
0843         }
0844 
0845         const auto elementName = xml.name();
0846 
0847         if (xml.isStartElement()) {
0848             if (elementName == QLatin1String("country")) {
0849                 data.countryName = xml.readElementText();
0850             } else if (elementName == QLatin1String("province") || elementName == QLatin1String("territory")) {
0851                 data.longTerritoryName = xml.readElementText();
0852             } else if (elementName == QLatin1String("name")) {
0853                 data.cityName = xml.readElementText();
0854             } else if (elementName == QLatin1String("region")) {
0855                 data.regionName = xml.readElementText();
0856             } else {
0857                 parseUnknownElement(xml);
0858             }
0859         }
0860     }
0861 }
0862 
0863 void EnvCanadaIon::parseWindInfo(WeatherData &data, QXmlStreamReader &xml)
0864 {
0865     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("wind"));
0866 
0867     while (!xml.atEnd()) {
0868         xml.readNext();
0869 
0870         if (xml.isEndElement()) {
0871             break;
0872         }
0873 
0874         const auto elementName = xml.name();
0875 
0876         if (xml.isStartElement()) {
0877             if (elementName == QLatin1String("speed")) {
0878                 parseFloat(data.windSpeed, xml);
0879             } else if (elementName == QLatin1String("gust")) {
0880                 parseFloat(data.windGust, xml);
0881             } else if (elementName == QLatin1String("direction")) {
0882                 data.windDirection = xml.readElementText();
0883             } else if (elementName == QLatin1String("bearing")) {
0884                 data.windDegrees = xml.attributes().value(QStringLiteral("degrees")).toString();
0885             } else {
0886                 parseUnknownElement(xml);
0887             }
0888         }
0889     }
0890 }
0891 
0892 void EnvCanadaIon::parseConditions(WeatherData &data, QXmlStreamReader &xml)
0893 {
0894     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("currentConditions"));
0895     data.temperature = qQNaN();
0896     data.dewpoint = qQNaN();
0897     data.condition = i18n("N/A");
0898     data.humidex.clear();
0899     data.stationID = i18n("N/A");
0900     data.stationLatitude = qQNaN();
0901     data.stationLongitude = qQNaN();
0902     data.pressure = qQNaN();
0903     data.visibility = qQNaN();
0904     data.humidity = qQNaN();
0905 
0906     while (!xml.atEnd()) {
0907         xml.readNext();
0908 
0909         const auto elementName = xml.name();
0910 
0911         if (xml.isEndElement() && elementName == QLatin1String("currentConditions"))
0912             break;
0913 
0914         if (xml.isStartElement()) {
0915             if (elementName == QLatin1String("station")) {
0916                 data.stationID = xml.attributes().value(QStringLiteral("code")).toString();
0917                 QRegularExpression dumpDirection(QStringLiteral("[^0-9.]"));
0918                 data.stationLatitude = xml.attributes().value(QStringLiteral("lat")).toString().remove(dumpDirection).toDouble();
0919                 data.stationLongitude = xml.attributes().value(QStringLiteral("lon")).toString().remove(dumpDirection).toDouble();
0920             } else if (elementName == QLatin1String("dateTime")) {
0921                 parseDateTime(data, xml);
0922             } else if (elementName == QLatin1String("condition")) {
0923                 data.condition = xml.readElementText().trimmed();
0924             } else if (elementName == QLatin1String("temperature")) {
0925                 // prevent N/A text to result in 0.0 value
0926                 parseFloat(data.temperature, xml);
0927             } else if (elementName == QLatin1String("dewpoint")) {
0928                 // prevent N/A text to result in 0.0 value
0929                 parseFloat(data.dewpoint, xml);
0930             } else if (elementName == QLatin1String("humidex")) {
0931                 data.humidex = xml.readElementText();
0932             } else if (elementName == QLatin1String("windChill")) {
0933                 // prevent N/A text to result in 0.0 value
0934                 parseFloat(data.windchill, xml);
0935             } else if (elementName == QLatin1String("pressure")) {
0936                 data.pressureTendency = xml.attributes().value(QStringLiteral("tendency")).toString();
0937                 if (data.pressureTendency.isEmpty()) {
0938                     data.pressureTendency = QStringLiteral("steady");
0939                 }
0940                 parseFloat(data.pressure, xml);
0941             } else if (elementName == QLatin1String("visibility")) {
0942                 parseFloat(data.visibility, xml);
0943             } else if (elementName == QLatin1String("relativeHumidity")) {
0944                 parseFloat(data.humidity, xml);
0945             } else if (elementName == QLatin1String("wind")) {
0946                 parseWindInfo(data, xml);
0947             }
0948             //} else {
0949             //    parseUnknownElement(xml);
0950             //}
0951         }
0952     }
0953 }
0954 
0955 void EnvCanadaIon::parseWarnings(WeatherData &data, QXmlStreamReader &xml)
0956 {
0957     WeatherData::WeatherEvent *warning = new WeatherData::WeatherEvent;
0958 
0959     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("warnings"));
0960     QString eventURL = xml.attributes().value(QStringLiteral("url")).toString();
0961 
0962     // envcan provides three type of events: 'warning', 'watch' and 'advisory'
0963     const auto mapToPriority = [](const QString &type) {
0964         if (type == QLatin1String("warning")) {
0965             return 3;
0966         } else if (type == QLatin1String("watch")) {
0967             return 2;
0968         } else {
0969             return 1;
0970         }
0971     };
0972 
0973     while (!xml.atEnd()) {
0974         xml.readNext();
0975 
0976         const auto elementName = xml.name();
0977 
0978         if (xml.isEndElement() && elementName == QLatin1String("warnings")) {
0979             break;
0980         }
0981 
0982         if (xml.isStartElement()) {
0983             if (elementName == QLatin1String("dateTime")) {
0984                 parseDateTime(data, xml, warning);
0985                 if (!warning->timestamp.isEmpty() && !warning->url.isEmpty()) {
0986                     data.warnings.append(warning);
0987                     warning = new WeatherData::WeatherEvent;
0988                 }
0989             } else if (elementName == QLatin1String("event")) {
0990                 // Append new event to list.
0991                 warning->url = eventURL;
0992                 warning->description = xml.attributes().value(QStringLiteral("description")).toString();
0993                 warning->priority = mapToPriority(xml.attributes().value(QStringLiteral("type")).toString());
0994             } else {
0995                 if (xml.name() != QLatin1String("dateTime")) {
0996                     parseUnknownElement(xml);
0997                 }
0998             }
0999         }
1000     }
1001     delete warning;
1002 }
1003 
1004 void EnvCanadaIon::parseWeatherForecast(WeatherData &data, QXmlStreamReader &xml)
1005 {
1006     WeatherData::ForecastInfo *forecast = new WeatherData::ForecastInfo;
1007     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("forecastGroup"));
1008 
1009     while (!xml.atEnd()) {
1010         xml.readNext();
1011 
1012         const auto elementName = xml.name();
1013 
1014         if (xml.isEndElement() && elementName == QLatin1String("forecastGroup")) {
1015             break;
1016         }
1017 
1018         if (xml.isStartElement()) {
1019             if (elementName == QLatin1String("dateTime")) {
1020                 parseDateTime(data, xml);
1021             } else if (elementName == QLatin1String("regionalNormals")) {
1022                 parseRegionalNormals(data, xml);
1023             } else if (elementName == QLatin1String("forecast")) {
1024                 parseForecast(data, xml, forecast);
1025                 forecast = new WeatherData::ForecastInfo;
1026             } else {
1027                 parseUnknownElement(xml);
1028             }
1029         }
1030     }
1031     delete forecast;
1032 }
1033 
1034 void EnvCanadaIon::parseRegionalNormals(WeatherData &data, QXmlStreamReader &xml)
1035 {
1036     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("regionalNormals"));
1037 
1038     while (!xml.atEnd()) {
1039         xml.readNext();
1040 
1041         if (xml.isEndElement()) {
1042             break;
1043         }
1044 
1045         const auto elementName = xml.name();
1046 
1047         if (xml.isStartElement()) {
1048             if (elementName == QLatin1String("textSummary")) {
1049                 xml.readElementText();
1050             } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("high")) {
1051                 // prevent N/A text to result in 0.0 value
1052                 parseFloat(data.normalHigh, xml);
1053             } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("low")) {
1054                 // prevent N/A text to result in 0.0 value
1055                 parseFloat(data.normalLow, xml);
1056             }
1057         }
1058     }
1059 }
1060 
1061 void EnvCanadaIon::parseForecast(WeatherData &data, QXmlStreamReader &xml, WeatherData::ForecastInfo *forecast)
1062 {
1063     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("forecast"));
1064 
1065     while (!xml.atEnd()) {
1066         xml.readNext();
1067 
1068         const auto elementName = xml.name();
1069 
1070         if (xml.isEndElement() && elementName == QLatin1String("forecast")) {
1071             data.forecasts.append(forecast);
1072             break;
1073         }
1074 
1075         if (xml.isStartElement()) {
1076             if (elementName == QLatin1String("period")) {
1077                 forecast->forecastPeriod = xml.attributes().value(QStringLiteral("textForecastName")).toString();
1078             } else if (elementName == QLatin1String("textSummary")) {
1079                 forecast->forecastSummary = xml.readElementText();
1080             } else if (elementName == QLatin1String("abbreviatedForecast")) {
1081                 parseShortForecast(forecast, xml);
1082             } else if (elementName == QLatin1String("temperatures")) {
1083                 parseForecastTemperatures(forecast, xml);
1084             } else if (elementName == QLatin1String("winds")) {
1085                 parseWindForecast(forecast, xml);
1086             } else if (elementName == QLatin1String("precipitation")) {
1087                 parsePrecipitationForecast(forecast, xml);
1088             } else if (elementName == QLatin1String("uv")) {
1089                 data.UVRating = xml.attributes().value(QStringLiteral("category")).toString();
1090                 parseUVIndex(data, xml);
1091                 // else if (elementName == QLatin1String("frost")) { FIXME: Wait until winter to see what this looks like.
1092                 //  parseFrost(xml, forecast);
1093             } else {
1094                 if (elementName != QLatin1String("forecast")) {
1095                     parseUnknownElement(xml);
1096                 }
1097             }
1098         }
1099     }
1100 }
1101 
1102 void EnvCanadaIon::parseShortForecast(WeatherData::ForecastInfo *forecast, QXmlStreamReader &xml)
1103 {
1104     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("abbreviatedForecast"));
1105 
1106     QString shortText;
1107 
1108     while (!xml.atEnd()) {
1109         xml.readNext();
1110 
1111         const auto elementName = xml.name();
1112 
1113         if (xml.isEndElement() && elementName == QLatin1String("abbreviatedForecast")) {
1114             break;
1115         }
1116 
1117         if (xml.isStartElement()) {
1118             if (elementName == QLatin1String("pop")) {
1119                 parseFloat(forecast->popPrecent, xml);
1120             }
1121             if (elementName == QLatin1String("textSummary")) {
1122                 shortText = xml.readElementText();
1123                 QMap<QString, ConditionIcons> forecastList = forecastIcons();
1124                 if ((forecast->forecastPeriod == QLatin1String("tonight")) || (forecast->forecastPeriod.contains(QLatin1String("night")))) {
1125                     forecastList.insert(QStringLiteral("a few clouds"), FewCloudsNight);
1126                     forecastList.insert(QStringLiteral("cloudy periods"), PartlyCloudyNight);
1127                     forecastList.insert(QStringLiteral("chance of drizzle mixed with rain"), ChanceShowersNight);
1128                     forecastList.insert(QStringLiteral("chance of drizzle"), ChanceShowersNight);
1129                     forecastList.insert(QStringLiteral("chance of drizzle or rain"), ChanceShowersNight);
1130                     forecastList.insert(QStringLiteral("chance of flurries"), ChanceSnowNight);
1131                     forecastList.insert(QStringLiteral("chance of light snow"), ChanceSnowNight);
1132                     forecastList.insert(QStringLiteral("chance of flurries at times heavy"), ChanceSnowNight);
1133                     forecastList.insert(QStringLiteral("chance of showers or drizzle"), ChanceShowersNight);
1134                     forecastList.insert(QStringLiteral("chance of showers"), ChanceShowersNight);
1135                     forecastList.insert(QStringLiteral("clearing"), ClearNight);
1136                 } else {
1137                     forecastList.insert(QStringLiteral("a few clouds"), FewCloudsDay);
1138                     forecastList.insert(QStringLiteral("cloudy periods"), PartlyCloudyDay);
1139                     forecastList.insert(QStringLiteral("chance of drizzle mixed with rain"), ChanceShowersDay);
1140                     forecastList.insert(QStringLiteral("chance of drizzle"), ChanceShowersDay);
1141                     forecastList.insert(QStringLiteral("chance of drizzle or rain"), ChanceShowersDay);
1142                     forecastList.insert(QStringLiteral("chance of flurries"), ChanceSnowDay);
1143                     forecastList.insert(QStringLiteral("chance of light snow"), ChanceSnowDay);
1144                     forecastList.insert(QStringLiteral("chance of flurries at times heavy"), ChanceSnowDay);
1145                     forecastList.insert(QStringLiteral("chance of showers or drizzle"), ChanceShowersDay);
1146                     forecastList.insert(QStringLiteral("chance of showers"), ChanceShowersDay);
1147                     forecastList.insert(QStringLiteral("clearing"), ClearDay);
1148                 }
1149                 forecast->shortForecast = shortText;
1150                 forecast->iconName = getWeatherIcon(forecastList, shortText.toLower());
1151             }
1152         }
1153     }
1154 }
1155 
1156 void EnvCanadaIon::parseUVIndex(WeatherData &data, QXmlStreamReader &xml)
1157 {
1158     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("uv"));
1159 
1160     while (!xml.atEnd()) {
1161         xml.readNext();
1162 
1163         const auto elementName = xml.name();
1164 
1165         if (xml.isEndElement() && elementName == QLatin1String("uv")) {
1166             break;
1167         }
1168 
1169         if (xml.isStartElement()) {
1170             if (elementName == QLatin1String("index")) {
1171                 data.UVIndex = xml.readElementText();
1172             }
1173             if (elementName == QLatin1String("textSummary")) {
1174                 xml.readElementText();
1175             }
1176         }
1177     }
1178 }
1179 
1180 void EnvCanadaIon::parseForecastTemperatures(WeatherData::ForecastInfo *forecast, QXmlStreamReader &xml)
1181 {
1182     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("temperatures"));
1183 
1184     while (!xml.atEnd()) {
1185         xml.readNext();
1186 
1187         const auto elementName = xml.name();
1188 
1189         if (xml.isEndElement() && elementName == QLatin1String("temperatures")) {
1190             break;
1191         }
1192 
1193         if (xml.isStartElement()) {
1194             if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("low")) {
1195                 parseFloat(forecast->tempLow, xml);
1196             } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("high")) {
1197                 parseFloat(forecast->tempHigh, xml);
1198             } else if (elementName == QLatin1String("textSummary")) {
1199                 xml.readElementText();
1200             }
1201         }
1202     }
1203 }
1204 
1205 void EnvCanadaIon::parsePrecipitationForecast(WeatherData::ForecastInfo *forecast, QXmlStreamReader &xml)
1206 {
1207     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("precipitation"));
1208 
1209     while (!xml.atEnd()) {
1210         xml.readNext();
1211 
1212         const auto elementName = xml.name();
1213 
1214         if (xml.isEndElement() && elementName == QLatin1String("precipitation")) {
1215             break;
1216         }
1217 
1218         if (xml.isStartElement()) {
1219             if (elementName == QLatin1String("textSummary")) {
1220                 forecast->precipForecast = xml.readElementText();
1221             } else if (elementName == QLatin1String("precipType")) {
1222                 forecast->precipType = xml.readElementText();
1223             } else if (elementName == QLatin1String("accumulation")) {
1224                 parsePrecipTotals(forecast, xml);
1225             }
1226         }
1227     }
1228 }
1229 
1230 void EnvCanadaIon::parsePrecipTotals(WeatherData::ForecastInfo *forecast, QXmlStreamReader &xml)
1231 {
1232     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("accumulation"));
1233 
1234     while (!xml.atEnd()) {
1235         xml.readNext();
1236 
1237         const auto elementName = xml.name();
1238 
1239         if (xml.isEndElement() && elementName == QLatin1String("accumulation")) {
1240             break;
1241         }
1242 
1243         if (elementName == QLatin1String("name")) {
1244             xml.readElementText();
1245         } else if (elementName == QLatin1String("amount")) {
1246             forecast->precipTotalExpected = xml.readElementText();
1247         }
1248     }
1249 }
1250 
1251 void EnvCanadaIon::parseWindForecast(WeatherData::ForecastInfo *forecast, QXmlStreamReader &xml)
1252 {
1253     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("winds"));
1254 
1255     while (!xml.atEnd()) {
1256         xml.readNext();
1257 
1258         const auto elementName = xml.name();
1259 
1260         if (xml.isEndElement() && elementName == QLatin1String("winds")) {
1261             break;
1262         }
1263 
1264         if (xml.isStartElement()) {
1265             if (elementName == QLatin1String("textSummary")) {
1266                 forecast->windForecast = xml.readElementText();
1267             } else {
1268                 if (xml.name() != QLatin1String("winds")) {
1269                     parseUnknownElement(xml);
1270                 }
1271             }
1272         }
1273     }
1274 }
1275 
1276 void EnvCanadaIon::parseYesterdayWeather(WeatherData &data, QXmlStreamReader &xml)
1277 {
1278     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("yesterdayConditions"));
1279 
1280     while (!xml.atEnd()) {
1281         xml.readNext();
1282 
1283         if (xml.isEndElement()) {
1284             break;
1285         }
1286 
1287         const auto elementName = xml.name();
1288 
1289         if (xml.isStartElement()) {
1290             if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("high")) {
1291                 parseFloat(data.prevHigh, xml);
1292             } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("low")) {
1293                 parseFloat(data.prevLow, xml);
1294             } else if (elementName == QLatin1String("precip")) {
1295                 data.prevPrecipType = xml.attributes().value(QStringLiteral("units")).toString();
1296                 if (data.prevPrecipType.isEmpty()) {
1297                     data.prevPrecipType = QString::number(KUnitConversion::NoUnit);
1298                 }
1299                 data.prevPrecipTotal = xml.readElementText();
1300             }
1301         }
1302     }
1303 }
1304 
1305 void EnvCanadaIon::parseWeatherRecords(WeatherData &data, QXmlStreamReader &xml)
1306 {
1307     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("almanac"));
1308 
1309     while (!xml.atEnd()) {
1310         xml.readNext();
1311 
1312         const auto elementName = xml.name();
1313 
1314         if (xml.isEndElement() && elementName == QLatin1String("almanac")) {
1315             break;
1316         }
1317 
1318         if (xml.isStartElement()) {
1319             if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("extremeMax")) {
1320                 parseFloat(data.recordHigh, xml);
1321             } else if (elementName == QLatin1String("temperature") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("extremeMin")) {
1322                 parseFloat(data.recordLow, xml);
1323             } else if (elementName == QLatin1String("precipitation") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("extremeRainfall")) {
1324                 parseFloat(data.recordRain, xml);
1325             } else if (elementName == QLatin1String("precipitation") && xml.attributes().value(QStringLiteral("class")) == QLatin1String("extremeSnowfall")) {
1326                 parseFloat(data.recordSnow, xml);
1327             }
1328         }
1329     }
1330 }
1331 
1332 void EnvCanadaIon::parseAstronomicals(WeatherData &data, QXmlStreamReader &xml)
1333 {
1334     Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("riseSet"));
1335 
1336     while (!xml.atEnd()) {
1337         xml.readNext();
1338 
1339         const auto elementName = xml.name();
1340 
1341         if (xml.isEndElement() && elementName == QLatin1String("riseSet")) {
1342             break;
1343         }
1344 
1345         if (xml.isStartElement()) {
1346             if (elementName == QLatin1String("disclaimer")) {
1347                 xml.readElementText();
1348             } else if (elementName == QLatin1String("dateTime")) {
1349                 parseDateTime(data, xml);
1350             }
1351         }
1352     }
1353 }
1354 
1355 // handle when no XML tag is found
1356 void EnvCanadaIon::parseUnknownElement(QXmlStreamReader &xml) const
1357 {
1358     while (!xml.atEnd()) {
1359         xml.readNext();
1360 
1361         if (xml.isEndElement()) {
1362             break;
1363         }
1364 
1365         if (xml.isStartElement()) {
1366             parseUnknownElement(xml);
1367         }
1368     }
1369 }
1370 
1371 void EnvCanadaIon::updateWeather(const QString &source)
1372 {
1373     // qCDebug(IONENGINE_ENVCAN) << "updateWeather()";
1374 
1375     const WeatherData &weatherData = m_weatherData[source];
1376 
1377     Plasma5Support::DataEngine::Data data;
1378 
1379     data.insert(QStringLiteral("Country"), weatherData.countryName);
1380     data.insert(QStringLiteral("Place"), QVariant(weatherData.cityName + QStringLiteral(", ") + weatherData.shortTerritoryName));
1381     data.insert(QStringLiteral("Region"), weatherData.regionName);
1382 
1383     data.insert(QStringLiteral("Station"), weatherData.stationID.isEmpty() ? i18n("N/A") : weatherData.stationID.toUpper());
1384 
1385     const bool stationCoordValid = (!qIsNaN(weatherData.stationLatitude) && !qIsNaN(weatherData.stationLongitude));
1386 
1387     if (stationCoordValid) {
1388         data.insert(QStringLiteral("Latitude"), weatherData.stationLatitude);
1389         data.insert(QStringLiteral("Longitude"), weatherData.stationLongitude);
1390     }
1391 
1392     // Real weather - Current conditions
1393     if (weatherData.observationDateTime.isValid()) {
1394         data.insert(QStringLiteral("Observation Timestamp"), weatherData.observationDateTime);
1395     }
1396 
1397     data.insert(QStringLiteral("Observation Period"), weatherData.obsTimestamp);
1398 
1399     if (!weatherData.condition.isEmpty()) {
1400         data.insert(QStringLiteral("Current Conditions"), i18nc("weather condition", weatherData.condition.toUtf8().data()));
1401     }
1402     // qCDebug(IONENGINE_ENVCAN) << "i18n condition string: " << qPrintable(condition(source));
1403 
1404     QMap<QString, ConditionIcons> conditionList = conditionIcons();
1405 
1406     if (weatherData.isNight) {
1407         conditionList.insert(QStringLiteral("decreasing cloud"), FewCloudsNight);
1408         conditionList.insert(QStringLiteral("mostly cloudy"), PartlyCloudyNight);
1409         conditionList.insert(QStringLiteral("partly cloudy"), PartlyCloudyNight);
1410         conditionList.insert(QStringLiteral("fair"), FewCloudsNight);
1411     } else {
1412         conditionList.insert(QStringLiteral("decreasing cloud"), FewCloudsDay);
1413         conditionList.insert(QStringLiteral("mostly cloudy"), PartlyCloudyDay);
1414         conditionList.insert(QStringLiteral("partly cloudy"), PartlyCloudyDay);
1415         conditionList.insert(QStringLiteral("fair"), FewCloudsDay);
1416     }
1417 
1418     data.insert(QStringLiteral("Condition Icon"), getWeatherIcon(conditionList, weatherData.condition));
1419 
1420     if (!qIsNaN(weatherData.temperature)) {
1421         data.insert(QStringLiteral("Temperature"), weatherData.temperature);
1422     }
1423     if (!qIsNaN(weatherData.windchill)) {
1424         data.insert(QStringLiteral("Windchill"), weatherData.windchill);
1425     }
1426     if (!weatherData.humidex.isEmpty()) {
1427         data.insert(QStringLiteral("Humidex"), weatherData.humidex);
1428     }
1429 
1430     // Used for all temperatures
1431     data.insert(QStringLiteral("Temperature Unit"), KUnitConversion::Celsius);
1432 
1433     if (!qIsNaN(weatherData.dewpoint)) {
1434         data.insert(QStringLiteral("Dewpoint"), weatherData.dewpoint);
1435     }
1436 
1437     if (!qIsNaN(weatherData.pressure)) {
1438         data.insert(QStringLiteral("Pressure"), weatherData.pressure);
1439         data.insert(QStringLiteral("Pressure Unit"), KUnitConversion::Kilopascal);
1440         data.insert(QStringLiteral("Pressure Tendency"), weatherData.pressureTendency);
1441     }
1442 
1443     if (!qIsNaN(weatherData.visibility)) {
1444         data.insert(QStringLiteral("Visibility"), weatherData.visibility);
1445         data.insert(QStringLiteral("Visibility Unit"), KUnitConversion::Kilometer);
1446     }
1447 
1448     if (!qIsNaN(weatherData.humidity)) {
1449         data.insert(QStringLiteral("Humidity"), weatherData.humidity);
1450         data.insert(QStringLiteral("Humidity Unit"), KUnitConversion::Percent);
1451     }
1452 
1453     if (!qIsNaN(weatherData.windSpeed)) {
1454         data.insert(QStringLiteral("Wind Speed"), weatherData.windSpeed);
1455     }
1456     if (!qIsNaN(weatherData.windGust)) {
1457         data.insert(QStringLiteral("Wind Gust"), weatherData.windGust);
1458     }
1459 
1460     if (!qIsNaN(weatherData.windSpeed) || !qIsNaN(weatherData.windGust)) {
1461         data.insert(QStringLiteral("Wind Speed Unit"), KUnitConversion::KilometerPerHour);
1462     }
1463 
1464     if (!qIsNaN(weatherData.windSpeed) && static_cast<int>(weatherData.windSpeed) == 0) {
1465         data.insert(QStringLiteral("Wind Direction"), QStringLiteral("VR")); // Variable/calm
1466     } else if (!weatherData.windDirection.isEmpty()) {
1467         data.insert(QStringLiteral("Wind Direction"), weatherData.windDirection);
1468     }
1469 
1470     if (!qIsNaN(weatherData.normalHigh)) {
1471         data.insert(QStringLiteral("Normal High"), weatherData.normalHigh);
1472     }
1473     if (!qIsNaN(weatherData.normalLow)) {
1474         data.insert(QStringLiteral("Normal Low"), weatherData.normalLow);
1475     }
1476 
1477     // Check if UV index is available for the location
1478     if (!weatherData.UVIndex.isEmpty()) {
1479         data.insert(QStringLiteral("UV Index"), weatherData.UVIndex);
1480     }
1481     if (!weatherData.UVRating.isEmpty()) {
1482         data.insert(QStringLiteral("UV Rating"), weatherData.UVRating);
1483     }
1484 
1485     const QList<WeatherData::WeatherEvent *> &warnings = weatherData.warnings;
1486 
1487     data.insert(QStringLiteral("Total Warnings Issued"), warnings.size());
1488 
1489     for (int k = 0; k < warnings.size(); ++k) {
1490         const WeatherData::WeatherEvent *warning = warnings.at(k);
1491         const QString number = QString::number(k);
1492 
1493         data.insert(QStringLiteral("Warning Priority ") + number, warning->priority);
1494         data.insert(QStringLiteral("Warning Description ") + number, warning->description);
1495         data.insert(QStringLiteral("Warning Info ") + number, warning->url);
1496         data.insert(QStringLiteral("Warning Timestamp ") + number, warning->timestamp);
1497     }
1498 
1499     const QList<WeatherData::ForecastInfo *> &forecasts = weatherData.forecasts;
1500 
1501     // Set number of forecasts per day/night supported
1502     data.insert(QStringLiteral("Total Weather Days"), forecasts.size());
1503 
1504     int i = 0;
1505     for (const WeatherData::ForecastInfo *forecastInfo : forecasts) {
1506         QString forecastPeriod = forecastInfo->forecastPeriod;
1507         if (forecastPeriod.isEmpty()) {
1508             forecastPeriod = i18n("N/A");
1509         } else {
1510             // We need to shortform the day/night strings.
1511 
1512             forecastPeriod.replace(QStringLiteral("Today"), i18n("day"));
1513             forecastPeriod.replace(QStringLiteral("Tonight"), i18nc("Short for tonight", "nite"));
1514             forecastPeriod.replace(QStringLiteral("night"), i18nc("Short for night, appended to the end of the weekday", "nt"));
1515             forecastPeriod.replace(QStringLiteral("Saturday"), i18nc("Short for Saturday", "Sat"));
1516             forecastPeriod.replace(QStringLiteral("Sunday"), i18nc("Short for Sunday", "Sun"));
1517             forecastPeriod.replace(QStringLiteral("Monday"), i18nc("Short for Monday", "Mon"));
1518             forecastPeriod.replace(QStringLiteral("Tuesday"), i18nc("Short for Tuesday", "Tue"));
1519             forecastPeriod.replace(QStringLiteral("Wednesday"), i18nc("Short for Wednesday", "Wed"));
1520             forecastPeriod.replace(QStringLiteral("Thursday"), i18nc("Short for Thursday", "Thu"));
1521             forecastPeriod.replace(QStringLiteral("Friday"), i18nc("Short for Friday", "Fri"));
1522         }
1523         const QString shortForecast =
1524             forecastInfo->shortForecast.isEmpty() ? i18n("N/A") : i18nc("weather forecast", forecastInfo->shortForecast.toUtf8().data());
1525 
1526         const QString tempHigh = qIsNaN(forecastInfo->tempHigh) ? QString() : QString::number(forecastInfo->tempHigh);
1527         const QString tempLow = qIsNaN(forecastInfo->tempLow) ? QString() : QString::number(forecastInfo->tempLow);
1528         const QString popPrecent = qIsNaN(forecastInfo->popPrecent) ? QString() : QString::number(forecastInfo->popPrecent);
1529 
1530         data.insert(QStringLiteral("Short Forecast Day %1").arg(i),
1531                     QStringLiteral("%1|%2|%3|%4|%5|%6").arg(forecastPeriod, forecastInfo->iconName, shortForecast, tempHigh, tempLow, popPrecent));
1532         ++i;
1533     }
1534 
1535     // yesterday
1536     if (!qIsNaN(weatherData.prevHigh)) {
1537         data.insert(QStringLiteral("Yesterday High"), weatherData.prevHigh);
1538     }
1539     if (!qIsNaN(weatherData.prevLow)) {
1540         data.insert(QStringLiteral("Yesterday Low"), weatherData.prevLow);
1541     }
1542 
1543     const QString &prevPrecipTotal = weatherData.prevPrecipTotal;
1544     if (prevPrecipTotal == QLatin1String("Trace")) {
1545         data.insert(QStringLiteral("Yesterday Precip Total"), i18nc("precipitation total, very little", "Trace"));
1546     } else if (!prevPrecipTotal.isEmpty()) {
1547         data.insert(QStringLiteral("Yesterday Precip Total"), prevPrecipTotal);
1548         const QString &prevPrecipType = weatherData.prevPrecipType;
1549         const KUnitConversion::UnitId unit = (prevPrecipType == QLatin1String("mm")       ? KUnitConversion::Millimeter
1550                                                   : prevPrecipType == QLatin1String("cm") ? KUnitConversion::Centimeter
1551                                                                                           : KUnitConversion::NoUnit);
1552         data.insert(QStringLiteral("Yesterday Precip Unit"), unit);
1553     }
1554 
1555     // records
1556     if (!qIsNaN(weatherData.recordHigh)) {
1557         data.insert(QStringLiteral("Record High Temperature"), weatherData.recordHigh);
1558     }
1559     if (!qIsNaN(weatherData.recordLow)) {
1560         data.insert(QStringLiteral("Record Low Temperature"), weatherData.recordLow);
1561     }
1562     if (!qIsNaN(weatherData.recordRain)) {
1563         data.insert(QStringLiteral("Record Rainfall"), weatherData.recordRain);
1564         data.insert(QStringLiteral("Record Rainfall Unit"), KUnitConversion::Millimeter);
1565     }
1566     if (!qIsNaN(weatherData.recordSnow)) {
1567         data.insert(QStringLiteral("Record Snowfall"), weatherData.recordSnow);
1568         data.insert(QStringLiteral("Record Snowfall Unit"), KUnitConversion::Centimeter);
1569     }
1570 
1571     data.insert(QStringLiteral("Credit"), i18nc("credit line, keep string short", "Data from Environment and Climate Change\302\240Canada"));
1572     data.insert(QStringLiteral("Credit Url"), weatherData.creditUrl);
1573     setData(source, data);
1574 }
1575 
1576 void EnvCanadaIon::dataUpdated(const QString &sourceName, const Plasma5Support::DataEngine::Data &data)
1577 {
1578     const bool isNight = (data.value(QStringLiteral("Corrected Elevation")).toDouble() < 0.0);
1579 
1580     for (auto end = m_weatherData.end(), it = m_weatherData.begin(); it != end; ++it) {
1581         auto &weatherData = it.value();
1582         if (weatherData.solarDataTimeEngineSourceName == sourceName) {
1583             weatherData.isNight = isNight;
1584             updateWeather(it.key());
1585         }
1586     }
1587 }
1588 
1589 K_PLUGIN_CLASS_WITH_JSON(EnvCanadaIon, "ion-envcan.json")
1590 
1591 #include "ion_envcan.moc"