Java tutorial
/* * substitution-schedule-parser - Java library for parsing schools' substitution schedules * Copyright (c) 2017 Johan v. Forstner * * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ package me.vertretungsplan.parser; import me.vertretungsplan.exception.CredentialInvalidException; import me.vertretungsplan.objects.Substitution; import me.vertretungsplan.objects.SubstitutionSchedule; import me.vertretungsplan.objects.SubstitutionScheduleData; import me.vertretungsplan.objects.SubstitutionScheduleDay; import org.apache.http.client.HttpResponseException; import org.jetbrains.annotations.NotNull; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.json.JSONException; import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.parser.Parser; import org.jsoup.select.Elements; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; /** * Parser for the mobile version of substitution schedules created with the <a href="http://indiware.de/">Indiware</a> * software. * <p> * This parser can be accessed using <code>"indiware-mobile"</code> for {@link SubstitutionScheduleData#setApi(String)}. * * <h4>Configuration parameters</h4> * These parameters can be supplied in {@link SubstitutionScheduleData#setData(JSONObject)} to configure the parser: * * <dl> * <dt><code>baseurl</code> (Array of Strings, required)</dt> * <dd>Base URL of the Indiware mobile schedule.</dd> * * </dl> * * Additionally, this parser supports the parameters specified in {@link LoginHandler} for login-protected schedules. */ public class IndiwareMobileParser extends BaseParser { private static final String PARAM_BASEURL = "baseurl"; private static final int MAX_DAYS = 7; private JSONObject data; public IndiwareMobileParser(SubstitutionScheduleData scheduleData, CookieProvider cookieProvider) { super(scheduleData, cookieProvider); data = scheduleData.getData(); } @Override public SubstitutionSchedule getSubstitutionSchedule() throws IOException, JSONException, CredentialInvalidException { new LoginHandler(scheduleData, credential, cookieProvider).handleLogin(executor, cookieStore); String baseurl = data.getString(PARAM_BASEURL) + "/"; List<Document> docs = new ArrayList<>(); HttpResponseException lastException = null; for (int i = 0; i < MAX_DAYS; i++) { LocalDate date = LocalDate.now().plusDays(i); String dateStr = DateTimeFormat.forPattern("yyyyMMdd").print(date); String filePrefix = scheduleData.getType() == SubstitutionSchedule.Type.TEACHER ? "PlanLe" : "PlanKl"; String url = baseurl + "mobdaten/" + filePrefix + dateStr + "" + ".xml?_=" + System.currentTimeMillis(); try { String xml = httpGet(url, "UTF-8"); Document doc = Jsoup.parse(xml, url, Parser.xmlParser()); if (doc.select("kopf datei").text().equals(filePrefix + dateStr + ".xml")) { docs.add(doc); } } catch (HttpResponseException e) { lastException = e; } } if (docs.size() == 0 && lastException != null) { throw lastException; } SubstitutionSchedule v = SubstitutionSchedule.fromData(scheduleData); for (Document doc : docs) { v.addDay(parseDay(doc, colorProvider, scheduleData)); } v.setClasses(getAllClasses()); v.setTeachers(getAllTeachers()); v.setWebsite(baseurl + "plankl.html"); return v; } static SubstitutionScheduleDay parseDay(Document doc, ColorProvider colorProvider, SubstitutionScheduleData scheduleData) { SubstitutionScheduleDay day = new SubstitutionScheduleDay(); day.setDate(ParserUtils.parseDate(doc.select("Kopf > DatumPlan").text())); day.setLastChange(ParserUtils.parseDateTime(doc.select("Kopf > Zeitstempel").text())); for (Element klasse : doc.select("Klassen > Kl")) { String className = klasse.select("Kurz").first().text(); HashSet<String> classes = new HashSet<>(); classes.add(className); for (Element lesson : klasse.select("Pl > Std")) { if (lesson.select("If:not(:empty), Le[LeAe], Ra[RaAe], Fa[FaAe]").size() == 0) { continue; } Substitution subst = new Substitution(); subst.setLesson(text(lesson.select("St"))); if (scheduleData.getType() == SubstitutionSchedule.Type.STUDENT) { subst.setTeachers(split(text(lesson.select("Le")))); subst.setClasses(classes); } else { subst.setClasses(split(text(lesson.select("Le")))); subst.setTeachers(classes); } subst.setSubject(text(lesson.select("Fa"))); subst.setRoom(text(lesson.select("Ra"))); IndiwareParser.handleDescription(subst, text(lesson.select("If")), scheduleData.getType() == SubstitutionSchedule.Type.TEACHER); if (subst.getType() == null) subst.setType("Vertretung"); subst.setColor(colorProvider.getColor(subst.getType())); day.addSubstitution(subst); } } for (Element info : doc.select("ZusatzInfo > ZiZeile")) { day.getMessages().add(info.text()); } return day; } @NotNull private static HashSet<String> split(String text) { if (text != null) { return new HashSet<>(Arrays.asList(text.split(","))); } else { return new HashSet<>(); } } private static String text(Elements elem) { String text = elem.text().replace(" ", "").trim(); if (!text.isEmpty()) { return text; } else { return null; } } @Override public List<String> getAllClasses() throws IOException, JSONException, CredentialInvalidException { if (scheduleData.getType() == SubstitutionSchedule.Type.STUDENT) { return parseClasses("PlanKl"); } else { return new ArrayList<>(); } } @NotNull private List<String> parseClasses(String filePrefix) throws JSONException, IOException, CredentialInvalidException { String baseurl = data.getString(PARAM_BASEURL) + "/"; HttpResponseException lastException = null; for (int i = -4; i < MAX_DAYS; i++) { LocalDate date = LocalDate.now().plusDays(i); String dateStr = DateTimeFormat.forPattern("yyyyMMdd").print(date); String url = baseurl + "mobdaten/" + filePrefix + dateStr + ".xml?_=" + System.currentTimeMillis(); try { String xml = httpGet(url, "UTF-8"); Document doc = Jsoup.parse(xml, url, Parser.xmlParser()); List<String> classes = new ArrayList<>(); for (Element klasse : doc.select("Klassen > Kl")) { classes.add(klasse.select("Kurz").first().text()); } return classes; } catch (HttpResponseException e) { lastException = e; } } if (lastException != null) { throw lastException; } else { return new ArrayList<>(); } } @Override public List<String> getAllTeachers() throws IOException, JSONException, CredentialInvalidException { if (scheduleData.getType() == SubstitutionSchedule.Type.TEACHER) { return parseClasses("PlanLe"); } else { return null; } } }