Parse remaiing fields for departures/arrivals
[quandoparte] / application / stationschedulemodel.cpp
1 /*
2
3 Copyright (C) 2011 Luciano Montanaro <mikelima@cirulla.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING.  If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19
20 */
21
22 #include "stationschedulemodel.h"
23
24 #include "dataprovider.h"
25
26 #include <QDebug>
27 #include <QWebElement>
28 #include <QWebFrame>
29 #include <QWebPage>
30 StationScheduleModel::StationScheduleModel(const QString &name, QObject *parent) :
31     QAbstractListModel(parent),
32     m_name(name)
33
34 {
35     DataProvider *provider = DataProvider::instance();
36     QHash<int, QByteArray> roles;
37     roles[TrainRole] = "train";
38     roles[DepartureStationRole] = "departureStation";
39     roles[DepartureTimeRole] = "departureTime";
40     roles[ArrivalStationRole] = "arrivalStation";
41     roles[ArrivalTimeRole] = "ArrivalTime";
42     roles[DetailsUrlRole] = "DetailsUrl";
43     roles[DelayRole] = "delay";
44     roles[DelayClassRole] = "delayClassRole";
45     setRoleNames(roles);
46
47     connect(provider, SIGNAL(stationScheduleReady(QByteArray,QUrl)),
48             this, SLOT(parse(QByteArray,QUrl)));
49 }
50
51 QString & StationScheduleModel::name()
52 {
53     return m_name;
54 }
55
56 void StationScheduleModel::setName(const QString &name)
57 {
58     if (name != m_name) {
59         m_name = name;
60         emit nameChanged();
61     }
62 }
63
64 static void parseDelayClass(const QWebElement &element, StationScheduleItem &item)
65 {
66     if (!element.isNull()) {
67         QWebElement image = element.findFirst("img");
68         if (!image.isNull()) {
69             int delayClass = 42;
70             QString imageName = image.attribute("src");
71             if (!imageName.isEmpty()) {
72                 QRegExp delayClassRegexp("pallinoRit([0-9])\\.png");
73                 int pos = delayClassRegexp.indexIn(imageName);
74                 qDebug() << "regexp matched at pos:" << pos << "match:" << delayClassRegexp.cap(0);
75                 delayClass =  (pos >= 0) ? delayClassRegexp.cap(1).toInt() : 0;
76             }
77             item.setDelayClass(delayClass);
78         } else {
79             qDebug() << "img not found";
80         }
81     } else {
82         qDebug() << "div.bloccotreno not found";
83     }
84 }
85
86 static void parseDetailsUrl(const QWebElement &element, StationScheduleItem &item)
87 {
88     if (!element.isNull()) {
89         QWebElement link = element.findFirst("a");
90         QString url = link.attribute("href");
91         item.setDetailsUrl(url);
92     } else {
93         qDebug() << "link not found";
94     }
95 }
96
97 static void parseTrain(const QString &text, StationScheduleItem &item)
98 {
99     QRegExp filter("^(Per|Da) (.*)\\n"
100                    "Delle ore (.*)\n"
101                    "Binario Previsto: (.*)\n"
102                    "Binario Reale: (.*)\n"
103                    "(.*)$");
104     int pos = filter.indexIn(text);
105     if (pos >= 0) {
106         if (filter.cap(1) == "Per") {
107             item.setDepartureStation(filter.cap(2));
108             item.setDepartureTime(filter.cap(3));
109         } else {
110             item.setArrivalStation(filter.cap(2));
111             item.setArrivalTime(filter.cap(3));
112         }
113         item.setDelay(filter.cap(6));
114     } else {
115         qDebug() << "could not parse" << text;
116     }
117 }
118
119 StationScheduleItem parseResult(const QWebElement &result)
120 {
121     StationScheduleItem item;
122
123     QWebElement current = result.findFirst("h2");
124     if (!current.isNull()) {
125         item.setTrain(current.toPlainText());
126     }
127     parseDetailsUrl(result, item);
128     current = result.findFirst("div.bloccotreno");
129     parseDelayClass(current, item);
130     QString rawText = current.toPlainText();
131     parseTrain(rawText, item);
132
133     qDebug() << "train:" << item.train();
134     qDebug() << "delayClass:" << item.delayClass();
135     qDebug() << "detailsUrl:" << item.detailsUrl();
136     qDebug() << "departureStation:" << item.departureStation();
137     qDebug() << "departureTime:" << item.departureTime();
138     qDebug() << "arrivalStation:" << item.arrivalStation();
139     qDebug() << "arrivalTime:" << item.arrivalTime();
140     qDebug() << "delay:" << item.delay();
141     return item;
142 }
143
144 void StationScheduleModel::parse(const QByteArray &htmlReply, const QUrl &baseUrl)
145 {
146     Q_UNUSED(baseUrl);
147     qDebug() << "--- start of query result --- cut here ------";
148     qDebug() << QString::fromUtf8(htmlReply.constData());
149     qDebug() << "--- end of query result ----- cut here ------";
150
151     emit layoutAboutToBeChanged();
152     beginInsertRows(QModelIndex(), 0, 0);
153     QWebPage page;
154     page.mainFrame()->setContent(htmlReply, "text/html", baseUrl);
155     QWebElement doc = page.mainFrame()->documentElement();
156
157     // Find the first div
158     QWebElement current = doc.findFirst("div");
159
160     QStringList departures, arrivals;
161     qDebug() << "skipping to the departures";
162     // Skip to the first div of class corpocentrale, which contains the first
163     // departure-related contents
164     while (!current.classes().contains("corpocentrale")) {
165         current = current.nextSibling();
166         qDebug() << "skipping to the next element";
167         if (current.isNull())
168             break;
169     }
170     // Mark every div as a departure class element; the next corpocentrale
171     // marks the start of the arrivals section
172     qDebug() << "marking departures";
173     do {
174         if (current.classes().contains("bloccorisultato")) {
175             departures << current.toPlainText();
176             StationScheduleItem schedule = parseResult(current);
177             if (schedule.isValid()) {
178                 m_schedules << schedule;
179             }
180         }
181         current = current.nextSibling();
182         qDebug() << "marking as departures";
183         if (current.isNull())
184             break;
185     } while (!current.classes().contains("corpocentrale"));
186
187     // Mark everything as an arrival, until reaching the footer
188     while (!current.classes().contains("footer")) {
189         if (current.classes().contains("bloccorisultato")) {
190             arrivals << current.toPlainText();
191         }
192         current = current.nextSibling();
193         qDebug() << "marking as arrival";
194         if (current.isNull())
195             break;
196     }
197
198     qDebug() << "departures list contain:";
199     qDebug() << departures;
200     //qDebug() << "arrivals list contain:";
201     //qDebug() << arrivals;
202     endInsertRows();
203     emit layoutChanged();
204 }
205
206 void StationScheduleModel::fetch(const QString &name)
207 {
208     DataProvider *provider = DataProvider::instance();
209
210     provider->fetchStationSchedule(name);
211     setName(name);
212 }
213
214 int StationScheduleModel::rowCount(const QModelIndex &parent) const
215 {
216     qDebug() << "schedule.count" << m_schedules.count();
217     return m_schedules.count();
218 }
219
220 QVariant StationScheduleModel::data(const QModelIndex &index, int role) const
221 {
222     qDebug() << "getting data for role" << role;
223     if (!index.isValid()) {
224         return QVariant();
225     }
226     if (index.row() >= m_schedules.count()) {
227         return QVariant();
228     }
229     StationScheduleItem item = m_schedules[index.row()];
230     switch (role) {
231     case Qt::DisplayRole:
232     case TrainRole:
233         return QVariant::fromValue(item.train());
234     case DepartureStationRole:
235         return QVariant::fromValue(item.departureStation());
236     case DepartureTimeRole:
237         return QVariant::fromValue(item.departureTime());
238     case ArrivalStationRole:
239         return QVariant::fromValue(item.arrivalStation());
240     case ArrivalTimeRole:
241         return QVariant::fromValue(item.arrivalTime());
242     case DetailsUrlRole:
243         return QVariant::fromValue(item.detailsUrl());
244     case DelayRole:
245         return QVariant::fromValue(item.delay());
246     case DelayClassRole:
247         return QVariant::fromValue(item.delayClass());
248     default:
249         return QVariant::fromValue(QString("Unknown role requested"));
250     }
251 }