();
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
+ "(?:(.*?)\n"//
+ "(.*?)
\n"//
+ "(?:.*?
\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(".*?" //
+ "\n" //
+ "(?:]*>)?" // departureId
+ "([^\n<]*?)<.*?" // departure
+ " | ]*>\n(?:.., (\\d{2}\\.\\d{2}\\.\\d{2})\n)? | .*?" // departureDate
+ "]*>(?:(\\d{2}:\\d{2})| ) | .*?" // departureTime
+ "]*>\n(?:]*>\n)?(.+?)?\\s*(?: ]*>\n\n)? | .*?" // departurePosition
+ "
]*>\\s*(.*?)\\s*|" // line
+ "\n(\\d+) Min\\.).*?" // min
+ "\n" //
+ "(?:]*>)?" // arrivalId
+ "([^\n<]*?)<.*?" // arrival
+ " | ]*>\n(?:.., (\\d{2}\\.\\d{2}\\.\\d{2})\n)? | .*?" // arrivalDate
+ "]*>(?:(\\d{2}:\\d{2})| ) | .*?" // arrivalTime
+ "]*>\n(?:]*>\n)?(.+?)?\\s*(?: ]*>\n\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();
String oldZebra = null;
final Matcher mConCoarse = P_CONNECTIONS_COARSE.matcher(page);
while (mConCoarse.find())
{
final String zebra = mConCoarse.group(1);
if (oldZebra != null && zebra.equals(oldZebra))
throw new IllegalArgumentException("missed row? last:" + zebra);
else
oldZebra = zebra;
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 = mDetFine.group(1) != null ? Integer.parseInt(mDetFine.group(1)) : 0;
final String departure = ParserUtils.resolveEntities(mDetFine.group(2));
Date departureDate = mDetFine.group(3) != null ? ParserUtils.parseDate(mDetFine.group(3)) : lastDate;
if (departureDate != null)
lastDate = departureDate;
final String lineType = mDetFine.group(6);
final int arrivalId = mDetFine.group(9) != null ? Integer.parseInt(mDetFine.group(9)) : 0;
final String arrival = ParserUtils.resolveEntities(mDetFine.group(10));
Date arrivalDate = mDetFine.group(11) != null ? ParserUtils.parseDate(mDetFine.group(11)) : lastDate;
if (arrivalDate != null)
lastDate = arrivalDate;
if (!lineType.equals("fuss") && !lineType.equals("transfer"))
{
if (departureId == 0)
throw new IllegalStateException("departureId");
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)));
if (arrivalId == 0)
throw new IllegalStateException("arrivalId");
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, lineColors(line), 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, departureId, departure, arrivalId, 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(final List connections, final 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);
uri.append("&disableEquivs=yes"); // don't use nearby stations
return uri.toString();
}
private static final Pattern P_DEPARTURES_HEAD_COARSE = Pattern.compile(".*?" //
+ "(?:" //
+ "\n(.+?)\n
.*?" // head
+ "(?:(.+)|(an dieser Haltestelle keines)).*?" // departures
+ "\n(.+?)\n
" //
+ "|(Informationen zu)" // messages
+ "|(Verbindung zum Server konnte leider nicht hergestellt werden|kann vom Server derzeit leider nicht bearbeitet werden)" // messages
+ ").*?" //
, Pattern.DOTALL);
private static final Pattern P_DEPARTURES_HEAD_FINE = Pattern.compile("" //
+ "(.*?)
\n" // location
+ "Abfahrt (\\d+:\\d+)\n" // time
+ "Uhr, (\\d{2}\\.\\d{2}\\.\\d{2}).*?" // date
+ "input=(\\d+).*?" // locationId
, Pattern.DOTALL);
private static final Pattern P_DEPARTURES_COARSE = Pattern.compile("\n(.+?)
", Pattern.DOTALL);
private static final Pattern P_DEPARTURES_FINE = Pattern.compile("" //
+ "(.*?)\n" // line
+ ">>\n" //
+ "(.*?)\n" // destination
+ "
\n" //
+ "(\\d+:\\d+)\n" // time
+ "(?:Gl\\. (" + ParserUtils.P_PLATFORM + ")\n)?" // position
+ ".*?" //
, Pattern.DOTALL);
public QueryDeparturesResult queryDepartures(final String uri) throws IOException
{
final CharSequence page = ParserUtils.scrape(uri);
// parse page
final Matcher mHeadCoarse = P_DEPARTURES_HEAD_COARSE.matcher(page);
if (mHeadCoarse.matches())
{
// messages
if (mHeadCoarse.group(3) != null)
return new QueryDeparturesResult(uri, Status.NO_INFO);
else if (mHeadCoarse.group(5) != null)
return new QueryDeparturesResult(uri, Status.INVALID_STATION);
else if (mHeadCoarse.group(6) != null)
return new QueryDeparturesResult(uri, Status.SERVICE_DOWN);
final String c = mHeadCoarse.group(1) + mHeadCoarse.group(4);
final Matcher mHeadFine = P_DEPARTURES_HEAD_FINE.matcher(c);
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 int locationId = Integer.parseInt(mHeadFine.group(4));
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 String position = ParserUtils.resolveEntities(mDepFine.group(4));
final Departure dep = new Departure(parsed.getTime(), line, line != null ? lineColors(line) : null, position, 0, destination);
if (!departures.contains(dep))
departures.add(dep);
}
else
{
throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(1) + "' on " + uri);
}
}
return new QueryDeparturesResult(uri, locationId, location, departures);
}
else
{
throw new IllegalArgumentException("cannot parse '" + c + "' on " + uri);
}
}
else
{
throw new IllegalArgumentException("cannot parse '" + page + "' on " + uri);
}
}
private 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("SN?\\d*");
private static final Pattern P_NORMALIZE_TYPE_BUS = Pattern.compile("BUS\\w*");
@Override
protected char normalizeType(final String type)
{
final String ucType = type.toUpperCase();
final char t = normalizeCommonTypes(ucType);
if (t != 0)
return t;
if (ucType.equals("ICN")) // Intercity-Neigezug, Schweiz
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("ARZ")) // Frankreich, Nacht
return 'I';
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 (P_NORMALIZE_TYPE_SBAHN.matcher(ucType).matches())
return 'S';
if (ucType.equals("MET")) // Lausanne
return 'U';
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("TRO"))
return 'B';
if (ucType.equals("NTO")) // Niederflurtrolleybus zwischen Bern, Bahnhofsplatz und Bern, Wankdorf Bahnhof
return 'B';
if (ucType.equals("NFB"))
return 'B';
if (ucType.equals("NBU"))
return 'B';
if (ucType.equals("MIN"))
return 'B';
if (ucType.equals("MID"))
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;
}
}