switch Austrian departures to JSON

git-svn-id: https://public-transport-enabler.googlecode.com/svn/trunk@181 0924bc21-9374-b0fa-ee44-9ff1593b38f0
This commit is contained in:
andreas.schildbach 2010-09-23 11:14:46 +00:00
parent e856c0a270
commit f8ebbc4054
3 changed files with 280 additions and 309 deletions

58
pom.xml Normal file
View file

@ -0,0 +1,58 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.schildbach.pte</groupId>
<artifactId>public-transport-enabler</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20090211</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<testResources>
<testResource>
<directory>test</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -4,27 +4,27 @@ import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import de.schildbach.pte.QueryDeparturesResult.Status;
public class OebbProvider implements NetworkProvider
{
public static final String NETWORK_ID = "fahrplan.oebb.at";
private static final long PARSER_DAY_ROLLOVER_THRESHOLD_MS = 12 * 60 * 60 * 1000;
public boolean hasCapabilities(final Capability... capabilities)
{
for (final Capability capability : capabilities)
if (capability == Capability.DEPARTURES || capability == Capability.CONNECTIONS || capability == Capability.LOCATION_STATION_ID)
if (capability == Capability.DEPARTURES || capability == Capability.LOCATION_STATION_ID)
return true;
return false;
@ -462,142 +462,99 @@ public class OebbProvider implements NetworkProvider
public String departuresQueryUri(final String stationId, final int maxDepartures)
{
final DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
final Date now = new Date();
final StringBuilder uri = new StringBuilder();
uri.append("http://fahrplan.oebb.at/bin/stboard.exe/dn");
uri.append("?input=").append(stationId);
uri.append("http://fahrplan.oebb.at/bin/stboard.exe/dn?L=vs_scotty.vs_stb");
uri.append("&input=").append(stationId);
uri.append("&boardType=dep");
uri.append("&time=").append(TIME_FORMAT.format(now));
uri.append("&productsFilter=111111111111");
if (maxDepartures != 0)
uri.append("&maxJourneys=").append(maxDepartures);
uri.append("&disableEquivs=yes"); // don't use nearby stations
uri.append("&additionalTime=0");
uri.append("&maxJourneys=20");
uri.append("&start=yes");
uri.append("&selectDate=today");
uri.append("&monitor=1");
uri.append("&requestType=0");
uri.append("&view=preview");
uri.append("&disableEquivs=yes"); // don't use nearby stations
return uri.toString();
}
private static final Pattern P_DEPARTURES_HEAD_COARSE = Pattern.compile(".*?" //
+ "(?:" //
+ "<table class=\"hafasResult\"[^>]*>(.+?)</table>.*?" //
+ "(?:<table cellspacing=\"0\" class=\"hafasResult\"[^>]*>(.+?)</table>|(verkehren an dieser Haltestelle keine))"//
+ "|(Eingabe kann nicht interpretiert)|(Verbindung zum Server konnte leider nicht hergestellt werden))" //
+ ".*?" //
, Pattern.DOTALL);
private static final Pattern P_DEPARTURES_HEAD_FINE = Pattern.compile(".*?" //
+ "<td class=\"querysummary screennowrap\">\\s*(.*?)\\s*<a.*?" // location
+ "(\\d{2}\\.\\d{2}\\.\\d{2}).*?" // date
+ "Abfahrt (\\d+:\\d+).*?" // time
+ "%23(\\d+)&.*?" // locationId
, Pattern.DOTALL);
private static final Pattern P_DEPARTURES_COARSE = Pattern.compile("<tr class=\"depboard-(\\w*)\">\n(.*?)</tr>", Pattern.DOTALL);
static final Pattern P_DEPARTURES_FINE = Pattern.compile("" //
+ "<td class=\"[\\w ]*\">(\\d{1,2}:\\d{2})</td>\n.*?" // plannedTime
+ "(?:<td class=\"[\\w ]*?prognosis[\\w ]*?\">\n" //
+ "(?:&nbsp;|<span class=\"rtLimit\\d\">(p&#252;nktlich|\\d{1,2}:\\d{2})</span>)\n</td>\n" // predictedTime
+ ")?.*?" //
+ "<img src=\"/img/vs_oebb/(\\w+)_pic\\.gif\"\\s+alt=\"[^\"]*\">\\s*(.*?)\\s*</.*?" // type, line
+ "<span class=\"bold\">\n" //
+ "<a href=\"http://fahrplan\\.oebb\\.at/bin/stboard\\.exe/dn\\?ld=web\\d+&input=[^%]*?(?:%23(\\d+))?&.*?\">" // destinationId
+ "\\s*(.*?)\\s*</a>\n" // destination
+ "</span>.*?" //
+ "(?:<td class=\"center sepline top\">\n(" + ParserUtils.P_PLATFORM + ").*?)?" // position
, Pattern.DOTALL);
public QueryDeparturesResult queryDepartures(final String uri) throws IOException
{
// scrape page
final CharSequence page = ParserUtils.scrape(uri);
final String page = ParserUtils.scrape(uri).toString().substring(14);
// parse page
final Matcher mHeadCoarse = P_DEPARTURES_HEAD_COARSE.matcher(page);
if (mHeadCoarse.matches())
try
{
// messages
if (mHeadCoarse.group(3) != null)
return new QueryDeparturesResult(uri, Status.NO_INFO);
else if (mHeadCoarse.group(4) != null)
final JSONObject head = new JSONObject(page);
final String location = ParserUtils.resolveEntities(head.getString("stationName"));
final int locationId = head.optInt("stationEvaId", -1);
// final boolean rt = head.optBoolean("rtInfo");
if (locationId == -1)
return new QueryDeparturesResult(uri, Status.INVALID_STATION);
else if (mHeadCoarse.group(5) != null)
return new QueryDeparturesResult(uri, Status.SERVICE_DOWN);
final Matcher mHeadFine = P_DEPARTURES_HEAD_FINE.matcher(mHeadCoarse.group(1));
if (mHeadFine.matches())
final List<Departure> departures = new ArrayList<Departure>(8);
final JSONArray aDeparture = head.optJSONArray("journey");
if (aDeparture != null)
{
final String location = ParserUtils.resolveEntities(mHeadFine.group(1));
final Date currentTime = ParserUtils.joinDateTime(ParserUtils.parseDate(mHeadFine.group(2)), ParserUtils
.parseTime(mHeadFine.group(3)));
final int stationId = Integer.parseInt(mHeadFine.group(4));
final List<Departure> departures = new ArrayList<Departure>(8);
String oldZebra = null;
final Matcher mDepCoarse = P_DEPARTURES_COARSE.matcher(mHeadCoarse.group(2));
while (mDepCoarse.find())
for (int i = 0; i < aDeparture.length(); i++)
{
final String zebra = mDepCoarse.group(1);
if (oldZebra != null && zebra.equals(oldZebra))
throw new IllegalArgumentException("missed row? last:" + zebra);
else
oldZebra = zebra;
final Matcher mDepFine = P_DEPARTURES_FINE.matcher(mDepCoarse.group(2));
if (mDepFine.matches())
final JSONObject departure = aDeparture.optJSONObject(i);
if (departure != null)
{
final Calendar current = new GregorianCalendar();
current.setTime(currentTime);
final Calendar parsed = new GregorianCalendar();
parsed.setTime(ParserUtils.parseTime(mDepFine.group(1)));
parsed.set(Calendar.YEAR, current.get(Calendar.YEAR));
parsed.set(Calendar.MONTH, current.get(Calendar.MONTH));
parsed.set(Calendar.DAY_OF_MONTH, current.get(Calendar.DAY_OF_MONTH));
if (ParserUtils.timeDiff(parsed.getTime(), currentTime) < -PARSER_DAY_ROLLOVER_THRESHOLD_MS)
parsed.add(Calendar.DAY_OF_MONTH, 1);
final Date time = ParserUtils.joinDateTime(ParserUtils.parseDate(departure.getString("da")), ParserUtils.parseTime(departure
.getString("ti")));
final String line = normalizeLine(departure.getString("pr"));
final String destination = ParserUtils.resolveEntities(departure.getString("st"));
String position = departure.optString("tr");
if (position != null)
position = "Gl. " + position;
final boolean rt = head.optBoolean("rt", false);
if (rt == true)
System.out.println("Realtime!!");
final String lineLink = departure.optString("tinfoline");
final Date plannedTime = parsed.getTime();
Date predictedTime = null;
final String prognosis = ParserUtils.resolveEntities(mDepFine.group(2));
if (prognosis != null)
{
if (prognosis.equals("pünktlich"))
predictedTime = plannedTime;
else
predictedTime = ParserUtils.joinDateTime(currentTime, ParserUtils.parseTime(prognosis));
}
final String lineType = mDepFine.group(3);
final String line = normalizeLine(lineType, ParserUtils.resolveEntities(mDepFine.group(4)));
final int destinationId = mDepFine.group(5) != null ? Integer.parseInt(mDepFine.group(5)) : 0;
final String destination = ParserUtils.resolveEntities(mDepFine.group(6));
final String position = mDepFine.group(7) != null ? "Gl. " + ParserUtils.resolveEntities(mDepFine.group(7)) : null;
final Departure dep = new Departure(plannedTime, predictedTime, line, line != null ? LINES.get(line.charAt(0)) : null, null,
position, destinationId, destination, null);
if (!departures.contains(dep))
departures.add(dep);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(2) + "' on " + uri);
departures.add(new Departure(!rt ? time : null, rt ? time : null, line, line != null ? LINES.get(line.charAt(0)) : null,
lineLink, position, 0, destination, null));
}
}
}
return new QueryDeparturesResult(uri, stationId, location, currentTime, departures);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mHeadCoarse.group(1) + "' on " + uri);
}
return new QueryDeparturesResult(uri, locationId, location, null, departures);
}
else
catch (final JSONException x)
{
throw new IllegalArgumentException("cannot parse '" + page + "' on " + uri);
throw new RuntimeException("cannot parse: '" + page + "' on " + uri, x);
}
}
private static final Pattern P_NORMALIZE_LINE = Pattern.compile("([A-Za-zÄÖÜäöüßáàâéèêíìîóòôúùû]+)[\\s-]*(.*)");
private static final Pattern P_NORMALIZE_LINE = Pattern.compile("([A-Za-zÄÖÜäöüßáàâéèêíìîóòôúùû-]+)[\\s]*(.*)");
private static String normalizeLine(final String line)
{
final Matcher m = P_NORMALIZE_LINE.matcher(line);
if (m.matches())
{
final String type = m.group(1);
final String number = m.group(2);
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
return normalizedType + type + number;
throw new IllegalStateException("cannot normalize type " + type + " line " + line);
}
else
{
throw new IllegalStateException("cannot normalize line " + line);
}
}
private static String normalizeLine(final String type, final String line)
{
@ -608,7 +565,8 @@ public class OebbProvider implements NetworkProvider
if (normalizedType != 0)
return normalizedType + strippedLine;
throw new IllegalStateException("cannot normalize type " + type + " line " + line);
return '?' + strippedLine;
// throw new IllegalStateException("cannot normalize type " + type + " line " + line);
}
private static char normalizeType(final String type)
@ -625,176 +583,184 @@ public class OebbProvider implements NetworkProvider
return 'I';
if (ucType.equals("ICE")) // InterCityExpress
return 'I';
if (ucType.equals("X")) // Interconnex
return 'I';
if (ucType.equals("EN")) // EuroNight
return 'I';
if (ucType.equals("CNL")) // CityNightLine
return 'I';
if (ucType.equals("DNZ")) // Berlin-Saratov, Berlin-Moskva
return 'I';
if (ucType.equals("INT")) // Rußland
return 'I';
if (ucType.equals("D")) // Rußland
return 'I';
if (ucType.equals("RR")) // Finnland
return 'I';
if (ucType.equals("TLK")) // Tanie Linie Kolejowe, Polen
return 'I';
if (ucType.equals("EE")) // Rumänien
return 'I';
if (ucType.equals("SC")) // SuperCity, Tschechien
return 'I';
if (ucType.equals("RJ")) // RailJet, Österreichische Bundesbahnen
return 'I';
if (ucType.equals("EST")) // Eurostar Frankreich
return 'I';
if (ucType.equals("ALS")) // Spanien
return 'I';
if (ucType.equals("ARC")) // Spanien
return 'I';
if (ucType.equals("TLG")) // Spanien, Madrid
return 'I';
if (ucType.equals("HOT")) // Spanien, Nacht
return 'I';
if (ucType.equals("AVE")) // Alta Velocidad Española, Spanien
return 'I';
if (ucType.equals("INZ")) // Schweden, Nacht
return 'I';
if (ucType.equals("OZ")) // Schweden, Oeresundzug
return 'I';
if (ucType.equals("X2")) // Schweden
return 'I';
if (ucType.equals("THA")) // Thalys
return 'I';
if (ucType.equals("TGV")) // Train à Grande Vitesse
return 'I';
if (ucType.equals("LYN")) // Dänemark
return 'I';
// if (ucType.equals("X")) // Interconnex
// return 'I';
// if (ucType.equals("EN")) // EuroNight
// return 'I';
// if (ucType.equals("CNL")) // CityNightLine
// return 'I';
// if (ucType.equals("DNZ")) // Berlin-Saratov, Berlin-Moskva
// return 'I';
// if (ucType.equals("INT")) // Rußland
// return 'I';
// if (ucType.equals("D")) // Rußland
// return 'I';
// if (ucType.equals("RR")) // Finnland
// return 'I';
// if (ucType.equals("TLK")) // Tanie Linie Kolejowe, Polen
// return 'I';
// if (ucType.equals("EE")) // Rumänien
// return 'I';
// if (ucType.equals("SC")) // SuperCity, Tschechien
// return 'I';
// if (ucType.equals("RJ")) // RailJet, Österreichische Bundesbahnen
// return 'I';
// if (ucType.equals("EST")) // Eurostar Frankreich
// return 'I';
// if (ucType.equals("ALS")) // Spanien
// return 'I';
// if (ucType.equals("ARC")) // Spanien
// return 'I';
// if (ucType.equals("TLG")) // Spanien, Madrid
// return 'I';
// if (ucType.equals("HOT")) // Spanien, Nacht
// return 'I';
// if (ucType.equals("AVE")) // Alta Velocidad Española, Spanien
// return 'I';
// if (ucType.equals("INZ")) // Schweden, Nacht
// return 'I';
// if (ucType.equals("OZ")) // Schweden, Oeresundzug
// return 'I';
// if (ucType.equals("X2")) // Schweden
// return 'I';
// if (ucType.equals("THA")) // Thalys
// return 'I';
// if (ucType.equals("TGV")) // Train à Grande Vitesse
// return 'I';
// if (ucType.equals("LYN")) // Dänemark
// return 'I';
if (ucType.equals("ARZ")) // Frankreich, Nacht
return 'I';
if (ucType.equals("ES")) // Eurostar Italia
return 'I';
if (ucType.equals("ICN")) // Italien, Nacht
return 'I';
if (ucType.equals("UUU")) // Italien, Nacht
return 'I';
if (ucType.equals("RHI")) // ICE
return 'I';
if (ucType.equals("RHT")) // TGV
return 'I';
if (ucType.equals("TGD")) // TGV
return 'I';
if (ucType.equals("ECB")) // EC
return 'I';
if (ucType.equals("IRX")) // IC
return 'I';
if (ucType.equals("AIR"))
return 'I';
// if (ucType.equals("ES")) // Eurostar Italia
// return 'I';
// if (ucType.equals("ICN")) // Italien, Nacht
// return 'I';
// if (ucType.equals("UUU")) // Italien, Nacht
// return 'I';
// if (ucType.equals("RHI")) // ICE
// return 'I';
// if (ucType.equals("RHT")) // TGV
// return 'I';
// if (ucType.equals("TGD")) // TGV
// return 'I';
// if (ucType.equals("ECB")) // EC
// return 'I';
// if (ucType.equals("IRX")) // IC
// return 'I';
// if (ucType.equals("AIR"))
// return 'I';
//
if (ucType.equals("R"))
return 'R';
if (ucType.equals("REX")) // RegionalExpress
return 'R';
if (ucType.equals("ZUG"))
// if (ucType.equals("ZUG"))
// return 'R';
// if (ucType.equals("EZ")) // Erlebniszug
// return 'R';
// if (ucType.equals("S2")) // Helsinki-Turku
// return 'R';
if (ucType.equals("RB")) // RegionalBahn Deutschland
return 'R';
if (ucType.equals("EZ")) // Erlebniszug
return 'R';
if (ucType.equals("S2")) // Helsinki-Turku
return 'R';
if (ucType.equals("RB")) // RegionalBahn
return 'R';
if (ucType.equals("RE"))
return 'R';
if (ucType.equals("DPN")) // TODO nicht evtl. doch eher ne S-Bahn?
return 'R';
if (ucType.equals("VIA"))
return 'R';
if (ucType.equals("PCC")) // Polen
return 'R';
if (ucType.equals("KM")) // Polen
return 'R';
if (ucType.equals("SKM")) // Polen
return 'R';
if (ucType.equals("SKW")) // Polen
return 'R';
if (ucType.equals("WKD")) // Warszawska Kolej Dojazdowa, Polen
return 'R';
if (ucType.equals("IR")) // Polen
return 'R';
if (ucType.equals("OS")) // Chop-Cierna nas Tisou
return 'R';
if (ucType.equals("SP")) // Polen
return 'R';
if (ucType.equals("EX")) // Polen
return 'R';
if (ucType.equals("E")) // Budapest, Ungarn
return 'R';
if (ucType.equals("IP")) // Ozd, Ungarn
return 'R';
if (ucType.equals("ZR")) // Bratislava, Slovakai
return 'R';
if (ucType.equals("CAT")) // Stockholm-Arlanda, Arlanda Express
return 'R';
if (ucType.equals("RT")) // Deutschland
return 'R';
if (ucType.equals("IRE")) // Interregio Express
return 'R';
if (ucType.equals("N")) // Frankreich, Tours
return 'R';
if (ucType.equals("DPF")) // VX=Vogtland Express
if (ucType.equals("RE")) // RegionalExpress Deutschland
return 'R';
// if (ucType.equals("DPN")) // TODO nicht evtl. doch eher ne S-Bahn?
// return 'R';
// if (ucType.equals("VIA"))
// return 'R';
// if (ucType.equals("PCC")) // Polen
// return 'R';
// if (ucType.equals("KM")) // Polen
// return 'R';
// if (ucType.equals("SKM")) // Polen
// return 'R';
// if (ucType.equals("SKW")) // Polen
// return 'R';
// if (ucType.equals("WKD")) // Warszawska Kolej Dojazdowa, Polen
// return 'R';
// if (ucType.equals("IR")) // Polen
// return 'R';
// if (ucType.equals("OS")) // Chop-Cierna nas Tisou
// return 'R';
// if (ucType.equals("SP")) // Polen
// return 'R';
// if (ucType.equals("EX")) // Polen
// return 'R';
// if (ucType.equals("E")) // Budapest, Ungarn
// return 'R';
// if (ucType.equals("IP")) // Ozd, Ungarn
// return 'R';
// if (ucType.equals("ZR")) // Bratislava, Slovakai
// return 'R';
// if (ucType.equals("CAT")) // Stockholm-Arlanda, Arlanda Express
// return 'R';
// if (ucType.equals("RT")) // Deutschland
// return 'R';
// if (ucType.equals("IRE")) // Interregio Express
// return 'R';
// if (ucType.equals("N")) // Frankreich, Tours
// return 'R';
// if (ucType.equals("DPF")) // VX=Vogtland Express
// return 'R';
if (ucType.equals("S"))
return 'S';
if (ucType.equals("RSB")) // Schnellbahn Wien
return 'S';
if (ucType.equals("RER")) // Réseau Express Régional, Frankreich
return 'S';
// if (ucType.equals("RER")) // Réseau Express Régional, Frankreich
// return 'S';
if (ucType.equals("U"))
return 'U';
if (ucType.equals("STR"))
return 'T';
if (ucType.equals("LKB"))
// if (ucType.equals("LKB"))
// return 'T';
if (ucType.equals("WLB")) // via JSON API
return 'T';
if (ucType.equals("BUS"))
return 'B';
if (ucType.equals("RFB"))
return 'B';
if (ucType.equals("OBU"))
return 'B';
// if (ucType.equals("RFB"))
// return 'B';
// if (ucType.equals("OBU"))
// return 'B';
if (ucType.equals("AST"))
return 'B';
if (ucType.equals("ICB")) // ICBus
if (ucType.equals("ASTSV")) // via JSON API
return 'B';
if (ucType.equals("FB")) // Polen
if (ucType.equals("ICB")) // ÖBB ICBus
return 'B';
if (ucType.equals("BSV")) // Deutschland
// if (ucType.equals("FB")) // Polen
// return 'B';
// if (ucType.equals("BSV")) // Deutschland
// return 'B';
// if (ucType.equals("LT")) // Linien-Taxi
// return 'B';
if (ucType.equals("BUSSV")) // via JSON API
return 'B';
if (ucType.equals("LT")) // Linien-Taxi
if (ucType.equals("O-B")) // Stadtbus, via JSON API
return 'B';
if (ucType.equals("SCH"))
return 'F';
if (ucType.equals("AS")) // SyltShuttle
return 'F';
if (ucType.equals("SB"))
return 'C';
if (ucType.equals("LIF"))
return 'C';
if (ucType.equals("U70")) // U.K.
return '?';
if (ucType.equals("R84"))
return '?';
if (ucType.equals("S84"))
return '?';
if (ucType.equals("T84"))
return '?';
// if (ucType.equals("SCH"))
// return 'F';
// if (ucType.equals("AS")) // SyltShuttle
// return 'F';
//
// if (ucType.equals("SB"))
// return 'C';
// if (ucType.equals("LIF"))
// return 'C';
//
// if (ucType.equals("U70")) // U.K.
// return '?';
// if (ucType.equals("R84"))
// return '?';
// if (ucType.equals("S84"))
// return '?';
// if (ucType.equals("T84"))
// return '?';
return 0;
}

