File indexing completed on 2024-05-12 15:31:19

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2009 Bastian Holst <bastianholst@gmx.de>
0004 //
0005 
0006 // Self
0007 #include "BBCParser.h"
0008 
0009 // Marble
0010 #include "MarbleGlobal.h"
0011 #include "BBCWeatherItem.h"
0012 #include "MarbleDebug.h"
0013 
0014 // Qt
0015 #include <QDateTime>
0016 #include <QFile>
0017 #include <QMutexLocker>
0018 #include <QRegExp>
0019 
0020 using namespace Marble;
0021 
0022 BBCParser::BBCParser( QObject *parent ) :
0023     AbstractWorkerThread( parent ),
0024     m_dayConditions(),
0025     m_nightConditions(),
0026     m_windDirections(),
0027     m_pressureDevelopments(),
0028     m_visibilityStates(),
0029     m_monthNames()
0030 {
0031     m_dayConditions["sunny"] = WeatherData::ClearDay;
0032     m_dayConditions["clear"] = WeatherData::ClearDay;
0033     m_dayConditions["clear sky"] = WeatherData::ClearDay;
0034     m_dayConditions["sunny intervals"] = WeatherData::FewCloudsDay;
0035     m_dayConditions["partly cloudy"] = WeatherData::PartlyCloudyDay;
0036     m_dayConditions["white cloud"] = WeatherData::Overcast;
0037     m_dayConditions["grey cloud"] = WeatherData::Overcast;
0038     m_dayConditions["cloudy"] = WeatherData::Overcast;
0039     m_dayConditions["drizzle"] = WeatherData::LightRain;
0040     m_dayConditions["misty"] = WeatherData::Mist;
0041     m_dayConditions["mist"] = WeatherData::Mist;
0042     m_dayConditions["fog"] = WeatherData::Mist;
0043     m_dayConditions["foggy"] = WeatherData::Mist;
0044     m_dayConditions["dense fog"] = WeatherData::Mist;
0045     m_dayConditions["Thick Fog"] = WeatherData::Mist;
0046     m_dayConditions["tropical storm"] = WeatherData::Thunderstorm;
0047     m_dayConditions["hazy"] = WeatherData::Mist;
0048     m_dayConditions["light shower"] = WeatherData::LightShowersDay;
0049     m_dayConditions["light rain shower"] = WeatherData::LightShowersDay;
0050     m_dayConditions["light showers"] = WeatherData::LightShowersDay;
0051     m_dayConditions["light rain"] = WeatherData::ShowersDay;
0052     m_dayConditions["heavy rain"] = WeatherData::Rain;
0053     m_dayConditions["heavy showers"] = WeatherData::Rain;
0054     m_dayConditions["heavy shower"] = WeatherData::Rain;
0055     m_dayConditions["heavy rain shower"] = WeatherData::Rain;
0056     m_dayConditions["thundery shower"] = WeatherData::Thunderstorm;
0057     m_dayConditions["thunderstorm"] = WeatherData::Thunderstorm;
0058     m_dayConditions["thunder storm"] = WeatherData::Thunderstorm;
0059     m_dayConditions["cloudy with sleet"] = WeatherData::RainSnow;
0060     m_dayConditions["sleet shower"] = WeatherData::RainSnow;
0061     m_dayConditions["sleet showers"] = WeatherData::RainSnow;
0062     m_dayConditions["sleet"] = WeatherData::RainSnow;
0063     m_dayConditions["cloudy with hail"] = WeatherData::Hail;
0064     m_dayConditions["hail shower"] = WeatherData::Hail;
0065     m_dayConditions["hail showers"] = WeatherData::Hail;
0066     m_dayConditions["hail"] = WeatherData::Hail;
0067     m_dayConditions["light snow"] = WeatherData::LightSnow;
0068     m_dayConditions["light snow shower"] = WeatherData::ChanceSnowDay;
0069     m_dayConditions["light snow showers"] = WeatherData::ChanceSnowDay;
0070     m_dayConditions["cloudy with light snow"] = WeatherData::LightSnow;
0071     m_dayConditions["heavy snow"] = WeatherData::Snow;
0072     m_dayConditions["heavy snow shower"] = WeatherData::Snow;
0073     m_dayConditions["heavy snow showers"] = WeatherData::Snow;
0074     m_dayConditions["cloudy with heavy snow"] = WeatherData::Snow;
0075     m_dayConditions["sandstorm"] = WeatherData::SandStorm;
0076     m_dayConditions["na"] = WeatherData::ConditionNotAvailable;
0077     m_dayConditions["N/A"] = WeatherData::ConditionNotAvailable;
0078 
0079     m_nightConditions["sunny"] = WeatherData::ClearNight;
0080     m_nightConditions["clear"] = WeatherData::ClearNight;
0081     m_nightConditions["clear sky"] = WeatherData::ClearNight;
0082     m_nightConditions["sunny intervals"] = WeatherData::FewCloudsNight;
0083     m_nightConditions["partly cloudy"] = WeatherData::PartlyCloudyNight;
0084     m_nightConditions["white cloud"] = WeatherData::Overcast;
0085     m_nightConditions["grey cloud"] = WeatherData::Overcast;
0086     m_nightConditions["cloudy"] = WeatherData::Overcast;
0087     m_nightConditions["drizzle"] = WeatherData::LightRain;
0088     m_nightConditions["misty"] = WeatherData::Mist;
0089     m_nightConditions["mist"] = WeatherData::Mist;
0090     m_nightConditions["fog"] = WeatherData::Mist;
0091     m_nightConditions["foggy"] = WeatherData::Mist;
0092     m_nightConditions["dense fog"] = WeatherData::Mist;
0093     m_nightConditions["Thick Fog"] = WeatherData::Mist;
0094     m_nightConditions["tropical storm"] = WeatherData::Thunderstorm;
0095     m_nightConditions["hazy"] = WeatherData::Mist;
0096     m_nightConditions["light shower"] = WeatherData::LightShowersNight;
0097     m_nightConditions["light rain shower"] = WeatherData::LightShowersNight;
0098     m_nightConditions["light showers"] = WeatherData::LightShowersNight;
0099     m_nightConditions["light rain"] = WeatherData::ShowersNight;
0100     m_nightConditions["heavy rain"] = WeatherData::Rain;
0101     m_nightConditions["heavy showers"] = WeatherData::Rain;
0102     m_nightConditions["heavy shower"] = WeatherData::Rain;
0103     m_nightConditions["heavy rain shower"] = WeatherData::Rain;
0104     m_nightConditions["thundery shower"] = WeatherData::Thunderstorm;
0105     m_nightConditions["thunderstorm"] = WeatherData::Thunderstorm;
0106     m_nightConditions["thunder storm"] = WeatherData::Thunderstorm;
0107     m_nightConditions["cloudy with sleet"] = WeatherData::RainSnow;
0108     m_nightConditions["sleet shower"] = WeatherData::RainSnow;
0109     m_nightConditions["sleet showers"] = WeatherData::RainSnow;
0110     m_nightConditions["sleet"] = WeatherData::RainSnow;
0111     m_nightConditions["cloudy with hail"] = WeatherData::Hail;
0112     m_nightConditions["hail shower"] = WeatherData::Hail;
0113     m_nightConditions["hail showers"] = WeatherData::Hail;
0114     m_nightConditions["hail"] = WeatherData::Hail;
0115     m_nightConditions["light snow"] = WeatherData::LightSnow;
0116     m_nightConditions["light snow shower"] = WeatherData::ChanceSnowNight;
0117     m_nightConditions["light snow showers"] = WeatherData::ChanceSnowNight;
0118     m_nightConditions["cloudy with light snow"] = WeatherData::LightSnow;
0119     m_nightConditions["heavy snow"] = WeatherData::Snow;
0120     m_nightConditions["heavy snow shower"] = WeatherData::Snow;
0121     m_nightConditions["heavy snow showers"] = WeatherData::Snow;
0122     m_nightConditions["cloudy with heavy snow"] = WeatherData::Snow;
0123     m_nightConditions["sandstorm"] = WeatherData::SandStorm;
0124     m_nightConditions["na"] = WeatherData::ConditionNotAvailable;
0125     m_nightConditions["N/A"] = WeatherData::ConditionNotAvailable;
0126 
0127     m_windDirections["N"] = WeatherData::N;
0128     m_windDirections["NE"] = WeatherData::NE;
0129     m_windDirections["ENE"] = WeatherData::ENE;
0130     m_windDirections["NNE"] = WeatherData::NNE;
0131     m_windDirections["E"] = WeatherData::E;
0132     m_windDirections["SSE"] = WeatherData::SSE;
0133     m_windDirections["SE"] = WeatherData::SE;
0134     m_windDirections["ESE"] = WeatherData::ESE;
0135     m_windDirections["S"] = WeatherData::S;
0136     m_windDirections["NNW"] = WeatherData::NNW;
0137     m_windDirections["NW"] = WeatherData::NW;
0138     m_windDirections["WNW"] = WeatherData::WNW;
0139     m_windDirections["W"] = WeatherData::W;
0140     m_windDirections["SSW"] = WeatherData::SSW;
0141     m_windDirections["SW"] = WeatherData::SW;
0142     m_windDirections["WSW"] = WeatherData::WSW;
0143     m_windDirections["N/A"] = WeatherData::DirectionNotAvailable;
0144 
0145     m_pressureDevelopments["falling"] = WeatherData::Falling;
0146     m_pressureDevelopments["no change"] = WeatherData::NoChange;
0147     m_pressureDevelopments["steady"] = WeatherData::NoChange;
0148     m_pressureDevelopments["rising"] = WeatherData::Rising;
0149     m_pressureDevelopments["N/A"] = WeatherData::PressureDevelopmentNotAvailable;
0150 
0151     m_visibilityStates["excellent"] = WeatherData::VeryGood;
0152     m_visibilityStates["very good"] = WeatherData::VeryGood;
0153     m_visibilityStates["good"] = WeatherData::Good;
0154     m_visibilityStates["moderate"] = WeatherData::Normal;
0155     m_visibilityStates["poor"] = WeatherData::Poor;
0156     m_visibilityStates["very poor"] = WeatherData::VeryPoor;
0157     m_visibilityStates["fog"] = WeatherData::Fog;
0158     m_visibilityStates["n/a"] = WeatherData::VisibilityNotAvailable;
0159 
0160     m_monthNames["Jan"] = 1;
0161     m_monthNames["Feb"] = 2;
0162     m_monthNames["Mar"] = 3;
0163     m_monthNames["Apr"] = 4;
0164     m_monthNames["May"] = 5;
0165     m_monthNames["Jun"] = 6;
0166     m_monthNames["Jul"] = 7;
0167     m_monthNames["Aug"] = 8;
0168     m_monthNames["Sep"] = 9;
0169     m_monthNames["Oct"] = 10;
0170     m_monthNames["Nov"] = 11;
0171     m_monthNames["Dec"] = 12;
0172 }
0173 
0174 BBCParser::~BBCParser()
0175 {
0176 }
0177 
0178 BBCParser *BBCParser::instance()
0179 {
0180     static BBCParser parser;
0181     return &parser;
0182 }
0183 
0184 void BBCParser::scheduleRead( const QString& path,
0185                               BBCWeatherItem *item,
0186                               const QString& type )
0187 {
0188     ScheduleEntry entry;
0189     entry.path = path;
0190     entry.item = item;
0191     entry.type = type;
0192 
0193     m_scheduleMutex.lock();
0194     m_schedule.push( entry );
0195     m_scheduleMutex.unlock();
0196 
0197     ensureRunning();
0198 }
0199 
0200 bool BBCParser::workAvailable()
0201 {
0202     QMutexLocker locker( &m_scheduleMutex );
0203     return !m_schedule.isEmpty();
0204 }
0205 
0206 void BBCParser::work()
0207 {
0208     m_scheduleMutex.lock();
0209     ScheduleEntry entry = m_schedule.pop();
0210     m_scheduleMutex.unlock();
0211 
0212     QFile file( entry.path );
0213     if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) {
0214         return;
0215     }
0216 
0217     QList<WeatherData> data = read( &file );
0218 
0219     if( !data.isEmpty() && !entry.item.isNull() ) {
0220         if (entry.type == QLatin1String("bbcobservation")) {
0221             entry.item->setCurrentWeather( data.at( 0 ) );
0222         }
0223         else if (entry.type == QLatin1String("bbcforecast")) {
0224             entry.item->addForecastWeather( data );
0225         }
0226 
0227         emit parsedFile();
0228     }
0229 }
0230 
0231 QList<WeatherData> BBCParser::read( QIODevice *device )
0232 {
0233     m_list.clear();
0234     setDevice( device );
0235 
0236     while ( !atEnd() ) {
0237         readNext();
0238 
0239         if ( isStartElement() ) {
0240             if (name() == QLatin1String("rss"))
0241                 readBBC();
0242             else
0243                 raiseError( QObject::tr("The file is not a valid BBC answer.") );
0244         }
0245     }
0246 
0247     return m_list;
0248 }
0249 
0250 void BBCParser::readUnknownElement()
0251 {
0252     Q_ASSERT( isStartElement() );
0253 
0254     while ( !atEnd() ) {
0255         readNext();
0256 
0257         if ( isEndElement() )
0258             break;
0259 
0260         if ( isStartElement() )
0261             readUnknownElement();
0262     }
0263 }
0264 
0265 void BBCParser::readBBC()
0266 {
0267     Q_ASSERT( isStartElement()
0268               && name() == QLatin1String("rss"));
0269               
0270     while( !atEnd() ) {
0271         readNext();
0272         
0273         if( isEndElement() )
0274             break;
0275         
0276         if( isStartElement() ) {
0277             if (name() == QLatin1String("channel"))
0278                 readChannel();
0279             else
0280                 readUnknownElement();
0281         }
0282     }
0283 }
0284 
0285 void BBCParser::readChannel()
0286 {
0287     Q_ASSERT( isStartElement()
0288               && name() == QLatin1String("channel"));
0289 
0290     while( !atEnd() ) {
0291         readNext();
0292         
0293         if( isEndElement() )
0294             break;
0295         
0296         if( isStartElement() ) {
0297             if (name() == QLatin1String("item"))
0298                 readItem();
0299             else
0300                 readUnknownElement();
0301         }
0302     }
0303 }
0304 
0305 void BBCParser::readItem()
0306 {
0307     Q_ASSERT( isStartElement()
0308               && name() == QLatin1String("item"));
0309     
0310     WeatherData item;
0311     
0312     while( !atEnd() ) {
0313         readNext();
0314         
0315         if( isEndElement() )
0316             break;
0317         
0318         if( isStartElement() ) {
0319             if (name() == QLatin1String("description"))
0320                 readDescription( &item );
0321             else if(name() == QLatin1String("title"))
0322                 readTitle( &item );
0323             else if (name() == QLatin1String("pubDate"))
0324                 readPubDate( &item );
0325             else
0326                 readUnknownElement();
0327         }
0328     }
0329     
0330     m_list.append( item );
0331 }
0332 
0333 void BBCParser::readDescription( WeatherData *data )
0334 {
0335     Q_ASSERT( isStartElement()
0336               && name() == QLatin1String("description"));
0337         
0338     while( !atEnd() ) {
0339         readNext();
0340         
0341         if( isEndElement() )
0342             break;
0343         
0344         if( isStartElement() ) {
0345             readUnknownElement();
0346         }
0347         
0348         if( isCharacters() ) {
0349             QString description = text().toString();
0350             QRegExp regExp;
0351             
0352             // Temperature
0353             regExp.setPattern( "(Temperature:\\s*)(-?\\d+)(.C)" );
0354             int pos = regExp.indexIn( description );
0355             if ( pos > -1 ) {
0356                 QString value = regExp.cap( 2 );
0357                 data->setTemperature( value.toDouble(), WeatherData::Celsius );
0358             }
0359 
0360             // Max Temperature
0361             regExp.setPattern( "(Max Temp:\\s*)(-?\\d+)(.C)" );
0362             pos = regExp.indexIn( description );
0363             if ( pos > -1 ) {
0364                 QString value = regExp.cap( 2 );
0365                 data->setMaxTemperature( value.toDouble(), WeatherData::Celsius );
0366             }
0367 
0368             // Min Temperature
0369             regExp.setPattern( "(Min Temp:\\s*)(-?\\d+)(.C)" );
0370             pos = regExp.indexIn( description );
0371             if ( pos > -1 ) {
0372                 QString value = regExp.cap( 2 );
0373                 data->setMinTemperature( value.toDouble(), WeatherData::Celsius );
0374             }
0375 
0376             // Wind direction
0377             regExp.setPattern( "(Wind Direction:\\s*)([NESW]+)(,)" );
0378             pos = regExp.indexIn( description );
0379             if ( pos > -1 ) {
0380                 QString wind = regExp.cap( 2 );
0381 
0382                 if ( m_windDirections.contains( wind ) ) {
0383                     data->setWindDirection( m_windDirections.value( wind ) );
0384                 }
0385                 else {
0386                     mDebug() << "UNHANDLED WIND DIRECTION, PLEASE REPORT: " << wind;
0387                 }
0388             }
0389 
0390             // Wind speed
0391             regExp.setPattern( "(Wind Speed:\\s*)(\\d+)(mph)" );
0392             pos = regExp.indexIn( description );
0393             if ( pos > -1 ) {
0394                 QString speed = regExp.cap( 2 );
0395                 data->setWindSpeed( speed.toFloat(), WeatherData::mph );
0396             }
0397 
0398             // Relative Humidity
0399             regExp.setPattern( "(Relative Humidity:\\s*)(\\d+)(.,)" );
0400             pos = regExp.indexIn( description );
0401             if ( pos > -1 ) {
0402                 QString humidity = regExp.cap( 2 );
0403                 data->setHumidity( humidity.toFloat() );
0404             }
0405 
0406             // Pressure
0407             regExp.setPattern( "(Pressure:\\s*)(\\d+mB|N/A)(, )([a-z ]+|N/A)(,)" );
0408             pos = regExp.indexIn( description );
0409             if ( pos > -1 ) {
0410                 QString pressure = regExp.cap( 2 );
0411                 if (pressure != QLatin1String("N/A")) {
0412                     pressure.chop( 2 );
0413                     data->setPressure( pressure.toFloat()/1000, WeatherData::Bar );
0414                 }
0415 
0416                 QString pressureDevelopment = regExp.cap( 4 );
0417 
0418                 if ( m_pressureDevelopments.contains( pressureDevelopment ) ) {
0419                     data->setPressureDevelopment( m_pressureDevelopments.value( pressureDevelopment ) );
0420                 }
0421                 else {
0422                     mDebug() << "UNHANDLED PRESSURE DEVELOPMENT, PLEASE REPORT: "
0423                              << pressureDevelopment;
0424                 }
0425             }
0426 
0427             // Visibility
0428             regExp.setPattern( "(Visibility:\\s*)([^,]+)" );
0429             pos = regExp.indexIn( description );
0430             if ( pos > -1 ) {
0431                 QString visibility = regExp.cap( 2 );
0432 
0433                 if ( m_visibilityStates.contains( visibility.toLower() ) ) {
0434                     data->setVisibilty( m_visibilityStates.value( visibility ) );
0435                 }
0436                 else {
0437                     mDebug() << "UNHANDLED VISIBILITY, PLEASE REPORT: " << visibility;
0438                 }
0439             }
0440         }
0441     }
0442 }
0443 
0444 void BBCParser::readTitle( WeatherData *data )
0445 {
0446     Q_ASSERT( isStartElement()
0447               && name() == QLatin1String("title"));
0448     
0449     while( !atEnd() ) {
0450         readNext();
0451         
0452         if( isEndElement() )
0453             break;
0454         
0455         if( isStartElement() ) {
0456             readUnknownElement();
0457         }
0458         
0459         if( isCharacters() ) {
0460             QString title = text().toString();
0461             QRegExp regExp;
0462             
0463             // Condition
0464             regExp.setPattern( "(^.*)(:\\s*)([\\w ]+)([\\,\\.]\\s*)" );
0465             int pos = regExp.indexIn( title );
0466             if ( pos > -1 ) {
0467                 QString value = regExp.cap( 3 );
0468                 
0469                 if( m_dayConditions.contains( value ) ) {
0470                     // TODO: Switch for day/night
0471                     data->setCondition( m_dayConditions.value( value ) );
0472                 }
0473                 else {
0474                     mDebug() << "UNHANDLED BBC WEATHER CONDITION, PLEASE REPORT: " << value;
0475                 }
0476 
0477                 QString dayString = regExp.cap( 1 );
0478                 Qt::DayOfWeek dayOfWeek = (Qt::DayOfWeek) 0;
0479                 if (dayString.contains(QLatin1String("Monday"))) {
0480                     dayOfWeek = Qt::Monday;
0481                 } else if (dayString.contains(QLatin1String("Tuesday"))) {
0482                     dayOfWeek = Qt::Tuesday;
0483                 } else if (dayString.contains(QLatin1String("Wednesday"))) {
0484                     dayOfWeek = Qt::Wednesday;
0485                 } else if (dayString.contains(QLatin1String("Thursday"))) {
0486                     dayOfWeek = Qt::Thursday;
0487                 } else if (dayString.contains(QLatin1String("Friday"))) {
0488                     dayOfWeek = Qt::Friday;
0489                 } else if (dayString.contains(QLatin1String("Saturday"))) {
0490                     dayOfWeek = Qt::Saturday;
0491                 } else if (dayString.contains(QLatin1String("Sunday"))) {
0492                     dayOfWeek = Qt::Sunday;
0493                 }
0494                 QDate date = QDate::currentDate();
0495                 date = date.addDays( -1 );
0496 
0497                 for ( int i = 0; i < 7; i++ ) {
0498                     if ( date.dayOfWeek() == dayOfWeek ) {
0499                         data->setDataDate( date );
0500                     }
0501                     date = date.addDays( 1 );
0502                 }
0503             }
0504         }
0505     }
0506 }
0507 
0508 void BBCParser::readPubDate( WeatherData *data )
0509 {
0510     Q_ASSERT( isStartElement()
0511               && name() == QLatin1String("pubDate"));
0512 
0513     while( !atEnd() ) {
0514         readNext();
0515 
0516         if( isEndElement() )
0517             break;
0518 
0519         if( isStartElement() ) {
0520             readUnknownElement();
0521         }
0522 
0523         if( isCharacters() ) {
0524             QString pubDate = text().toString();
0525             QRegExp regExp;
0526 
0527             regExp.setPattern( "([A-Za-z]+,\\s+)(\\d+)(\\s+)([A-Za-z]+)(\\s+)(\\d{4,4})(\\s+)(\\d+)(:)(\\d+)(:)(\\d+)(\\s+)([+-])(\\d{2,2})(\\d{2,2})" );
0528             int pos = regExp.indexIn( pubDate );
0529             if ( pos > -1 ) {
0530                 QDateTime dateTime;
0531                 QDate date;
0532                 QTime time;
0533 
0534                 dateTime.setTimeSpec( Qt::UTC );
0535                 date.setDate( regExp.cap( 6 ).toInt(),
0536                              m_monthNames.value( regExp.cap( 4 ) ),
0537                              regExp.cap( 2 ).toInt() );
0538                 time.setHMS( regExp.cap( 8 ).toInt(),
0539                              regExp.cap( 10 ).toInt(),
0540                              regExp.cap( 12 ).toInt() );
0541 
0542                 dateTime.setDate( date );
0543                 dateTime.setTime( time );
0544 
0545                 // Timezone
0546                 if (regExp.cap(14) == QLatin1String("-")) {
0547                     dateTime = dateTime.addSecs( 60*60*regExp.cap( 15 ).toInt() );
0548                     dateTime = dateTime.addSecs( 60   *regExp.cap( 16 ).toInt() );
0549                 }
0550                 else {
0551                     dateTime = dateTime.addSecs( -60*60*regExp.cap( 15 ).toInt() );
0552                     dateTime = dateTime.addSecs( -60   *regExp.cap( 16 ).toInt() );
0553                 }
0554 
0555                 data->setPublishingTime( dateTime );
0556             }
0557         }
0558     }
0559 }
0560 
0561 #include "moc_BBCParser.cpp"