();
final Matcher mConCoarse = P_CONNECTIONS_COARSE.matcher(page);
while (mConCoarse.find())
{
final Matcher mConFine = P_CONNECTIONS_FINE.matcher(mConCoarse.group(1));
if (mConFine.matches())
{
final String link = ParserUtils.resolveEntities(mConFine.group(1));
Date departureTime = ParserUtils.joinDateTime(currentDate, ParserUtils.parseTime(mConFine.group(2)));
if (!connections.isEmpty())
{
final long diff = ParserUtils.timeDiff(departureTime, connections.get(connections.size() - 1).departureTime);
if (diff > PARSER_DAY_ROLLOVER_THRESHOLD_MS)
departureTime = ParserUtils.addDays(departureTime, -1);
else if (diff < -PARSER_DAY_ROLLOVER_THRESHOLD_MS)
departureTime = ParserUtils.addDays(departureTime, 1);
}
Date arrivalTime = ParserUtils.joinDateTime(currentDate, ParserUtils.parseTime(mConFine.group(3)));
if (departureTime.after(arrivalTime))
arrivalTime = ParserUtils.addDays(arrivalTime, 1);
String line = mConFine.group(4);
if (line != null && !line.endsWith("Um."))
line = normalizeLine(line);
else
line = null;
final Connection connection = new Connection(extractConnectionId(link), link, departureTime, arrivalTime, line,
line != null ? lineColors(line) : null, 0, from.name, 0, to.name, null);
connections.add(connection);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mConCoarse.group(1) + "' on " + uri);
}
}
return new QueryConnectionsResult(uri, from, null, to, linkLater, connections);
}
else
{
throw new IOException(page.toString());
}
}
private static final Pattern P_CONNECTION_DETAILS_HEAD = Pattern.compile(".*?\n" //
+ "- (.*?) -.*?" // firstDeparture
+ "Abfahrt: (\\d{2}\\.\\d{2}\\.\\d{2})
\n"// date
+ "(?:Ankunft: \\d{2}\\.\\d{2}\\.\\d{2}
\n)?" //
+ "Dauer: (\\d{1,2}:\\d{2})
.*?" // duration
, Pattern.DOTALL);
private static final Pattern P_CONNECTION_DETAILS_COARSE = Pattern.compile("/b> -\n(.*?- [^<]*)<", Pattern.DOTALL);
private static final Pattern P_CONNECTION_DETAILS_FINE = Pattern.compile("
\n" //
+ "(?:(.*?) nach (.*?)\n" // line, destination
+ "
\n" //
+ "ab (\\d{1,2}:\\d{2})\n" // departureTime
+ "(?:(.*?)\\s*\n)?" // departurePosition
+ "
\n" //
+ "an (\\d{1,2}:\\d{2})\n" // arrivalTime
+ "(?:(.*?)\\s*\n)?" // arrivalPosition
+ "
\n|" //
+ "]*>\n" //
+ "Fussweg\\s*\n" //
+ "\n" //
+ "(\\d+) Min.
\n)" // footway
+ "- (.*?)" // arrival
, Pattern.DOTALL);
@Override
public GetConnectionDetailsResult getConnectionDetails(final String uri) throws IOException
{
final CharSequence page = ParserUtils.scrape(uri);
final Matcher mHead = P_CONNECTION_DETAILS_HEAD.matcher(page);
if (mHead.matches())
{
final String firstDeparture = ParserUtils.resolveEntities(mHead.group(1));
final Date currentDate = ParserUtils.parseDate(mHead.group(2));
final List parts = new ArrayList(4);
Date lastTime = currentDate;
Date firstDepartureTime = null;
Date lastArrivalTime = null;
String lastArrival = null;
Connection.Trip lastTrip = null;
final Matcher mDetCoarse = P_CONNECTION_DETAILS_COARSE.matcher(page);
while (mDetCoarse.find())
{
final Matcher mDetFine = P_CONNECTION_DETAILS_FINE.matcher(mDetCoarse.group(1));
if (mDetFine.matches())
{
final String departure = lastArrival != null ? lastArrival : firstDeparture;
final String arrival = ParserUtils.resolveEntities(mDetFine.group(8));
lastArrival = arrival;
final String min = mDetFine.group(7);
if (min == null)
{
final String line = normalizeLine(ParserUtils.resolveEntities(mDetFine.group(1)));
final String destination = ParserUtils.resolveEntities(mDetFine.group(2));
final Date departureTime = upTime(lastTime, ParserUtils.joinDateTime(currentDate, ParserUtils.parseTime(mDetFine.group(3))));
final String departurePosition = ParserUtils.resolveEntities(mDetFine.group(4));
final Date arrivalTime = upTime(lastTime, ParserUtils.joinDateTime(currentDate, ParserUtils.parseTime(mDetFine.group(5))));
final String arrivalPosition = ParserUtils.resolveEntities(mDetFine.group(6));
lastTrip = new Connection.Trip(line, line != null ? lineColors(line) : null, 0, destination, departureTime,
departurePosition, 0, departure, arrivalTime, arrivalPosition, 0, arrival);
parts.add(lastTrip);
if (firstDepartureTime == null)
firstDepartureTime = departureTime;
lastArrivalTime = arrivalTime;
}
else
{
if (parts.size() > 0 && parts.get(parts.size() - 1) instanceof Connection.Footway)
{
final Connection.Footway lastFootway = (Connection.Footway) parts.remove(parts.size() - 1);
parts.add(new Connection.Footway(lastFootway.min + Integer.parseInt(min), 0, lastFootway.departure, 0, arrival));
}
else
{
parts.add(new Connection.Footway(Integer.parseInt(min), 0, departure, 0, arrival));
}
}
}
else
{
throw new IllegalArgumentException("cannot parse '" + mDetCoarse.group(1) + "' on " + uri);
}
}
return new GetConnectionDetailsResult(currentDate, new Connection(extractConnectionId(uri), uri, firstDepartureTime, lastArrivalTime,
null, null, 0, firstDeparture, 0, lastArrival, parts));
}
else
{
throw new IOException(page.toString());
}
}
private static Date upTime(final Date lastTime, Date time)
{
while (time.before(lastTime))
time = ParserUtils.addDays(time, 1);
lastTime.setTime(time.getTime());
return time;
}
private String departuresQueryUri(final String stationId, final int maxDepartures)
{
final DateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yy");
final DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
final Date now = new Date();
final StringBuilder uri = new StringBuilder();
uri.append(API_BASE).append("stboard.exe/dox");
uri.append("?input=").append(stationId);
uri.append("&boardType=dep"); // show departures
uri.append("&maxJourneys=").append(maxDepartures != 0 ? maxDepartures : 50); // maximum taken from RMV site
uri.append("&time=").append(TIME_FORMAT.format(now));
uri.append("&date=").append(DATE_FORMAT.format(now));
uri.append("&disableEquivs=yes"); // don't use nearby stations
uri.append("&start=yes");
return uri.toString();
}
private static final Pattern P_DEPARTURES_HEAD_COARSE = Pattern.compile(".*?" //
+ "(?:" //
+ "\n(.*?)
\n" // head
+ "(.*?).*?" // departures
+ "input=(\\d+).*?" // locationId
+ "|(Eingabe kann nicht interpretiert)" // messages
+ "|(Internal Error)" // messages
+ ").*?", Pattern.DOTALL);
private static final Pattern P_DEPARTURES_HEAD_FINE = Pattern.compile("" //
+ "(.*?)
.*?" //
+ "Abfahrt (\\d+:\\d+).*?" //
+ "Uhr, (\\d+\\.\\d+\\.\\d+).*?" //
, Pattern.DOTALL);
private static final Pattern P_DEPARTURES_COARSE = Pattern.compile("
\n(.+?)
", Pattern.DOTALL);
static final Pattern P_DEPARTURES_FINE = Pattern.compile("" //
+ "\\s*(.*?)\\s*.*?" // line
+ ">>\n" //
+ "(.*?)\n" // destination
+ "
\n" //
+ "(\\d{1,2}:\\d{2})\n" // plannedTime
+ "(?:keine Prognose verfügbar\n)?" //
+ "(?:ca\\. (\\d{1,2}:\\d{2})\n)?" // predictedTime
+ "(?:heute (Gl\\. " + ParserUtils.P_PLATFORM + ")
\n)?" // predictedPosition
+ "(?:(Gl\\. " + ParserUtils.P_PLATFORM + ")
\n)?" // position
+ "(?:([^>]*)\n)?" // message
+ "(?:
\n[^<]*\n
\n)*" // (messages)
, Pattern.DOTALL);
public QueryDeparturesResult queryDepartures(final String stationId, final int maxDepartures) throws IOException
{
final CharSequence page = ParserUtils.scrape(departuresQueryUri(stationId, maxDepartures));
// parse page
final Matcher mHeadCoarse = P_DEPARTURES_HEAD_COARSE.matcher(page);
if (mHeadCoarse.matches())
{
// messages
if (mHeadCoarse.group(4) != null)
return new QueryDeparturesResult(Status.INVALID_STATION, Integer.parseInt(stationId));
else if (mHeadCoarse.group(5) != null)
return new QueryDeparturesResult(Status.SERVICE_DOWN, Integer.parseInt(stationId));
final int locationId = Integer.parseInt(mHeadCoarse.group(3));
final Matcher mHeadFine = P_DEPARTURES_HEAD_FINE.matcher(mHeadCoarse.group(1));
if (mHeadFine.matches())
{
final String location = ParserUtils.resolveEntities(mHeadFine.group(1));
final Date currentTime = ParserUtils.joinDateTime(ParserUtils.parseDate(mHeadFine.group(3)), ParserUtils
.parseTime(mHeadFine.group(2)));
final List departures = new ArrayList(8);
final Matcher mDepCoarse = P_DEPARTURES_COARSE.matcher(mHeadCoarse.group(2));
while (mDepCoarse.find())
{
final Matcher mDepFine = P_DEPARTURES_FINE.matcher(mDepCoarse.group(1));
if (mDepFine.matches())
{
final String line = normalizeLine(ParserUtils.resolveEntities(mDepFine.group(1)));
final String destination = ParserUtils.resolveEntities(mDepFine.group(2));
final Calendar current = new GregorianCalendar();
current.setTime(currentTime);
final Calendar parsed = new GregorianCalendar();
parsed.setTime(ParserUtils.parseTime(mDepFine.group(3)));
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 plannedTime = parsed.getTime();
Date predictedTime = null;
if (mDepFine.group(4) != null)
{
parsed.setTime(ParserUtils.parseTime(mDepFine.group(4)));
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);
predictedTime = parsed.getTime();
}
final String position = ParserUtils.resolveEntities(ParserUtils.selectNotNull(mDepFine.group(5), mDepFine.group(6)));
final Departure dep = new Departure(plannedTime, predictedTime, line, line != null ? lineColors(line) : null, null, position,
0, destination, null);
if (!departures.contains(dep))
departures.add(dep);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(1) + "' on " + stationId);
}
}
return new QueryDeparturesResult(new Location(LocationType.STATION, locationId, 0, 0, location), departures);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mHeadCoarse.group(1) + "' on " + stationId);
}
}
else
{
throw new IllegalArgumentException("cannot parse '" + page + "' on " + stationId);
}
}
private static String normalizeLine(final String line)
{
if (line == null || line.length() == 0)
return null;
final Matcher m = P_NORMALIZE_LINE.matcher(line);
if (m.matches())
{
final String type = m.group(1);
final String number = m.group(2).replace(" ", "");
if (type.equals("ICE")) // InterCityExpress
return "IICE" + number;
if (type.equals("IC")) // InterCity
return "IIC" + number;
if (type.equals("EC")) // EuroCity
return "IEC" + number;
if (type.equals("EN")) // EuroNight
return "IEN" + number;
if (type.equals("CNL")) // CityNightLine
return "ICNL" + number;
if (type.equals("DNZ")) // Basel-Minsk, Nacht
return "IDNZ" + number;
if (type.equals("D")) // Prag-Fulda
return "ID" + number;
if (type.equals("RB")) // RegionalBahn
return "RRB" + number;
if (type.equals("RE")) // RegionalExpress
return "RRE" + number;
if (type.equals("SE")) // StadtExpress
return "RSE" + number;
if (type.equals("R"))
return "R" + number;
if (type.equals("S"))
return "SS" + number;
if (type.equals("U"))
return "UU" + number;
if (type.equals("Tram"))
return "T" + number;
if (type.equals("RT")) // RegioTram
return "TRT" + number;
if (type.startsWith("Bus"))
return "B" + type.substring(3) + number;
if (type.startsWith("AST")) // Anruf-Sammel-Taxi
return "BAST" + type.substring(3) + number;
if (type.startsWith("ALT")) // Anruf-Linien-Taxi
return "BALT" + type.substring(3) + number;
if (type.equals("LTaxi"))
return "BLTaxi" + number;
if (type.equals("AT")) // AnschlußSammelTaxi
return "BAT" + number;
if (type.equals("SCH"))
return "FSCH" + number;
throw new IllegalStateException("cannot normalize type " + type + " number " + number + " line " + line);
}
throw new IllegalStateException("cannot normalize line " + line);
}
@Override
protected char normalizeType(final String type)
{
throw new UnsupportedOperationException();
}
}