();
while (mAddresses.find())
{
final String address = ParserUtils.resolveEntities(mAddresses.group(1)).trim();
if (!addresses.contains(address))
addresses.add(address);
}
if (type.equals("REQ0JourneyStopsS0K"))
fromAddresses = addresses;
else if (type.equals("REQ0JourneyStopsZ0K"))
toAddresses = addresses;
else if (type.equals("REQ0JourneyStops1.0K"))
viaAddresses = addresses;
else
throw new IOException(type);
}
if (fromAddresses != null || viaAddresses != null || toAddresses != null)
return new QueryConnectionsResult(QueryConnectionsResult.Status.AMBIGUOUS, fromAddresses, viaAddresses, toAddresses);
else
return queryConnections(uri, page);
}
public QueryConnectionsResult queryMoreConnections(final String uri) throws IOException
{
final CharSequence page = ParserUtils.scrape(uri);
return queryConnections(uri, page);
}
private static final Pattern P_CONNECTIONS_HEAD = Pattern.compile(".*?" //
+ "Von:.*?(.*?) | .*?" // from
+ "Datum:.*?.., (\\d{2}\\.\\d{2}\\.\\d{2}) | .*?" // date
+ "Nach:.*?(.*?) | .*?" // to
+ "(?:.*?)?" // linkEarlier
+ "(?:.*?)?" // linkLater
, Pattern.DOTALL);
private static final Pattern P_CONNECTIONS_COARSE = Pattern.compile("(.*?)
\n?"//
+ "(.+?)
", Pattern.DOTALL);
private static final Pattern P_CONNECTIONS_FINE = Pattern.compile(".*?" //
+ "name=\"guiVCtrl_connection_detailsOut_select_([\\w-]+)\".*?" // id
+ ".., (\\d{2}\\.\\d{2}\\.\\d{2}).*?" // departureDate
+ "ab.*?(\\d{2}:\\d{2}).*?" // departureTime
+ "duration.*?\\d{1,2}:\\d{2}.*?" //
+ "(?:.., (\\d{2}\\.\\d{2}\\.\\d{2}).*?)?" // arrivalDate
+ "an.*?(\\d{2}:\\d{2}).*?" // arrivalTime
, Pattern.DOTALL);
private static final Pattern P_CONNECTIONS_DETAILS_COARSE = Pattern.compile(".*?" // id
+ "", Pattern.DOTALL);
private static final Pattern P_CONNECTION_DETAILS_COARSE = Pattern.compile("(.*?class=\"stop-station-icon\".*?)
\n?" //
+ "(.*?class=\"stop-station-icon last\".*?)
", Pattern.DOTALL);
static final Pattern P_CONNECTION_DETAILS_FINE = Pattern.compile(".*?" //
+ "" // departureId
+ "(.*?).*?" // departure
+ "\n?(?:.., (\\d{2}\\.\\d{2}\\.\\d{2})\n?)? | .*?" // departureDate
+ "(?:(\\d{2}:\\d{2})| ) | .*?" // departureTime
+ "\n?\\s*(.+?)?\\s*\n? | .*?" // departurePosition
+ "_pic.gif\")
.*?" // line
+ "(?:\n?(\\d+) Min\\..*? | .*?)?" // min
+ "" // arrivalId,
+ "(.*?).*?" // arrival
+ "\n?(?:.., (\\d{2}\\.\\d{2}\\.\\d{2})\n?)? | .*?" // arrivalDate
+ "(?:(\\d{2}:\\d{2})| ) | .*?" // arrivalTime
+ "\n?\\s*(.+?)?\\s*\n? | .*?" // arrivalPosition
, Pattern.DOTALL);
private QueryConnectionsResult queryConnections(final String uri, final CharSequence page) throws IOException
{
final Matcher mHead = P_CONNECTIONS_HEAD.matcher(page);
if (mHead.matches())
{
final String from = ParserUtils.resolveEntities(mHead.group(1));
final Date currentDate = ParserUtils.parseDate(mHead.group(2));
final String to = ParserUtils.resolveEntities(mHead.group(3));
final String linkEarlier = mHead.group(4) != null ? ParserUtils.resolveEntities(mHead.group(4)) : null;
final String linkLater = mHead.group(5) != null ? ParserUtils.resolveEntities(mHead.group(5)) : null;
final List connections = new ArrayList();
final Matcher mConCoarse = P_CONNECTIONS_COARSE.matcher(page);
while (mConCoarse.find())
{
final String set = mConCoarse.group(2) + mConCoarse.group(3);
final Matcher mConFine = P_CONNECTIONS_FINE.matcher(set);
if (mConFine.matches())
{
final String id = mConFine.group(1);
final Date departureDate = ParserUtils.parseDate(mConFine.group(2));
final Date departureTime = ParserUtils.joinDateTime(departureDate, ParserUtils.parseTime(mConFine.group(3)));
final Date arrivalDate = mConFine.group(4) != null ? ParserUtils.parseDate(mConFine.group(4)) : null;
final Date arrivalTime = ParserUtils.joinDateTime(arrivalDate != null ? arrivalDate : departureDate, ParserUtils
.parseTime(mConFine.group(5)));
final String link = uri + "#" + id; // TODO use print link?
final Connection connection = new Connection(id, link, departureTime, arrivalTime, null, null, 0, from, 0, to,
new ArrayList(1));
connections.add(connection);
}
else
{
throw new IllegalArgumentException("cannot parse '" + set + "' on " + uri);
}
}
final Matcher mConDetCoarse = P_CONNECTIONS_DETAILS_COARSE.matcher(page);
while (mConDetCoarse.find())
{
final String id = mConDetCoarse.group(1);
final Connection connection = findConnection(connections, id);
Date lastDate = null;
final Matcher mDetCoarse = P_CONNECTION_DETAILS_COARSE.matcher(mConDetCoarse.group(2));
while (mDetCoarse.find())
{
final String set = mDetCoarse.group(1) + mDetCoarse.group(2);
final Matcher mDetFine = P_CONNECTION_DETAILS_FINE.matcher(set);
if (mDetFine.matches())
{
final int departureId = Integer.parseInt(mDetFine.group(1));
final String departure = ParserUtils.resolveEntities(mDetFine.group(2));
final String lineType = mDetFine.group(6);
final int arrivalId = Integer.parseInt(mDetFine.group(9));
final String arrival = ParserUtils.resolveEntities(mDetFine.group(10));
if (!lineType.equals("fuss") && !lineType.equals("transfer"))
{
Date departureDate = mDetFine.group(3) != null ? ParserUtils.parseDate(mDetFine.group(3)) : null;
if (departureDate != null)
lastDate = departureDate;
else
departureDate = lastDate;
final Date departureTime = ParserUtils.joinDateTime(departureDate, ParserUtils.parseTime(mDetFine.group(4)));
final String departurePosition = mDetFine.group(5) != null ? ParserUtils.resolveEntities(mDetFine.group(5)) : null;
final String line = normalizeLine(lineType, ParserUtils.resolveEntities(mDetFine.group(7)));
Date arrivalDate = mDetFine.group(11) != null ? ParserUtils.parseDate(mDetFine.group(11)) : null;
if (arrivalDate != null)
lastDate = arrivalDate;
else
arrivalDate = lastDate;
final Date arrivalTime = ParserUtils.joinDateTime(arrivalDate, ParserUtils.parseTime(mDetFine.group(12)));
final String arrivalPosition = mDetFine.group(13) != null ? ParserUtils.resolveEntities(mDetFine.group(13)) : null;
final Connection.Trip trip = new Connection.Trip(line, LINES.get(line.charAt(0)), null, departureTime, departurePosition,
departureId, departure, arrivalTime, arrivalPosition, arrivalId, arrival);
connection.parts.add(trip);
}
else
{
final int min = Integer.parseInt(mDetFine.group(8));
final Connection.Footway footway = new Connection.Footway(min, departure, arrival);
connection.parts.add(footway);
}
}
else
{
throw new IllegalArgumentException("cannot parse '" + set + "' on " + uri);
}
}
}
return new QueryConnectionsResult(uri, from, to, currentDate, linkEarlier, linkLater, connections);
}
else
{
throw new IOException(page.toString());
}
}
private Connection findConnection(List connections, String id)
{
for (final Connection connection : connections)
if (connection.id.equals(id))
return connection;
return null;
}
public GetConnectionDetailsResult getConnectionDetails(final String connectionUri) throws IOException
{
throw new UnsupportedOperationException();
}
public String departuresQueryUri(final String stationId, final int maxDepartures)
{
final StringBuilder uri = new StringBuilder();
uri.append("http://fahrplan.sbb.ch/bin/bhftafel.exe/dox");
uri.append("?start=");
if (maxDepartures != 0)
uri.append("&maxJourneys=").append(maxDepartures);
uri.append("&boardType=dep");
uri.append("&productsFilter=1111111111000000");
uri.append("&input=").append(stationId);
return uri.toString();
}
private static final Pattern P_DEPARTURES_HEAD = Pattern.compile(".*?\n?" //
+ "(.*?)
\n?"//
+ "Abfahrt (\\d+:\\d+)\n?"//
+ "Uhr, (\\d+\\.\\d+\\.\\d+)\n?"//
+ "
.*", Pattern.DOTALL);
private static final Pattern P_DEPARTURES_COARSE = Pattern.compile("(.+?)
", Pattern.DOTALL);
private static final Pattern P_DEPARTURES_FINE = Pattern.compile(".*?(.*?)\n?" //
+ ">>\n?" //
+ "(.*?)\n?" //
+ "
\n?" //
+ "(\\d+:\\d+).*", Pattern.DOTALL);
private static final Pattern P_DEPARTURES_URI_STATION_ID = Pattern.compile("input=(\\d+)");
public QueryDeparturesResult queryDepartures(final String uri) throws IOException
{
final CharSequence page = ParserUtils.scrape(uri);
final Matcher mStationId = P_DEPARTURES_URI_STATION_ID.matcher(uri);
if (!mStationId.find())
throw new IllegalStateException(uri);
final int stationId = Integer.parseInt(mStationId.group(1));
// parse page
final Matcher mHead = P_DEPARTURES_HEAD.matcher(page);
if (mHead.matches())
{
final String location = ParserUtils.resolveEntities(mHead.group(1));
final Date currentTime = ParserUtils.joinDateTime(ParserUtils.parseDate(mHead.group(3)), ParserUtils.parseTime(mHead.group(2)));
final List departures = new ArrayList(8);
// choose matcher
final Matcher mDepCoarse = P_DEPARTURES_COARSE.matcher(page);
while (mDepCoarse.find())
{
final Matcher mDepFine = P_DEPARTURES_FINE.matcher(mDepCoarse.group(1));
if (mDepFine.matches())
{
final Departure dep = parseDeparture(mDepFine, currentTime);
if (!departures.contains(dep))
departures.add(dep);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(1) + "' on " + uri);
}
}
return new QueryDeparturesResult(uri, stationId, location, currentTime, departures);
}
else
{
return QueryDeparturesResult.NO_INFO;
}
}
private static Departure parseDeparture(final Matcher mDep, final Date currentTime)
{
// line
final String line = normalizeLine(ParserUtils.resolveEntities(mDep.group(1)));
// destination
final String destination = ParserUtils.resolveEntities(mDep.group(2));
// time
final Calendar current = new GregorianCalendar();
current.setTime(currentTime);
final Calendar parsed = new GregorianCalendar();
parsed.setTime(ParserUtils.parseTime(mDep.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);
return new Departure(parsed.getTime(), line, line != null ? LINES.get(line.charAt(0)) : null, 0, destination);
}
private static final Pattern P_NORMALIZE_LINE = Pattern.compile("([A-Za-zÄÖÜäöüß]+)[\\s-]*(.*)");
private static String normalizeLine(final String type, final String line)
{
final Matcher m = P_NORMALIZE_LINE.matcher(line);
final String strippedLine = m.matches() ? m.group(1) + m.group(2) : line;
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
return normalizedType + strippedLine;
throw new IllegalStateException("cannot normalize type " + type + " line " + line);
}
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);
final char normalizedType = normalizeType(type);
if (normalizedType != 0)
return normalizedType + type + number;
throw new IllegalStateException("cannot normalize type " + type + " number " + number + " line " + line);
}
throw new IllegalStateException("cannot normalize line " + line);
}
private static final Pattern P_NORMALIZE_TYPE_SBAHN = Pattern.compile("S\\d*");
private static final Pattern P_NORMALIZE_TYPE_BUS = Pattern.compile("BUS\\w*");
private static char normalizeType(final String type)
{
// TODO ARZ
final String ucType = type.toUpperCase();
if (ucType.equals("EC")) // EuroCity
return 'I';
if (ucType.equals("EN")) // EuroNight
return 'I';
if (ucType.equals("ICE")) // InterCityExpress
return 'I';
if (ucType.equals("IC")) // InterCity
return 'I';
if (ucType.equals("ICN")) // Intercity-Neigezug, Schweiz
return 'I';
if (ucType.equals("CNL")) // CityNightLine
return 'I';
if (ucType.equals("THA")) // Thalys
return 'I';
if (ucType.equals("TGV")) // Train à Grande Vitesse
return 'I';
if (ucType.equals("RJ")) // RailJet, Österreichische Bundesbahnen
return 'I';
if (ucType.equals("OEC")) // ÖBB-EuroCity
return 'I';
if (ucType.equals("OIC")) // ÖBB-InterCity
return 'I';
if (ucType.equals("X")) // InterConnex
return 'I';
if (ucType.equals("ES")) // Eurostar Italia
return 'I';
if (ucType.equals("EST")) // Eurostar Frankreich
return 'I';
if (ucType.equals("NZ")) // Nachtzug?
return 'I';
if (ucType.equals("IN")) // Oslo
return 'I';
if (ucType.equals("AVE")) // Alta Velocidad Española, Spanien
return 'I';
if (ucType.equals("EM")) // Barcelona-Alicante, Spanien
return 'I';
if (ucType.equals("FYR")) // Fyra, Amsterdam-Schiphol-Rotterdam
return 'I';
if (ucType.equals("R"))
return 'R';
if (ucType.equals("RE")) // RegionalExpress
return 'R';
if (ucType.equals("IR"))
return 'R';
if (ucType.equals("IRE")) // Interregio Express
return 'R';
if (ucType.equals("D"))
return 'R';
if (ucType.equals("E"))
return 'R';
if (ucType.equals("EXT"))
return 'R';
if (ucType.equals("ATZ"))
return 'R';
if (ucType.equals("RSB"))
return 'R';
if (ucType.equals("SN"))
return 'R';
if (ucType.equals("CAT")) // City Airport Train Wien
return 'R';
if (ucType.equals("ALS")) // Spanien
return 'R';
if (ucType.equals("ARC")) // Spanien
return 'R';
if (ucType.equals("TAL")) // Spanien
return 'R';
if (ucType.equals("ATR")) // Spanien
return 'R';
if (ucType.equals("ZUG"))
return 'R';
if (P_NORMALIZE_TYPE_SBAHN.matcher(ucType).matches())
return 'S';
if (ucType.equals("TRAM"))
return 'T';
if (ucType.equals("TRA"))
return 'T';
if (ucType.equals("M")) // Lausanne
return 'T';
if (ucType.equals("T"))
return 'T';
if (ucType.equals("NTR"))
return 'T';
if (ucType.equals("BUS"))
return 'B';
if (ucType.equals("TRO"))
return 'B';
if (ucType.equals("NFB"))
return 'B';
if (ucType.equals("NBU"))
return 'B';
if (ucType.equals("N"))
return 'B';
if (ucType.equals("TX"))
return 'B';
if (ucType.equals("TAXI"))
return 'B';
if (ucType.equals("BUXI"))
return 'B';
if (P_NORMALIZE_TYPE_BUS.matcher(ucType).matches())
return 'B';
if (ucType.equals("BAT"))
return 'F';
if (ucType.equals("BAV"))
return 'F';
if (ucType.equals("FAE"))
return 'F';
if (ucType.equals("KAT")) // z.B. Friedrichshafen <-> Konstanz
return 'F';
if (ucType.equals("GB")) // Gondelbahn
return 'C';
if (ucType.equals("SL")) // Sessel-Lift
return 'C';
if (ucType.equals("LB"))
return 'C';
if (ucType.equals("FUN")) // Standseilbahn
return 'C';
if (ucType.equals("P"))
return '?';
return 0;
}
private static final Map LINES = new HashMap();
static
{
LINES.put('I', new int[] { Color.WHITE, Color.RED, Color.RED });
LINES.put('R', new int[] { Color.GRAY, Color.WHITE });
LINES.put('S', new int[] { Color.parseColor("#006e34"), Color.WHITE });
LINES.put('U', new int[] { Color.parseColor("#003090"), Color.WHITE });
LINES.put('T', new int[] { Color.parseColor("#cc0000"), Color.WHITE });
LINES.put('B', new int[] { Color.parseColor("#993399"), Color.WHITE });
LINES.put('F', new int[] { Color.BLUE, Color.WHITE });
LINES.put('?', new int[] { Color.DKGRAY, Color.WHITE });
}
public int[] lineColors(final String line)
{
return LINES.get(line.charAt(0));
}
}