I'm working on an app for my school (not homework, an app that's going to be used by students), that's supposed to display our week schedules. I get the data from a webapp, but it has no API that I can use. So I have to parse the HTML. This HTML is a serious mess (and on top of that, they use Finnish in their pages), and it seems my code has become such a mess too. I'd like to simplify this code, but I don't know jsoup well enough to use little tricks that make code simple. Most of the code is probably fine, but I'm concerned about the parseRows()
method.
It's an android app, and I use jsoup to parse the HTML. It's an AsyncTask, the compiler required me to do that. I've translated all variable and function names, so they might be inaccurate descriptions. In dutch they seem fine to me though. I also hope the comments tell enough about the code to be decipherable.
Here is an example page that I parse. And here is the relevant part of the HTML, with a cleaner structure, on pastebin since the HTML was too long.
And here is the relevant code:
public class DownloadScheduleTask extends AsyncTask<String, Void, Void>
{
private LesroostersActivity activity;
private Schedule schedule;
private String debugTag = "Pxl App";
public DownloadLesroosterTask(LesroostersActivity activity)
{
//keep a reference to the activity
this.activity = activity;
}
@Override
protected Void doInBackground(String... URLs)
{
//doInBrackground required varargs parameter, but I only ever pass one URL, so I select the first.
String URL = URLs[0];
try
{
//Download the HTML document.
Document document = Jsoup.connect(URL).get();
schedule = new Schedule();
//Get the first day of this week out of the table
SimpleDateTime firstDay = SimpleDateTime.parseDate(document.select("table th span.hdr_date font").first().text());
schedule.setFirstDay(firstDay);
//Get all <tr> elements from the table
Elements rows = document.select("table.asio_basic > tbody > tr");
//Parse rows to elements
ArrayList<IndexedElement> elements = parseRows(rows);
//Parse elements to get their data
parseElements(elements);
}
catch (IOException e)
{
//Show an error message in the activity
activity.showError("Error", "Something went wrong while downloading the schedule. Please try again");
e.printStackTrace();
}
//Nothing to return here, but doInBackground requires a return statement.
return null;
}
protected void onPostExecute(Void result)
{
//Let activity know we are done.
activity.ontvangLesrooster(schedule);
}
private ArrayList<IndexedElement> parseRows(Elements rows)
{
//Offsets are used to get the right element when rowspans are used.
int[] offsets = new int[rows.size()];
//The array to return
ArrayList<IndexedElement> elements = new ArrayList<IndexedElement>();
//We need to parse the table per column, because there is no other way to know to which day a course belongs.
//For each column (1 column is 1 day)
for (int i = 0; i < rows.get(0).children().size(); i++)
{
int lesIndex = 0;
//For each row (1 row is 30 minutes)
for (int j = 0; j < rows.size(); j++)
{
//Get the correct cell. 'offsets' shifts our index to the left, so that we read from the correct column after reading a rowspan.
Element cell = rows.get(j).child(i + offsets[j]);
//If the cell has a rowspan
if (cell.hasAttr("rowspan"))
{
//First get the size of the rowspan (e.g. rowspan="4")
int rowspan = Integer.parseInt(cell.attr("rowspan"));
//Then for each row affected by the rowspan, change its offset
for (int k = 1; k < rowspan; k++)
{
offsets[j + k]--;
}
//Then add rowspan to our index to skip nonexistent cells
j += rowspan - 1;
//Finally add the cell to the array. i - 1 is a correction for the first column.
elements.add(new IndexedElement(cell, i - 1, lesIndex++));
}
}
}
return elements;
}
private void parseElements(ArrayList<IndexedElement> elements)
{
for (IndexedElement e : elements)
{
//This selects "[beginHour] - [endHour]<br />[courseName]"
String timeAndNameHTML = e.getElement().select("b").html();
//There are some cells without data (remember that we added all cells with a rowspan), so we have to filter those out first
if (!timeAndName.equals(""))
{
//Selects two strings. 1) "[beginHour] - [endHour]" 2) "[courseName]"
String[] parts = timeAndNameHTML.split("<br />");
//Selects two strings. 1) "[beginHour]" 2) [endHour]"
String[] times = parts[0].split(" - ");
//Parse the time string to a SimpleDateTime
SimpleDateTime beginHour = new SimpleDateTime(SimpleDateTime.parseTime(times[0]));
//Set the day of beginHour to the first day of the week + the dayIndex we are at now.
beginHour.setDay(schedule.getFirstDay().getDay() + e.getDay());
//Identical to previous 2 lines, but for endHour
SimpleDateTime endHour = new SimpleDateTime(SimpleDateTime.parseTime(times[1]));
endHour.setDay(schedule.getFirstDay().getDay() + e.getDay());
//Selects the string "[courseName]"
String courseName = parts[1];
//Selects the string "<b>[beginHour] - [endHour]<br />[courseName]</b><br />[classroom]<br/>"
String classRoomHMTL = e.getElement().select("font").html();
//Selects three strings. 1) "<b>[beginHour] - [endHour]" 2) "[courseName]</b>" 3) "[classroom]"
String[] classRoomParts = classRoomHMTL.split("<br />");
String classRoom = "";
//It's possible that a course doesn't have a classroom, in that case the classRoom string will stay empty.
if (classRoomParts.length >= 3)
{
classRoom = classRoomParts[2];
}
//selects the name of the teacher
String leerkrachtHTML = e.getElement().select("tr > td").first().ownText();
//Put all data in the schedule.
schedule.addCours(new Course(courseName, classRoom, teacher, beginHour, endHour));
}
}
}
//This class stores an Element along with two indexes. One for the day of the course. The second for the course index of that day.
private class IndexedElement
{
private Element element;
private int dayIndex;
private int courseIndex;
public IndexedElement(Element element, int day, int course)
{
this.setElement(element);
this.setDay(day);
this.setCourse(course);
}
public Element getElement() {
return element;
}
public void setElement(Element element) {
this.element = element;
}
public int getDay() {
return dayIndex;
}
public void setDay(int day) {
this.dayIndex = day;
}
public int getCourse() {
return courseIndex;
}
public void setCourse(int course) {
this.courseIndex = course;
}
}
}