View file

@ -1,53 +0,0 @@
package de.schildbach.pte;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import java.util.regex.Matcher;
import org.junit.Test;
public class OebbProviderTest
{
@Test
public void testDepartures()
{
assertFineDepartures("" //
+ "<td class=\"bold center sepline top\">09:12</td>\n" //
+ "<td class=\"bold top nowrap sepline\">\n" //
+ "<a href=\"http://fahrplan.oebb.at/bin/traininfo.exe/dn/152499/163408/534164/216252/81?ld=web05&date=21.09.10&station_evaId=912101&station_type=dep\"><img src=\"/img/vs_oebb/bus_pic.gif\" alt=\"Bus 16A\"> Bus 16A</a>\n" //
+ "</td>\n" //
+ "<td class=\"sepline top\">\n" //
+ "<span class=\"bold\">\n" //
+ "<a href=\"http://fahrplan.oebb.at/bin/stboard.exe/dn?ld=web05&input=Wien Hetzendorf Bahnhst&boardType=dep&time=09:13&maxJourneys=10&productsFilter=111111111111&\">\n" //
+ "Wien Hetzendorf Bahnhst\n" //
+ "</a>\n" //
+ "</span>\n" //
+ "<br />\n" //
+ "<a href=\"http://fahrplan.oebb.at/bin/stboard.exe/dn?ld=web05&input=Wien Breitenfurter Stra&#223;e/Hetzendorfer Stra&#223;e%23912101&boardType=dep&time=09:12&maxJourneys=10&productsFilter=111111111111&\">\n" //
+ "Wien Breitenfurter Stra&#223;e/Hetzendorfer Stra&#223;e\n" //
+ "</a>\n" //
+ "09:12\n" //
+ "-\n" //
+ "<a href=\"http://fahrplan.oebb.at/bin/stboard.exe/dn?ld=web05&input=Wien Hetzendorf Bahnhst (Eckartsaugasse)%23912018&boardType=dep&time=09:13&maxJourneys=10&productsFilter=111111111111&\">\n" //
+ "Wien Hetzendorf Bahnhst (Eckartsaugasse)\n" //
+ "</a>\n" //
+ "09:13\n" //
+ "</td>\n");
}
private Matcher assertFineDepartures(String s)
{
Matcher m = OebbProvider.P_DEPARTURES_FINE.matcher(s);
assertTrue(m.matches());
// ParserUtils.printGroups(m);
assertNotNull(m.group(1)); // time
assertNotNull(m.group(3)); // lineType
assertNotNull(m.group(4)); // line
assertNotNull(m.group(6)); // destination
return m;
}
}